@aparte/core 0.9.0 → 0.10.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 (70) hide show
  1. package/dist/components/bubble/aparte-chat-bubble.d.ts +24 -3
  2. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
  3. package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
  4. package/dist/components/composer/aparte-composer-add-attachment.d.ts +11 -0
  5. package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
  6. package/dist/components/composer/aparte-composer-cancel.d.ts +8 -0
  7. package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
  8. package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
  9. package/dist/components/composer/aparte-composer-send.d.ts +32 -0
  10. package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
  11. package/dist/components/elicitation/aparte-elicitation.d.ts +10 -0
  12. package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
  13. package/dist/components/viewport/aparte-chat-viewport.d.ts +3 -1
  14. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  15. package/dist/config/aparte-config.d.ts +34 -1
  16. package/dist/config/aparte-config.d.ts.map +1 -1
  17. package/dist/config/config-subscribe.d.ts +4 -0
  18. package/dist/config/config-subscribe.d.ts.map +1 -0
  19. package/dist/config/index.d.ts +1 -0
  20. package/dist/config/index.d.ts.map +1 -1
  21. package/dist/config/locale.d.ts +98 -0
  22. package/dist/config/locale.d.ts.map +1 -1
  23. package/dist/config/sanitize.d.ts.map +1 -1
  24. package/dist/custom-elements.json +4588 -4293
  25. package/dist/elicitation/panel.d.ts +13 -0
  26. package/dist/elicitation/panel.d.ts.map +1 -1
  27. package/dist/host/aparte-chat-host.d.ts +3 -1
  28. package/dist/host/aparte-chat-host.d.ts.map +1 -1
  29. package/dist/{index-hQQljwfX.js → index-BnKBciDG.js} +515 -232
  30. package/dist/index-BnKBciDG.js.map +1 -0
  31. package/dist/index.css +147 -310
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +248 -87
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.node.d.ts +3 -2
  37. package/dist/index.node.d.ts.map +1 -1
  38. package/dist/index.node.js +60 -57
  39. package/dist/index.node.js.map +1 -1
  40. package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
  41. package/dist/renderers/highlight-stream.d.ts +17 -0
  42. package/dist/renderers/highlight-stream.d.ts.map +1 -0
  43. package/dist/renderers/segment-renderers.d.ts.map +1 -1
  44. package/dist/renderers/segments/artifact/binary-file.d.ts.map +1 -1
  45. package/dist/renderers/segments/artifact/card.d.ts.map +1 -1
  46. package/dist/renderers/segments/artifact/shared.d.ts +19 -2
  47. package/dist/renderers/segments/artifact/shared.d.ts.map +1 -1
  48. package/dist/renderers/segments/code.d.ts.map +1 -1
  49. package/dist/renderers/segments/error.d.ts.map +1 -1
  50. package/dist/renderers/segments/pipeline-waiting.d.ts.map +1 -1
  51. package/dist/renderers/segments/thinking.d.ts.map +1 -1
  52. package/dist/renderers/segments/tool-call.d.ts.map +1 -1
  53. package/dist/types/event-map.d.ts +1 -2
  54. package/dist/types/event-map.d.ts.map +1 -1
  55. package/dist/types/events.d.ts +0 -22
  56. package/dist/types/events.d.ts.map +1 -1
  57. package/dist/types/index.d.ts +2 -2
  58. package/dist/types/index.d.ts.map +1 -1
  59. package/dist/types/models.d.ts +1 -6
  60. package/dist/types/models.d.ts.map +1 -1
  61. package/dist/types/segments.d.ts +87 -67
  62. package/dist/types/segments.d.ts.map +1 -1
  63. package/dist/utils/segments.d.ts +75 -5
  64. package/dist/utils/segments.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/dist/index-hQQljwfX.js.map +0 -1
  67. package/dist/renderers/segments/file-tree.d.ts +0 -3
  68. package/dist/renderers/segments/file-tree.d.ts.map +0 -1
  69. package/dist/renderers/segments/terminal.d.ts +0 -3
  70. package/dist/renderers/segments/terminal.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { H as escapeAttr, Y as resolveConfig, C as cssEscape, _ as runWithConfig, I as escapeHtml, L as getSegmentRenderer, M as installDefaultRenderersOnce, q as AparteMessageRepository, a2 as stampSegmentActivity, a3 as stampSegmentOnInsert, a4 as stampSegmentOnUpdate, a5 as renumberSegments, a6 as openSegmentIds, a7 as isTerminalStatus, N as isAwaitingReply, T as populateBubbleFromMessage, a1 as uuid, Z as revokeAttachmentUrls, v as buildElicitationPanel } from "./index-hQQljwfX.js";
2
- import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, r, s, t, u, w, x, y, z, B, D, E, F, G, J, K, O, P, Q, R, S, U, V, W, X, $, a0 } from "./index-hQQljwfX.js";
1
+ import { I as escapeAttr, D as cssEscape, $ as runWithConfig, Z as resolveConfig, a5 as mergeSegmentUpdate, J as escapeHtml, M as getSegmentRenderer, N as installDefaultRenderersOnce, r as AparteMessageRepository, a6 as stampSegmentActivity, a7 as stampSegmentOnInsert, a8 as stampSegmentOnUpdate, a9 as renumberSegments, aa as openSegmentIds, ab as isTerminalStatus, ac as adoptMessageSegments, O as isAwaitingReply, U as populateBubbleFromMessage, a4 as uuid, _ as revokeAttachmentUrls, a2 as subscribeConfigChange, w as buildElicitationPanel } from "./index-BnKBciDG.js";
2
+ import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, s, t, u, v, x, y, z, B, C, E, F, G, H, K, L, P, Q, R, S, T, V, W, X, Y, a0, a1, a3 } from "./index-BnKBciDG.js";
3
3
  class AparteOption extends HTMLElement {
4
4
  static get observedAttributes() {
5
5
  return ["value", "disabled", "selected", "data-status"];
@@ -804,7 +804,38 @@ class AparteChatBubble extends HTMLElement {
804
804
  this._updateName();
805
805
  this._updateLocalizedLabels();
806
806
  this._updateWaiting();
807
+ this._renderAvatar();
808
+ this._relabelSegments();
809
+ this._updateTimestamp(this.getAttribute("timestamp"));
807
810
  };
811
+ /**
812
+ * Ask every rendered segment to re-read its config-derived text.
813
+ *
814
+ * Not `_renderSegments()`, which wipes the container and rebuilds: that destroys a
815
+ * mounted artifact preview, reverts a reasoning block the reader expanded by
816
+ * clicking `<summary>` (the DOM's real state is never written back to `collapsed`),
817
+ * resets scroll inside long terminal panes, drops focus from an Approve/Reject
818
+ * gate, and throws away the incremental Markdown parser's buffered lookahead
819
+ * mid-stream — for a change that added no content. It also fires container-wide
820
+ * childList mutations, which is what the viewport's observer reads as "scroll to
821
+ * the bottom".
822
+ *
823
+ * `relabel` is the narrow alternative, bound by the same no-child-node rule as
824
+ * `update()`. A renderer that has no config-derived text does not implement it,
825
+ * and this loop simply skips it.
826
+ */
827
+ _relabelSegments() {
828
+ if (!this._segmentsEl) return;
829
+ for (const segment of this._segments) {
830
+ const renderer = resolveSegmentRenderer(segment.type, this._cfg);
831
+ if (!renderer?.relabel) continue;
832
+ const el = this._segmentsEl.querySelector(
833
+ `:scope > [data-segment-id="${cssEscape(segment.id)}"]`
834
+ );
835
+ if (!el) continue;
836
+ runWithConfig(this._cfg, () => renderer.relabel(el, segment));
837
+ }
838
+ }
808
839
  /**
809
840
  * Re-apply the locale strings written straight into the markup by `_render()` —
810
841
  * the accessible names a screen reader reads, which nothing else refreshes.
@@ -892,7 +923,7 @@ class AparteChatBubble extends HTMLElement {
892
923
  }
893
924
  /** Set segments for rich content */
894
925
  setSegments(segments) {
895
- this._segments = segments;
926
+ this._segments = [...segments];
896
927
  this._renderSegments();
897
928
  this._updateWaiting();
898
929
  }
@@ -906,7 +937,7 @@ class AparteChatBubble extends HTMLElement {
906
937
  updateSegment(segmentId, updates) {
907
938
  const index = this._segments.findIndex((s2) => s2.id === segmentId);
908
939
  if (index !== -1) {
909
- const updated = { ...this._segments[index], ...updates };
940
+ const updated = mergeSegmentUpdate(this._segments[index], updates);
910
941
  this._segments[index] = updated;
911
942
  this._applySegmentUpdate(segmentId, updated, updates);
912
943
  }
@@ -1135,9 +1166,12 @@ class AparteChatBubble extends HTMLElement {
1135
1166
  * (same null-guarded degradation as the other region hooks).
1136
1167
  */
1137
1168
  _updateWaiting() {
1169
+ const empty = this._segments.length === 0 && !this._content.trim();
1170
+ const waiting = this._streaming && this._role !== "user" && empty;
1171
+ const box = this.querySelector(".aparte-message-content");
1172
+ if (box) box.hidden = empty && !waiting;
1138
1173
  const el = this.querySelector(".aparte-waiting");
1139
1174
  if (!el) return;
1140
- const waiting = this._streaming && this._role !== "user" && this._segments.length === 0 && !this._content.trim();
1141
1175
  el.hidden = !waiting;
1142
1176
  if (!waiting) return;
1143
1177
  const label = this._cfg.getLocale().typing;
@@ -1145,9 +1179,13 @@ class AparteChatBubble extends HTMLElement {
1145
1179
  if (sr && sr.textContent !== label) sr.textContent = label;
1146
1180
  }
1147
1181
  /**
1148
- * Hand the avatar host element off to the registered AvatarProvider, if
1149
- * any. Falls back to the default initial / image rendered by `_render()`
1150
- * when no provider is set.
1182
+ * Hand the avatar host element off to the registered AvatarProvider, if any.
1183
+ *
1184
+ * With no provider the slot is left exactly as the shell rendered it — which for
1185
+ * the default shell means EMPTY, and hidden by `.aparte-avatar:empty`. This used
1186
+ * to claim it "falls back to the default initial rendered by `_render()`"; there
1187
+ * is no such initial, and believing there was is what made `_updateRole` write
1188
+ * one.
1151
1189
  */
1152
1190
  _renderAvatar() {
1153
1191
  const avatar = this.querySelector(".aparte-avatar");
@@ -1175,7 +1213,7 @@ class AparteChatBubble extends HTMLElement {
1175
1213
  }
1176
1214
  if (avatar) {
1177
1215
  avatar.setAttribute("data-role", this._role);
1178
- avatar.textContent = this._getAvatarInitial();
1216
+ if (avatar.textContent) avatar.textContent = this._getAvatarInitial();
1179
1217
  }
1180
1218
  if (nameEl) {
1181
1219
  nameEl.textContent = this._getDisplayName();
@@ -1186,7 +1224,9 @@ class AparteChatBubble extends HTMLElement {
1186
1224
  _updateName() {
1187
1225
  const avatar = this.querySelector(".aparte-avatar");
1188
1226
  const nameEl = this.querySelector(".aparte-name");
1189
- if (avatar && !this._cfg.getAvatarProvider()) avatar.textContent = this._getAvatarInitial();
1227
+ if (avatar && avatar.textContent && !this._cfg.getAvatarProvider()) {
1228
+ avatar.textContent = this._getAvatarInitial();
1229
+ }
1190
1230
  if (nameEl) nameEl.textContent = this._getDisplayName();
1191
1231
  }
1192
1232
  _updateContent() {
@@ -1271,7 +1311,7 @@ class AparteChatBubble extends HTMLElement {
1271
1311
  if (!timestampEl || !value) return;
1272
1312
  try {
1273
1313
  const date = new Date(isNaN(Number(value)) ? value : Number(value));
1274
- timestampEl.textContent = date.toLocaleTimeString(void 0, {
1314
+ timestampEl.textContent = date.toLocaleTimeString(this._cfg.getLocale().tag || void 0, {
1275
1315
  hour: "2-digit",
1276
1316
  minute: "2-digit"
1277
1317
  });
@@ -2023,7 +2063,14 @@ class AparteChatViewport extends HTMLElement {
2023
2063
  if (!message.segments) {
2024
2064
  message.segments = [];
2025
2065
  }
2026
- const stamped = stampSegmentOnInsert(message.segments, segment, messageId);
2066
+ const stamped = stampSegmentOnInsert(
2067
+ message.segments,
2068
+ segment,
2069
+ messageId,
2070
+ // THIS chat's defaults, not the page's: two chats on one page can be
2071
+ // configured differently, and the config seam is per instance.
2072
+ resolveConfig(this).getSegmentDefaults(segment.type)
2073
+ );
2027
2074
  message.segments.push(stamped);
2028
2075
  this._notifyBubble(messageId, "addSegment", stamped);
2029
2076
  this._autoScroll();
@@ -2040,10 +2087,7 @@ class AparteChatViewport extends HTMLElement {
2040
2087
  if (segmentIndex !== -1) {
2041
2088
  const current = message.segments[segmentIndex];
2042
2089
  const stamped = stampSegmentOnUpdate(current, updates);
2043
- message.segments[segmentIndex] = {
2044
- ...current,
2045
- ...stamped
2046
- };
2090
+ message.segments[segmentIndex] = mergeSegmentUpdate(current, stamped);
2047
2091
  this._notifyBubble(messageId, "updateSegment", { segmentId, updates: stamped });
2048
2092
  }
2049
2093
  }
@@ -2143,7 +2187,7 @@ class AparteChatViewport extends HTMLElement {
2143
2187
  * which both records the message and creates its bubble element.
2144
2188
  */
2145
2189
  addMessage(message) {
2146
- this._repo.addOrUpdateMessage(this._repo.headId, { ...message });
2190
+ this._repo.addOrUpdateMessage(this._repo.headId, adoptMessageSegments({ ...message }));
2147
2191
  this._pruneRenderedBubbles();
2148
2192
  this._autoScroll();
2149
2193
  }
@@ -2154,8 +2198,20 @@ class AparteChatViewport extends HTMLElement {
2154
2198
  * When `_frameworkManagedDOM` is true, only the internal repo is updated —
2155
2199
  * the framework owns the DOM and will create the bubble element itself.
2156
2200
  */
2157
- appendMessage(message) {
2158
- this._repo.addOrUpdateMessage(this._repo.headId, { ...message });
2201
+ appendMessage(message, options) {
2202
+ const stored = options?.historical ? adoptMessageSegments(message) : message.segments?.length ? { ...message, segments: message.segments.reduce(
2203
+ (acc, segment) => {
2204
+ acc.push(stampSegmentOnInsert(
2205
+ acc,
2206
+ segment,
2207
+ message.id,
2208
+ resolveConfig(this).getSegmentDefaults(segment.type)
2209
+ ));
2210
+ return acc;
2211
+ },
2212
+ []
2213
+ ) } : { ...message };
2214
+ this._repo.addOrUpdateMessage(this._repo.headId, stored);
2159
2215
  if (!this._frameworkManagedDOM) {
2160
2216
  const wrapper = this.querySelector(".aparte-messages-wrapper");
2161
2217
  if (wrapper) {
@@ -2172,7 +2228,7 @@ class AparteChatViewport extends HTMLElement {
2172
2228
  } else {
2173
2229
  wrapper.appendChild(bubble);
2174
2230
  }
2175
- populateBubbleFromMessage(bubble, message);
2231
+ populateBubbleFromMessage(bubble, stored);
2176
2232
  }
2177
2233
  }
2178
2234
  this._pruneRenderedBubbles();
@@ -2322,7 +2378,13 @@ class AparteChatViewport extends HTMLElement {
2322
2378
  */
2323
2379
  importTree(tree) {
2324
2380
  this.clearAll({ revokeAttachments: false });
2325
- this._repo.import(tree);
2381
+ this._repo.import({
2382
+ ...tree,
2383
+ messages: tree.messages.map((entry) => ({
2384
+ ...entry,
2385
+ message: adoptMessageSegments(entry.message)
2386
+ }))
2387
+ });
2326
2388
  this._reRenderActivePath();
2327
2389
  }
2328
2390
  /**
@@ -2370,7 +2432,7 @@ class AparteChatViewport extends HTMLElement {
2370
2432
  setMessages(messages) {
2371
2433
  this.clearAll({ revokeAttachments: false });
2372
2434
  for (const m2 of messages) {
2373
- this.appendMessage(m2);
2435
+ this.appendMessage(m2, { historical: true });
2374
2436
  }
2375
2437
  }
2376
2438
  /**
@@ -3231,6 +3293,7 @@ class AparteComposerInput extends HTMLElement {
3231
3293
  this._render();
3232
3294
  this._connectToRoot();
3233
3295
  this._scheduleInitialReflow();
3296
+ this._unsubscribes.push(subscribeConfigChange(this, () => this._updatePlaceholder()));
3234
3297
  }
3235
3298
  disconnectedCallback() {
3236
3299
  this._editor?.removeEventListener("input", this._onInput);
@@ -3434,6 +3497,16 @@ if (!customElements.get("aparte-composer-input")) {
3434
3497
  class AparteComposerSend extends HTMLElement {
3435
3498
  _button = null;
3436
3499
  _unsubscribes = [];
3500
+ /**
3501
+ * The last `panel-change` payload.
3502
+ *
3503
+ * This button has four meanings — send, stop, submit an answer, advance to the
3504
+ * next question — and three of them are decided by state it does not own: the
3505
+ * root's `streaming`, and this payload. It was read straight out of the event's
3506
+ * arguments and thrown away, so nothing could recompute the button's chrome
3507
+ * afterwards; a config change had no way to know which of the four to write.
3508
+ */
3509
+ _panel = null;
3437
3510
  // Bound handler
3438
3511
  _onClick = this._handleClick.bind(this);
3439
3512
  connectedCallback() {
@@ -3483,27 +3556,56 @@ class AparteComposerSend extends HTMLElement {
3483
3556
  root._on("attachments-change", () => this._syncState())
3484
3557
  );
3485
3558
  this._unsubscribes.push(
3486
- root._on("panel-change", ({ active, submitEnabled, mode }) => {
3487
- if (!this._button) return;
3488
- if (active) {
3489
- this._button.disabled = !submitEnabled;
3490
- const cfg = resolveConfig(this);
3491
- const advancing = mode === "advance";
3492
- this._button.innerHTML = advancing ? cfg.getIcon("nextBranch") : this._getSubmitIcon();
3493
- const label = advancing ? cfg.t("elicitationNext") || "Next" : cfg.t("submitButton") || "Submit";
3494
- this._button.setAttribute("aria-label", label);
3495
- this._button.setAttribute("title", label);
3496
- this._button.classList.remove("is-streaming");
3497
- } else {
3498
- const root2 = this._getRoot();
3499
- if (root2?.streaming) {
3500
- this._syncStreamingState(true);
3501
- } else {
3502
- this._syncState();
3503
- }
3504
- }
3559
+ root._on("panel-change", (payload) => {
3560
+ this._panel = payload;
3561
+ this._refreshChrome();
3505
3562
  })
3506
3563
  );
3564
+ this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));
3565
+ }
3566
+ /**
3567
+ * Write the chrome for the mode the button is IN, deciding before writing.
3568
+ *
3569
+ * Never `_render()`: it returns early once the button exists, and its own
3570
+ * disabled/icon computation consults neither `root.streaming` nor the panel — so
3571
+ * rebuilding mid-turn would put a paper plane back while a reply was still
3572
+ * streaming, and rebuilding with the question panel open would silently drop out
3573
+ * of answer mode. It would also take the focus off the one control in this
3574
+ * composer most likely to be holding it.
3575
+ */
3576
+ _refreshChrome() {
3577
+ if (!this._button) return;
3578
+ if (this._panel?.active) {
3579
+ this._syncPanelState();
3580
+ return;
3581
+ }
3582
+ if (this._getRoot()?.streaming) {
3583
+ this._syncStreamingState(true);
3584
+ return;
3585
+ }
3586
+ this._syncState();
3587
+ }
3588
+ /**
3589
+ * Panel open: this one button now means "answer", and WHICH answer depends on
3590
+ * where you are in the form.
3591
+ *
3592
+ * The icon has to move with the meaning: it drew a paper plane while the label
3593
+ * already said "Submit", so it read as "send a message" while it meant "answer
3594
+ * this question". And a check on a form with three questions left was just as
3595
+ * wrong — hence a chevron while there is more ahead. The visual is what a user
3596
+ * reads.
3597
+ */
3598
+ _syncPanelState() {
3599
+ const panel = this._panel;
3600
+ if (!this._button || !panel?.active) return;
3601
+ const cfg = resolveConfig(this);
3602
+ const advancing = panel.mode === "advance";
3603
+ this._button.disabled = !panel.submitEnabled;
3604
+ this._button.innerHTML = advancing ? cfg.getIcon("nextBranch") : this._getSubmitIcon();
3605
+ const label = advancing ? cfg.t("elicitationNext") || "Next" : cfg.t("submitButton") || "Submit";
3606
+ this._button.setAttribute("aria-label", label);
3607
+ this._button.setAttribute("title", label);
3608
+ this._button.classList.remove("is-streaming");
3507
3609
  }
3508
3610
  _handleClick(e2) {
3509
3611
  e2.preventDefault();
@@ -3526,8 +3628,9 @@ class AparteComposerSend extends HTMLElement {
3526
3628
  if (streaming) {
3527
3629
  this._button.disabled = false;
3528
3630
  this._button.innerHTML = this._getStopIcon();
3529
- this._button.setAttribute("aria-label", "Stop");
3530
- this._button.setAttribute("title", "Stop");
3631
+ const label = resolveConfig(this).t("stopButton") || "Stop";
3632
+ this._button.setAttribute("aria-label", label);
3633
+ this._button.setAttribute("title", label);
3531
3634
  this._button.classList.add("is-streaming");
3532
3635
  } else {
3533
3636
  this._syncState();
@@ -3558,6 +3661,7 @@ class AparteComposerCancel extends HTMLElement {
3558
3661
  connectedCallback() {
3559
3662
  this._render();
3560
3663
  this._connectToRoot();
3664
+ this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));
3561
3665
  }
3562
3666
  disconnectedCallback() {
3563
3667
  this._button?.removeEventListener("click", this._onClick);
@@ -3595,6 +3699,20 @@ class AparteComposerCancel extends HTMLElement {
3595
3699
  e2.preventDefault();
3596
3700
  this._getRoot()?.cancel();
3597
3701
  }
3702
+ /**
3703
+ * Re-read the accessible name and the icon in place.
3704
+ *
3705
+ * `hidden` is NOT touched: `_render()` always renders this button hidden and only
3706
+ * the root's `streaming-change` listener ever un-hides it, so a rebuild would make
3707
+ * the stop button vanish in the middle of a turn.
3708
+ */
3709
+ _refreshChrome() {
3710
+ if (!this._button) return;
3711
+ const label = resolveConfig(this).t("stopButton") || "Stop";
3712
+ this._button.setAttribute("aria-label", label);
3713
+ this._button.setAttribute("title", label);
3714
+ this._button.innerHTML = this._getStopIcon();
3715
+ }
3598
3716
  _getStopIcon() {
3599
3717
  return resolveConfig(this).getIcon("stop");
3600
3718
  }
@@ -3700,6 +3818,7 @@ class AparteComposerAddAttachment extends HTMLElement {
3700
3818
  this._render();
3701
3819
  this._connectToRoot();
3702
3820
  this._setupDragDrop();
3821
+ this._unsubscribes.push(subscribeConfigChange(this, () => this._refreshChrome()));
3703
3822
  }
3704
3823
  disconnectedCallback() {
3705
3824
  this._button?.removeEventListener("click", this._onClick);
@@ -3716,6 +3835,24 @@ class AparteComposerAddAttachment extends HTMLElement {
3716
3835
  _getRoot() {
3717
3836
  return this.closest("aparte-composer");
3718
3837
  }
3838
+ /**
3839
+ * Re-read the label and the icon on the button that already exists.
3840
+ *
3841
+ * Deliberately not a re-render: `_render()` returns early once the button is
3842
+ * there, and its `disabled` computation ignores `root.streaming` while the
3843
+ * streaming listener sets `disabled` directly — so rebuilding would silently
3844
+ * re-enable the attach button mid-turn, and drop focus if the user were on it.
3845
+ * The native file input and the drag listeners live outside this element
3846
+ * (on `document.body` and on the composer root), so they are untouched either way.
3847
+ */
3848
+ _refreshChrome() {
3849
+ if (!this._button) return;
3850
+ const cfg = resolveConfig(this);
3851
+ const label = cfg.t("actionUpload") || "Attach file";
3852
+ this._button.setAttribute("aria-label", label);
3853
+ this._button.setAttribute("title", label);
3854
+ this._button.innerHTML = cfg.getIcon("paperclip") || this._defaultIcon();
3855
+ }
3719
3856
  _render() {
3720
3857
  if (this.querySelector(".aparte-caa-button")) return;
3721
3858
  const label = resolveConfig(this).t("actionUpload") || "Attach file";
@@ -3809,6 +3946,9 @@ class AparteComposerAction extends HTMLElement {
3809
3946
  connectedCallback() {
3810
3947
  this._render();
3811
3948
  this._connectToRoot();
3949
+ this._unsubscribes.push(subscribeConfigChange(this, () => {
3950
+ if (this._button) this._button.innerHTML = this._resolveIcon(this.getAttribute("icon") ?? "");
3951
+ }));
3812
3952
  }
3813
3953
  disconnectedCallback() {
3814
3954
  this._button?.removeEventListener("click", this._onClick);
@@ -4090,8 +4230,10 @@ class AparteElicitation extends HTMLElement {
4090
4230
  if (evtTargetId && own && evtTargetId !== own) return;
4091
4231
  this._cancelPending();
4092
4232
  };
4233
+ _unsubscribeConfig = null;
4093
4234
  connectedCallback() {
4094
4235
  this.style.display = "none";
4236
+ this._unsubscribeConfig = subscribeConfigChange(this, () => this._relabelPending());
4095
4237
  resolveConfig(this).setElicitationPresenter(this._present);
4096
4238
  window.addEventListener("aparte-message-aborted", this._onTurnEnd);
4097
4239
  window.addEventListener("aparte-message-error", this._onTurnEnd);
@@ -4117,8 +4259,24 @@ class AparteElicitation extends HTMLElement {
4117
4259
  if (cfg.getElicitationPresenter() === this._present) cfg.setElicitationPresenter(null);
4118
4260
  window.removeEventListener("aparte-message-aborted", this._onTurnEnd);
4119
4261
  window.removeEventListener("aparte-message-error", this._onTurnEnd);
4262
+ this._unsubscribeConfig?.();
4263
+ this._unsubscribeConfig = null;
4120
4264
  this._cancelPending();
4121
4265
  }
4266
+ /**
4267
+ * Re-apply the open question's strings, in place.
4268
+ *
4269
+ * Two owners, and both have to move or the panel goes bilingual: the panel's own
4270
+ * defaults (`relabel`), and the Skip button, which this file builds and this file
4271
+ * therefore has to re-text. The composer's one button is a third, and it already
4272
+ * follows — `aparte-composer-send` remembers the panel state it was given.
4273
+ */
4274
+ _relabelPending() {
4275
+ if (!this._pending) return;
4276
+ const cfg = resolveConfig(this);
4277
+ runWithConfig(cfg, () => this._pending.panel.relabel());
4278
+ this._pending.skip.textContent = cfg.t("elicitationSkip");
4279
+ }
4122
4280
  _present = (request) => {
4123
4281
  if (this._pending) return Promise.resolve({ action: "cancel" });
4124
4282
  const composer = this._getComposer();
@@ -4149,7 +4307,7 @@ class AparteElicitation extends HTMLElement {
4149
4307
  skip.textContent = cfg.t("elicitationSkip");
4150
4308
  skip.addEventListener("click", () => settle({ action: "decline" }));
4151
4309
  panel.dismiss.appendChild(skip);
4152
- this._pending = { settle, composer };
4310
+ this._pending = { settle, composer, panel, skip };
4153
4311
  composer.showPanel(panel.el, {
4154
4312
  submitEnabled: panel.canProceed(),
4155
4313
  mode: panel.mode(),
@@ -4233,21 +4391,22 @@ function registerAllComponents() {
4233
4391
  }
4234
4392
  }
4235
4393
  export {
4236
- A as APARTE_CONVERSATION_SCHEMA_VERSION,
4237
- a as APARTE_DEFAULT_BUBBLE_ACTIONS,
4238
- b as APARTE_DEFAULT_HOST_HANDLERS,
4239
- c as APARTE_DEFAULT_ICON_FALLBACKS,
4240
- d as APARTE_DEFAULT_LOCALE,
4241
- e as APARTE_DEFAULT_SKELETON_FALLBACKS,
4242
- f as APARTE_DEFAULT_UI_EVENTS,
4243
- g as APARTE_HOST_ATTR,
4244
- h as AparteBackendTransport,
4394
+ A as APARTE_CONFIG_CHANGE,
4395
+ a as APARTE_CONVERSATION_SCHEMA_VERSION,
4396
+ b as APARTE_DEFAULT_BUBBLE_ACTIONS,
4397
+ c as APARTE_DEFAULT_HOST_HANDLERS,
4398
+ d as APARTE_DEFAULT_ICON_FALLBACKS,
4399
+ e as APARTE_DEFAULT_LOCALE,
4400
+ f as APARTE_DEFAULT_SKELETON_FALLBACKS,
4401
+ g as APARTE_DEFAULT_UI_EVENTS,
4402
+ h as APARTE_HOST_ATTR,
4403
+ i as AparteBackendTransport,
4245
4404
  AparteChat,
4246
4405
  AparteChatBubble,
4247
- i as AparteChatHost,
4406
+ j as AparteChatHost,
4248
4407
  AparteChatStatus,
4249
4408
  AparteChatViewport,
4250
- j as AparteClient,
4409
+ k as AparteClient,
4251
4410
  AparteComposer,
4252
4411
  AparteComposerAction,
4253
4412
  AparteComposerAddAttachment,
@@ -4256,57 +4415,59 @@ export {
4256
4415
  AparteComposerInput,
4257
4416
  AparteComposerSend,
4258
4417
  AparteComposerToolbar,
4259
- k as AparteConfig,
4260
- l as AparteConversationController,
4418
+ l as AparteConfig,
4419
+ m as AparteConversationController,
4261
4420
  AparteConversationList,
4262
- m as AparteConversationManager,
4263
- n as AparteDirectTransport,
4421
+ n as AparteConversationManager,
4422
+ o as AparteDirectTransport,
4264
4423
  AparteElicitation,
4265
- o as AparteError,
4266
- p as AparteErrorCode,
4424
+ p as AparteError,
4425
+ q as AparteErrorCode,
4267
4426
  AparteMessageRepository,
4268
4427
  AparteOptgroup,
4269
4428
  AparteOption,
4270
4429
  AparteProgressSpinner,
4271
4430
  AparteSelect,
4272
- r as AparteStreamParser,
4273
- s as aparteGlobalConfig,
4274
- t as applyElementProps,
4275
- u as attachConfig,
4431
+ s as AparteStreamParser,
4432
+ t as aparteGlobalConfig,
4433
+ u as applyElementProps,
4434
+ v as attachConfig,
4276
4435
  buildElicitationPanel,
4277
- w as collectRendererStyles,
4278
- x as contentToText,
4279
- y as contextConfig,
4280
- z as createAparteChatHandler,
4281
- B as createStreamAdapter,
4436
+ x as collectRendererStyles,
4437
+ y as contentToText,
4438
+ z as contextConfig,
4439
+ B as createAparteChatHandler,
4440
+ C as createStreamAdapter,
4282
4441
  cssEscape,
4283
- D as declineDefaultRenderers,
4284
- E as defaultSanitizer,
4285
- F as deriveArtifactKind,
4286
- G as detachConfig,
4442
+ E as declineDefaultRenderers,
4443
+ F as defaultSanitizer,
4444
+ G as deriveArtifactKind,
4445
+ H as detachConfig,
4287
4446
  escapeAttr,
4288
4447
  escapeHtml,
4289
- J as filesToAttachments,
4290
- K as getAllRenderers,
4448
+ K as filesToAttachments,
4449
+ L as getAllRenderers,
4291
4450
  getSegmentRenderer,
4292
4451
  installDefaultRenderersOnce,
4293
4452
  isAwaitingReply,
4294
- O as isFormatAdapter,
4295
- P as isSafeUrl,
4296
- Q as isSegmentSettled,
4297
- R as parseAparteEventStream,
4298
- S as parseMarkdownToSegments,
4453
+ P as isFormatAdapter,
4454
+ Q as isSafeUrl,
4455
+ R as isSegmentSettled,
4456
+ S as parseAparteEventStream,
4457
+ T as parseMarkdownToSegments,
4299
4458
  populateBubbleFromMessage,
4300
- U as readableToAsyncIterable,
4459
+ V as readableToAsyncIterable,
4301
4460
  registerAllComponents,
4302
- V as registerDefaultRenderers,
4303
- W as registerSegmentRenderer,
4304
- X as requestUserInput,
4461
+ W as registerDefaultRenderers,
4462
+ X as registerSegmentRenderer,
4463
+ Y as requestUserInput,
4305
4464
  resolveConfig,
4306
4465
  revokeAttachmentUrls,
4307
4466
  runWithConfig,
4308
- $ as segmentDuration,
4309
- a0 as unregisterSegmentRenderer,
4467
+ a0 as segmentDuration,
4468
+ a1 as segmentTiming,
4469
+ subscribeConfigChange,
4470
+ a3 as unregisterSegmentRenderer,
4310
4471
  uuid
4311
4472
  };
4312
4473
  //# sourceMappingURL=index.js.map