@aparte/core 0.23.0 → 0.25.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 (85) hide show
  1. package/dist/client/aparte-client.d.ts +8 -0
  2. package/dist/client/aparte-client.d.ts.map +1 -1
  3. package/dist/client/stream-adapter.d.ts.map +1 -1
  4. package/dist/components/bubble/aparte-chat-bubble.d.ts +64 -0
  5. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
  6. package/dist/components/chat/aparte-chat.d.ts.map +1 -1
  7. package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
  8. package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
  9. package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
  10. package/dist/components/composer/aparte-composer-cancel.d.ts.map +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.map +1 -1
  13. package/dist/components/composer/aparte-composer-toolbar.d.ts.map +1 -1
  14. package/dist/components/composer/aparte-composer.d.ts.map +1 -1
  15. package/dist/components/context/aparte-context.d.ts.map +1 -1
  16. package/dist/components/conversation-list/aparte-conversation-list.d.ts +38 -0
  17. package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
  18. package/dist/components/conversation-list/index.d.ts +1 -1
  19. package/dist/components/conversation-list/index.d.ts.map +1 -1
  20. package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
  21. package/dist/components/index.d.ts +1 -1
  22. package/dist/components/index.d.ts.map +1 -1
  23. package/dist/components/scroll-rail/aparte-scroll-rail.d.ts.map +1 -1
  24. package/dist/components/sidebar/aparte-sidebar.d.ts.map +1 -1
  25. package/dist/components/split/aparte-split.d.ts.map +1 -1
  26. package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
  27. package/dist/components/suggestions/aparte-suggestions.d.ts.map +1 -1
  28. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  29. package/dist/config/aparte-config.d.ts +40 -1
  30. package/dist/config/aparte-config.d.ts.map +1 -1
  31. package/dist/config/locale.d.ts +4 -0
  32. package/dist/config/locale.d.ts.map +1 -1
  33. package/dist/config/tool-table.d.ts +2 -0
  34. package/dist/config/tool-table.d.ts.map +1 -1
  35. package/dist/conversations/conversation-controller.d.ts +19 -0
  36. package/dist/conversations/conversation-controller.d.ts.map +1 -1
  37. package/dist/conversations/conversation-status.d.ts +27 -0
  38. package/dist/conversations/conversation-status.d.ts.map +1 -0
  39. package/dist/custom-elements.json +11477 -10170
  40. package/dist/{dialog-triggers-3G9TqG9g.js → defer-hydration-BwMqeRHq.js} +816 -211
  41. package/dist/defer-hydration-BwMqeRHq.js.map +1 -0
  42. package/dist/{glyphs-C8z2oJUg.js → glyphs-2bym5jl5.js} +1 -1
  43. package/dist/{glyphs-C8z2oJUg.js.map → glyphs-2bym5jl5.js.map} +1 -1
  44. package/dist/host/aparte-chat-host.d.ts +22 -0
  45. package/dist/host/aparte-chat-host.d.ts.map +1 -1
  46. package/dist/icons.js +2 -2
  47. package/dist/index.css +1 -1
  48. package/dist/index.d.ts +5 -3
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +338 -58
  51. package/dist/index.js.map +1 -1
  52. package/dist/index.node.d.ts +4 -3
  53. package/dist/index.node.d.ts.map +1 -1
  54. package/dist/index.node.js +35 -29
  55. package/dist/index.node.js.map +1 -1
  56. package/dist/interop/defer-hydration.d.ts +34 -0
  57. package/dist/interop/defer-hydration.d.ts.map +1 -0
  58. package/dist/interop/element-props.d.ts +1 -1
  59. package/dist/interop/element-props.d.ts.map +1 -1
  60. package/dist/primitives/icon/aparte-icon.d.ts.map +1 -1
  61. package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -1
  62. package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
  63. package/dist/primitives/select/aparte-option.d.ts.map +1 -1
  64. package/dist/primitives/select/aparte-select.d.ts.map +1 -1
  65. package/dist/renderers/index.d.ts +2 -1
  66. package/dist/renderers/index.d.ts.map +1 -1
  67. package/dist/renderers/mounted-renderer.d.ts +34 -0
  68. package/dist/renderers/mounted-renderer.d.ts.map +1 -0
  69. package/dist/renderers/segment-renderers.d.ts +14 -1
  70. package/dist/renderers/segment-renderers.d.ts.map +1 -1
  71. package/dist/renderers/segments/thinking.d.ts.map +1 -1
  72. package/dist/renderers/segments/tool-call.d.ts +2 -1
  73. package/dist/renderers/segments/tool-call.d.ts.map +1 -1
  74. package/dist/types/event-map.d.ts +4 -2
  75. package/dist/types/event-map.d.ts.map +1 -1
  76. package/dist/types/events.d.ts +15 -0
  77. package/dist/types/events.d.ts.map +1 -1
  78. package/dist/types/index.d.ts +3 -3
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/segments.d.ts +21 -3
  81. package/dist/types/segments.d.ts.map +1 -1
  82. package/dist/types/tools.d.ts +95 -0
  83. package/dist/types/tools.d.ts.map +1 -1
  84. package/package.json +2 -2
  85. package/dist/dialog-triggers-3G9TqG9g.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { toolRefusal as toolRefusal$1, unansweredTail, runStreamAgent } from "@aparte/engine";
2
- import { A as APARTE_ICON_GLYPHS } from "./glyphs-C8z2oJUg.js";
2
+ import { A as APARTE_ICON_GLYPHS } from "./glyphs-2bym5jl5.js";
3
3
  function contentToText(content) {
4
4
  if (typeof content === "string") return content;
5
5
  return content.filter((p) => p.type === "text").map((p) => p.text).join("");
@@ -173,6 +173,8 @@ const APARTE_DEFAULT_LOCALE = {
173
173
  archiveConversation: "Archive",
174
174
  unarchiveConversation: "Unarchive",
175
175
  conversationActions: "Conversation actions",
176
+ conversationRunning: "Answering",
177
+ conversationWaiting: "Waiting for you",
176
178
  renameConversation: "Rename",
177
179
  conversationTitle: "Conversation title",
178
180
  pinConversation: "Pin",
@@ -948,13 +950,13 @@ function stampSegmentOnInsert(segments, segment, messageId, defaults) {
948
950
  ...timingPatch(base, { startedAt: segmentTiming(segment)?.startedAt ?? Date.now() })
949
951
  };
950
952
  }
951
- function adoptSegment(segments, segment, messageId, live = false) {
953
+ function adoptSegment(segments, segment, messageId, live2 = false) {
952
954
  return {
953
955
  ...segment,
954
956
  messageId,
955
957
  index: segments.length,
956
958
  isStreaming: false,
957
- ...segment.type === "tool_call" && (segment.status === "awaiting-approval" || segment.status === "pending" && !live) && !segment.deferred?.ticket ? { status: "aborted" } : {}
959
+ ...segment.type === "tool_call" && (segment.status === "awaiting-approval" || segment.status === "pending" && !live2) && !segment.deferred?.ticket ? { status: "aborted" } : {}
958
960
  };
959
961
  }
960
962
  const RESERVED = /* @__PURE__ */ new Set(["id", "type", "messageId", "index"]);
@@ -1028,11 +1030,11 @@ function openSegmentIds(segments) {
1028
1030
  }
1029
1031
  function adoptMessageSegments(message) {
1030
1032
  if (!message.segments?.length) return { ...message };
1031
- const live = message.status === "streaming" || message.status === "pending";
1033
+ const live2 = message.status === "streaming" || message.status === "pending";
1032
1034
  return {
1033
1035
  ...message,
1034
1036
  segments: message.segments.reduce((acc, segment) => {
1035
- acc.push(adoptSegment(acc, segment, message.id, live));
1037
+ acc.push(adoptSegment(acc, segment, message.id, live2));
1036
1038
  return acc;
1037
1039
  }, [])
1038
1040
  };
@@ -1068,8 +1070,8 @@ function detachConfig(el2) {
1068
1070
  if (previous) notifyConfigChanged(el2, resolveConfig(el2.parentElement), previous);
1069
1071
  }
1070
1072
  function resolveConfig(el2) {
1071
- const host = el2?.closest?.(`[${APARTE_HOST_ATTR}]`);
1072
- return host?.[CONFIG_SLOT] ?? aparteGlobalConfig;
1073
+ const host2 = el2?.closest?.(`[${APARTE_HOST_ATTR}]`);
1074
+ return host2?.[CONFIG_SLOT] ?? aparteGlobalConfig;
1073
1075
  }
1074
1076
  let _renderConfig = null;
1075
1077
  function runWithConfig(config, fn) {
@@ -1102,12 +1104,20 @@ function createStreamAdapter(opts) {
1102
1104
  };
1103
1105
  let thinkingContent = "";
1104
1106
  let thinkingCollapsed = false;
1107
+ const settleThinking = (fold) => {
1108
+ if (!thinkingId || thinkingCollapsed) return;
1109
+ const chatConfig = typeof Element !== "undefined" && target instanceof Element ? resolveConfig(target) : config;
1110
+ const keepOpen = !fold || chatConfig.getSegmentDefaults("thinking")?.collapsed === false;
1111
+ target.updateSegment?.(thinkingId, keepOpen ? { isStreaming: false } : { collapsed: true, isStreaming: false });
1112
+ thinkingCollapsed = true;
1113
+ };
1105
1114
  return (e) => {
1106
1115
  switch (e.type) {
1107
1116
  case "run-start":
1108
1117
  target.updateMessage?.(messageId, { status: "streaming" });
1109
1118
  break;
1110
1119
  case "turn-start":
1120
+ settleThinking(false);
1111
1121
  parser = new AparteStreamParser({ blocks: config.getStreamBlocks() });
1112
1122
  streaming = /* @__PURE__ */ new Set();
1113
1123
  thinkingId = null;
@@ -1120,7 +1130,8 @@ function createStreamAdapter(opts) {
1120
1130
  const seg = {
1121
1131
  id: `think-${uuid()}`,
1122
1132
  type: "thinking",
1123
- content: thinkingContent
1133
+ content: thinkingContent,
1134
+ isStreaming: true
1124
1135
  };
1125
1136
  thinkingId = seg.id;
1126
1137
  streaming.add(seg.id);
@@ -1131,12 +1142,7 @@ function createStreamAdapter(opts) {
1131
1142
  break;
1132
1143
  }
1133
1144
  case "text-delta": {
1134
- if (thinkingId && !thinkingCollapsed) {
1135
- const chatConfig = typeof Element !== "undefined" && target instanceof Element ? resolveConfig(target) : config;
1136
- const keepOpen = chatConfig.getSegmentDefaults("thinking")?.collapsed === false;
1137
- target.updateSegment?.(thinkingId, keepOpen ? { isStreaming: false } : { collapsed: true, isStreaming: false });
1138
- thinkingCollapsed = true;
1139
- }
1145
+ settleThinking(true);
1140
1146
  const result = parser.parse(e.delta);
1141
1147
  for (const segment of result.segments) {
1142
1148
  if (!streaming.has(segment.id)) {
@@ -1166,6 +1172,7 @@ function createStreamAdapter(opts) {
1166
1172
  break;
1167
1173
  }
1168
1174
  case "tool-start": {
1175
+ settleThinking(false);
1169
1176
  const toolSeg = {
1170
1177
  id: `tool-${e.toolCallId}`,
1171
1178
  type: "tool_call",
@@ -1890,6 +1897,12 @@ class ToolTable {
1890
1897
  }
1891
1898
  return this.any !== void 0 ? { value: this.any, level: "any" } : void 0;
1892
1899
  }
1900
+ /** Every value registered, whatever its matcher. */
1901
+ values() {
1902
+ const out = [...this.exact.values(), ...[...this.patterns.values()].map((p) => p.value)];
1903
+ if (this.any !== void 0) out.push(this.any);
1904
+ return out;
1905
+ }
1893
1906
  clear() {
1894
1907
  this.exact.clear();
1895
1908
  this.patterns.clear();
@@ -1915,6 +1928,34 @@ function renderingVersionOf(cfg) {
1915
1928
  function bumpRenderingVersion(cfg) {
1916
1929
  renderingVersions.set(cfg, renderingVersionOf(cfg) + 1);
1917
1930
  }
1931
+ const rendererListeners = /* @__PURE__ */ new Set();
1932
+ const pendingRendererChanges = /* @__PURE__ */ new Set();
1933
+ function onRenderersChange(listener) {
1934
+ rendererListeners.add(listener);
1935
+ return () => {
1936
+ rendererListeners.delete(listener);
1937
+ };
1938
+ }
1939
+ function notifyRenderersChanged(cfg) {
1940
+ if (pendingRendererChanges.size === 0) {
1941
+ queueMicrotask(() => {
1942
+ const changed = [...pendingRendererChanges];
1943
+ pendingRendererChanges.clear();
1944
+ for (const config of changed) {
1945
+ for (const listener of [...rendererListeners]) {
1946
+ try {
1947
+ listener(config);
1948
+ } catch (err) {
1949
+ console.error("[aparte] a renderer-change listener threw", err);
1950
+ }
1951
+ }
1952
+ }
1953
+ });
1954
+ }
1955
+ pendingRendererChanges.add(cfg);
1956
+ }
1957
+ const matcherKey = (match) => match instanceof RegExp ? `re:${String(match)}` : `name:${match}`;
1958
+ const ELEMENT_SHAPING = ["hidden", "open", "details", "quiet"];
1918
1959
  function drawingOf(provider, name) {
1919
1960
  if (!provider || !(name in provider) || name in Object.prototype) return void 0;
1920
1961
  const fn = provider[name];
@@ -2025,6 +2066,11 @@ class AparteConfig {
2025
2066
  _tools = /* @__PURE__ */ new Map();
2026
2067
  // By exact name, pattern or '*' — see `tool-table.ts` for the one resolution rule.
2027
2068
  _toolRenderers = new ToolTable();
2069
+ /** {@link overrideToolRenderer}'s stacks: what each matcher held below every override. */
2070
+ /** Per matcher: what it held below every override, then one entry per override — an object of its own, so two overrides of the same renderer are told apart. */
2071
+ _toolRendererOverrides = /* @__PURE__ */ new Map();
2072
+ /** Bumped by `reset()`: a restore function from before it must not undo what came after. */
2073
+ _overrideGeneration = 0;
2028
2074
  _toolDisplays = new ToolTable();
2029
2075
  /** Tagged blocks the stream parser recognises in the prose — see {@link registerStreamBlock}. */
2030
2076
  _streamBlocks = /* @__PURE__ */ new Map();
@@ -2866,12 +2912,64 @@ class AparteConfig {
2866
2912
  * // Custom pill for a web-search tool
2867
2913
  * aparteGlobalConfig.registerToolRenderer('web_search', { render: (seg) => `<div class="aparte-tool-label">Searching...</div>` });
2868
2914
  *
2915
+ * Calls already on screen follow: those this registration now draws are drawn again
2916
+ * by it (the renderer that drew them before gets its `cleanup`), and nothing else on
2917
+ * the page is touched.
2918
+ *
2869
2919
  * @example
2870
2920
  * // One look for every tool an MCP server brings
2871
2921
  * aparteGlobalConfig.registerToolRenderer(/^mcp__/, { render: (seg) => myMcpCard(seg) });
2872
2922
  */
2873
2923
  registerToolRenderer(match, renderer) {
2924
+ if (this._toolRenderers.get(match) === renderer) return;
2874
2925
  this._toolRenderers.set(match, renderer);
2926
+ notifyRenderersChanged(this);
2927
+ }
2928
+ /**
2929
+ * Put `renderer` over whatever this matcher holds, and get back the function that takes
2930
+ * it off again — the registration a component makes for as long as it is mounted.
2931
+ *
2932
+ * Overrides stack per matcher. The function restores what was there before THIS
2933
+ * override, and taking off an override that is no longer on top changes nothing on
2934
+ * screen: two components declaring the same tool, the newer wins, and unmounting the
2935
+ * older does not take the newer's away. When the last one goes, the matcher holds what
2936
+ * it held before the first — another registration, or nothing. Calls on screen follow
2937
+ * each change, as with {@link registerToolRenderer}. The returned function is safe to
2938
+ * call twice.
2939
+ *
2940
+ * @example
2941
+ * const restore = aparteGlobalConfig.overrideToolRenderer('get_weather', weatherCard);
2942
+ * // …later, when the component that declared it unmounts:
2943
+ * restore();
2944
+ */
2945
+ overrideToolRenderer(match, renderer) {
2946
+ const key = matcherKey(match);
2947
+ const current = this._toolRenderers.get(match);
2948
+ let stack = this._toolRendererOverrides.get(key);
2949
+ if (!stack) {
2950
+ stack = [{ renderer: current }];
2951
+ this._toolRendererOverrides.set(key, stack);
2952
+ } else if (stack[stack.length - 1].renderer !== current) {
2953
+ stack.splice(0, stack.length, { renderer: current });
2954
+ }
2955
+ const entry = { renderer };
2956
+ stack.push(entry);
2957
+ this.registerToolRenderer(match, renderer);
2958
+ const generation = this._overrideGeneration;
2959
+ let restored = false;
2960
+ return () => {
2961
+ if (restored || generation !== this._overrideGeneration) return;
2962
+ restored = true;
2963
+ const i = stack.indexOf(entry);
2964
+ if (i <= 0) return;
2965
+ const wasTop = i === stack.length - 1;
2966
+ stack.splice(i, 1);
2967
+ if (stack.length === 1 && this._toolRendererOverrides.get(key) === stack) this._toolRendererOverrides.delete(key);
2968
+ if (!wasTop || this._toolRenderers.get(match) !== renderer) return;
2969
+ const top = stack[stack.length - 1].renderer;
2970
+ if (top) this.registerToolRenderer(match, top);
2971
+ else this.unregisterToolRenderer(match);
2972
+ };
2875
2973
  }
2876
2974
  /**
2877
2975
  * Field defaults for a segment TYPE, filled in when a segment enters a message.
@@ -2911,9 +3009,11 @@ class AparteConfig {
2911
3009
  clearSegmentDefaults(type) {
2912
3010
  this._segmentDefaults.delete(type);
2913
3011
  }
2914
- /** Unregister the renderer registered under this matcher. */
3012
+ /** Unregister the renderer registered under this matcher. Calls on screen it drew go back to core's row. */
2915
3013
  unregisterToolRenderer(match) {
3014
+ if (this._toolRenderers.get(match) === void 0) return;
2916
3015
  this._toolRenderers.delete(match);
3016
+ notifyRenderersChanged(this);
2917
3017
  }
2918
3018
  /** The renderer that draws a tool named `toolName` — exact, pattern or `'*'` — or undefined. */
2919
3019
  getToolRenderer(toolName) {
@@ -2943,21 +3043,35 @@ class AparteConfig {
2943
3043
  * @example
2944
3044
  * // A consumer chat: the reply is what matters; the calls stay in the history.
2945
3045
  * aparteGlobalConfig.registerToolDisplay('*', { hidden: true });
3046
+ *
3047
+ * @example
3048
+ * // The line, and no raw JSON behind it: nothing, or a detail of the app's own.
3049
+ * aparteGlobalConfig.registerToolDisplay('read_file', { label: 'Reading a file', details: false });
3050
+ * aparteGlobalConfig.registerToolDisplay('web_search', {
3051
+ * label: 'Searched the web',
3052
+ * details: (segment) => sourcesList(segment.result),
3053
+ * });
2946
3054
  */
2947
3055
  registerToolDisplay(match, display) {
2948
3056
  const before = this._toolDisplays.get(match);
2949
3057
  const next = { ...before, ...display };
2950
3058
  this._toolDisplays.set(match, next);
2951
- if (before?.hidden !== next.hidden || before?.open !== next.open) bumpRenderingVersion(this);
3059
+ const shapes = ELEMENT_SHAPING.some((field) => before?.[field] !== next[field]) || before === void 0 && this._anotherShapes(next);
3060
+ if (shapes) bumpRenderingVersion(this);
2952
3061
  this._notify();
2953
3062
  }
2954
3063
  /** Forget the display registered under this matcher. */
2955
3064
  unregisterToolDisplay(match) {
2956
3065
  const before = this._toolDisplays.get(match);
2957
3066
  this._toolDisplays.delete(match);
2958
- if (before?.hidden !== void 0 || before?.open !== void 0) bumpRenderingVersion(this);
3067
+ const shapes = ELEMENT_SHAPING.some((field) => before?.[field] !== void 0) || before !== void 0 && this._anotherShapes(before);
3068
+ if (shapes) bumpRenderingVersion(this);
2959
3069
  this._notify();
2960
3070
  }
3071
+ /** Whether an entry other than `own` sets a field that shapes a row. */
3072
+ _anotherShapes(own) {
3073
+ return this._toolDisplays.values().some((entry) => entry !== own && ELEMENT_SHAPING.some((field) => entry[field] !== void 0));
3074
+ }
2961
3075
  /** How a tool named `toolName` is shown — the entry that applies to it — or undefined. */
2962
3076
  getToolDisplay(toolName) {
2963
3077
  return this._toolDisplays.resolve(toolName)?.value;
@@ -3250,6 +3364,9 @@ class AparteConfig {
3250
3364
  this._fetchedModels.clear();
3251
3365
  this._tools.clear();
3252
3366
  this._toolRenderers.clear();
3367
+ this._toolRendererOverrides.clear();
3368
+ this._overrideGeneration++;
3369
+ notifyRenderersChanged(this);
3253
3370
  this._toolDisplays.clear();
3254
3371
  this._streamBlocks.clear();
3255
3372
  this._modelConfig = {};
@@ -3301,19 +3418,19 @@ const ONE_SHOT_PROBE = "<i>a</i>";
3301
3418
  function hasOneShotMarkdownProvider() {
3302
3419
  return contextConfig().renderMarkdown(ONE_SHOT_PROBE) !== escapeHtml(ONE_SHOT_PROBE);
3303
3420
  }
3304
- function writeStreamedMarkdown(host, contentEl, content, streaming) {
3421
+ function writeStreamedMarkdown(host2, contentEl, content, streaming) {
3305
3422
  if (streaming) {
3306
- if (host._aparteSmd === void 0) {
3423
+ if (host2._aparteSmd === void 0) {
3307
3424
  settledFromParser.delete(contentEl);
3308
3425
  const renderer = contextConfig().createStreamingMarkdownRenderer(contentEl);
3309
3426
  if (renderer) {
3310
3427
  contentEl.textContent = "";
3311
- host._aparteSmd = { renderer, written: 0 };
3428
+ host2._aparteSmd = { renderer, written: 0 };
3312
3429
  } else {
3313
- host._aparteSmd = null;
3430
+ host2._aparteSmd = null;
3314
3431
  }
3315
3432
  }
3316
- const smd = host._aparteSmd;
3433
+ const smd = host2._aparteSmd;
3317
3434
  if (smd) {
3318
3435
  const delta = content.slice(smd.written);
3319
3436
  if (delta) {
@@ -3322,15 +3439,15 @@ function writeStreamedMarkdown(host, contentEl, content, streaming) {
3322
3439
  }
3323
3440
  return;
3324
3441
  }
3325
- } else if (host._aparteSmd) {
3326
- const smd = host._aparteSmd;
3442
+ } else if (host2._aparteSmd) {
3443
+ const smd = host2._aparteSmd;
3327
3444
  const tail = content.slice(smd.written);
3328
3445
  if (tail) {
3329
3446
  smd.renderer.write(tail);
3330
3447
  smd.written = content.length;
3331
3448
  }
3332
3449
  smd.renderer.end();
3333
- host._aparteSmd = void 0;
3450
+ host2._aparteSmd = void 0;
3334
3451
  if (!hasOneShotMarkdownProvider()) {
3335
3452
  contentEl.innerHTML = contextConfig().sanitizeHtml(contentEl.innerHTML);
3336
3453
  settledFromParser.set(contentEl, content);
@@ -3406,10 +3523,12 @@ function labelOf(segment) {
3406
3523
  if (ms === void 0) return cfg.t("thought");
3407
3524
  return cfg.t("thoughtFor").replace("{duration}", () => formatDuration(ms, cfg.getLocale().tag || void 0));
3408
3525
  }
3526
+ const arriving = (segment) => segment.isStreaming === true;
3409
3527
  function setLabel(el2, segment) {
3410
3528
  const text = labelOf(segment);
3411
3529
  const label = el2.querySelector(".aparte-thinking-label");
3412
3530
  if (label && label.textContent !== text) label.textContent = text;
3531
+ label?.classList.toggle("aparte-shimmer", arriving(segment));
3413
3532
  el2.querySelector(".aparte-thinking-content")?.setAttribute("aria-label", text);
3414
3533
  }
3415
3534
  const thinkingRenderer = {
@@ -3442,7 +3561,7 @@ const thinkingRenderer = {
3442
3561
  * only part that is genuinely about reasoning rather than about disclosure.
3443
3562
  * `.aparte-thinking-label` stays too: `relabel` below queries it.
3444
3563
  */
3445
- render: (segment) => `<details class="aparte-segment aparte-segment-thinking aparte-accordion__item" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed === false ? "open" : ""}><summary class="aparte-accordion__header aparte-thinking-header"><span class="aparte-thinking-label">${escapeHtml(labelOf(segment))}</span><span class="aparte-accordion__icon">${contextConfig().getIcon("expand")}</span></summary><div class="aparte-accordion__panel aparte-thinking-content" role="region" tabindex="0" aria-label="${escapeAttr(labelOf(segment))}">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
3564
+ render: (segment) => `<details class="aparte-segment aparte-segment-thinking aparte-accordion__item" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed === false ? "open" : ""}><summary class="aparte-accordion__header aparte-thinking-header"><span class="aparte-thinking-label${escapeAttr(arriving(segment) ? " aparte-shimmer" : "")}">${escapeHtml(labelOf(segment))}</span><span class="aparte-accordion__icon">${contextConfig().getIcon("expand")}</span></summary><div class="aparte-accordion__panel aparte-thinking-content" role="region" tabindex="0" aria-label="${escapeAttr(labelOf(segment))}">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
3446
3565
  /**
3447
3566
  * The default label is the only config-derived text here — and `segment.label`
3448
3567
  * still wins, exactly as in `render`, because that string is the app's.
@@ -3557,16 +3676,16 @@ function streamHighlight(element, paneSelector, content, lang, segId) {
3557
3676
  if (now - (_lastHighlightAt.get(segId) ?? 0) < HIGHLIGHT_DEBOUNCE_MS) return;
3558
3677
  markThrottle(_lastHighlightAt, segId, now);
3559
3678
  void contextConfig(element).highlightCode(content.slice(0, cut), lang).then((html) => {
3560
- const live = element.querySelector(paneSelector);
3561
- if (!live) return;
3562
- if (cut <= highlightedLen(live)) return;
3563
- live.innerHTML = html;
3564
- const codeEl = live.querySelector("code") ?? live;
3679
+ const live2 = element.querySelector(paneSelector);
3680
+ if (!live2) return;
3681
+ if (cut <= highlightedLen(live2)) return;
3682
+ live2.innerHTML = html;
3683
+ const codeEl = live2.querySelector("code") ?? live2;
3565
3684
  const span = document.createElement("span");
3566
3685
  span.dataset.aparteTail = "";
3567
3686
  span.textContent = content.slice(cut);
3568
3687
  codeEl.appendChild(span);
3569
- live.dataset.aparteHlLen = String(cut);
3688
+ live2.dataset.aparteHlLen = String(cut);
3570
3689
  }).catch(() => {
3571
3690
  });
3572
3691
  }
@@ -3742,6 +3861,72 @@ function outcomeOf(segment) {
3742
3861
  if (segment.status === "rejected" && segment.refusal) return null;
3743
3862
  return { key: "toolOutput", text };
3744
3863
  }
3864
+ function behindOf(segment) {
3865
+ const details = contextConfig().getToolDisplay(segment.toolCall?.name ?? "")?.details;
3866
+ if (details === false) return { kind: "none" };
3867
+ if (typeof details !== "function") return { kind: "parts" };
3868
+ try {
3869
+ const out = details(segment);
3870
+ if (!out) return { kind: "none" };
3871
+ return { kind: "body", body: typeof out === "string" ? contextConfig().sanitizeHtml(out) : out };
3872
+ } catch {
3873
+ return { kind: "none" };
3874
+ }
3875
+ }
3876
+ const appDetailOf = (element) => element.querySelector(':scope > .aparte-tool-detail[data-details="app"]');
3877
+ function setBody(element, body) {
3878
+ const detail = element.querySelector(".aparte-tool-detail");
3879
+ if (!detail) return;
3880
+ if (typeof body === "string") detail.innerHTML = body;
3881
+ else detail.replaceChildren(body);
3882
+ drawnBodies.set(detail, serialized(body));
3883
+ }
3884
+ const drawnBodies = /* @__PURE__ */ new WeakMap();
3885
+ const serialized = (body) => typeof body === "string" ? body : body.outerHTML;
3886
+ function relabelBody(detail, body) {
3887
+ if (typeof body !== "string" && detail.contains(body)) {
3888
+ drawnBodies.set(detail, body.outerHTML);
3889
+ return;
3890
+ }
3891
+ const before = drawnBodies.get(detail);
3892
+ const now = serialized(body);
3893
+ if (before === now) return;
3894
+ const next = document.createElement("div");
3895
+ if (typeof body === "string") next.innerHTML = body;
3896
+ else next.append(body);
3897
+ const prev = document.createElement("div");
3898
+ if (before !== void 0) prev.innerHTML = before;
3899
+ if (before !== void 0 && sameShape(prev, next) && sameShape(prev, detail)) patchChanges(detail, prev, next);
3900
+ else detail.replaceChildren(...Array.from(next.childNodes));
3901
+ drawnBodies.set(detail, now);
3902
+ }
3903
+ function sameShape(a, b) {
3904
+ if (a.childNodes.length !== b.childNodes.length) return false;
3905
+ for (let i = 0; i < a.childNodes.length; i++) {
3906
+ const x = a.childNodes[i];
3907
+ const y = b.childNodes[i];
3908
+ if (x.nodeType !== y.nodeType || x.nodeName !== y.nodeName || !sameShape(x, y)) return false;
3909
+ }
3910
+ return true;
3911
+ }
3912
+ function patchChanges(live2, prev, next) {
3913
+ for (let i = 0; i < live2.childNodes.length; i++) {
3914
+ const l = live2.childNodes[i];
3915
+ const p = prev.childNodes[i];
3916
+ const n = next.childNodes[i];
3917
+ if (l instanceof Element && p instanceof Element && n instanceof Element) {
3918
+ for (const { name, value } of Array.from(n.attributes)) {
3919
+ if (p.getAttribute(name) !== value) l.setAttribute(name, value);
3920
+ }
3921
+ for (const { name } of Array.from(p.attributes)) {
3922
+ if (!n.hasAttribute(name)) l.removeAttribute(name);
3923
+ }
3924
+ patchChanges(l, p, n);
3925
+ } else if (p.nodeValue !== n.nodeValue) {
3926
+ l.nodeValue = n.nodeValue;
3927
+ }
3928
+ }
3929
+ }
3745
3930
  function setNote(element, segment) {
3746
3931
  const el2 = element.querySelector(".aparte-tool-note");
3747
3932
  if (!el2) return;
@@ -3848,6 +4033,23 @@ function iconOf(segment) {
3848
4033
  const key = cfg.getToolDisplay(segment.toolCall?.name ?? "")?.icon;
3849
4034
  return (key ? cfg.getIcon(key) : void 0) || cfg.getIcon("tool");
3850
4035
  }
4036
+ const isQuiet = (segment) => contextConfig().getToolDisplay(segment.toolCall?.name ?? "")?.quiet === true;
4037
+ function quietOf(segment) {
4038
+ const status = segment.status ?? "pending";
4039
+ const quiet = isQuiet(segment);
4040
+ return {
4041
+ shimmer: quiet && status === "pending",
4042
+ spinner: !quiet && status === "pending",
4043
+ stateHidden: quiet && (status === "pending" || status === "resolved")
4044
+ };
4045
+ }
4046
+ function setQuiet(element, segment) {
4047
+ const quiet = quietOf(segment);
4048
+ element.toggleAttribute("data-quiet", isQuiet(segment));
4049
+ element.querySelector(".aparte-tool-spinner")?.toggleAttribute("hidden", !quiet.spinner);
4050
+ element.querySelector(".aparte-tool-name")?.classList.toggle("aparte-shimmer", quiet.shimmer);
4051
+ element.querySelector(".aparte-tool-state")?.classList.toggle("aparte-sr-only", quiet.stateHidden);
4052
+ }
3851
4053
  function setName(element, segment) {
3852
4054
  const el2 = element.querySelector(".aparte-tool-name");
3853
4055
  if (!el2) return;
@@ -3855,68 +4057,100 @@ function setName(element, segment) {
3855
4057
  if (el2.textContent !== text) el2.textContent = text;
3856
4058
  el2.classList.toggle("aparte-tool-name--label", isLabel);
3857
4059
  }
3858
- function pillMarkup(segment, status) {
4060
+ function pillMarkup(segment) {
3859
4061
  const badge = stateBadge(segment);
3860
4062
  const icon = iconOf(segment);
3861
4063
  const target = toolTarget(segment);
3862
4064
  const note = noteOf(segment);
3863
4065
  const name = nameOf(segment);
4066
+ const quiet = quietOf(segment);
3864
4067
  return `
3865
4068
  <span class="aparte-tool-label">
3866
4069
  <span class="aparte-tool-icon">${icon}</span>
3867
- <span class="aparte-tool-name${name.isLabel ? " aparte-tool-name--label" : ""}">${escapeHtml(name.text)}</span>
4070
+ <span class="aparte-tool-name${name.isLabel ? " aparte-tool-name--label" : ""}${quiet.shimmer ? " aparte-shimmer" : ""}">${escapeHtml(name.text)}</span>
3868
4071
  <span class="aparte-tool-target"${target ? "" : " hidden"}>${escapeHtml(target)}</span>
3869
4072
  </span>
3870
- <span class="aparte-spinner aparte-tool-spinner" aria-hidden="true"${status === "pending" ? "" : " hidden"}></span>
3871
- <span class="aparte-tool-state">${badge}</span>
4073
+ <span class="aparte-spinner aparte-tool-spinner" aria-hidden="true"${quiet.spinner ? "" : " hidden"}></span>
4074
+ <span class="aparte-tool-state${quiet.stateHidden ? " aparte-sr-only" : ""}">${badge}</span>
3872
4075
  <span class="aparte-tool-note"${note ? ` title="${escapeAttr(note)}"` : " hidden"}>${escapeHtml(note)}</span>`;
3873
4076
  }
3874
4077
  function placeholderMarkup(segment) {
3875
4078
  return `<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(segment.status ?? "pending")}" data-tool-call-id="${escapeAttr(segment.toolCall?.id ?? "")}" data-tool-name="${escapeAttr(segment.toolCall?.name ?? "tool")}" hidden></div>`;
3876
4079
  }
3877
4080
  const isPlaceholder = (element) => element.hidden && element.classList.contains("aparte-segment-tool-call") && element.childElementCount === 0;
4081
+ const drawnBy = /* @__PURE__ */ new WeakMap();
4082
+ const behind = /* @__PURE__ */ new WeakMap();
4083
+ function toolRendererThatDrew(element) {
4084
+ return behind.get(element) ?? null;
4085
+ }
4086
+ function drawerOf(element, current) {
4087
+ const recorded = drawnBy.get(element);
4088
+ if (recorded) return recorded;
4089
+ return element.classList.contains("aparte-segment-tool-call") ? null : current;
4090
+ }
4091
+ function drawDefault(segment, behind2) {
4092
+ const name = segment.toolCall?.name ?? "tool";
4093
+ const status = segment.status ?? "pending";
4094
+ const toolCallId = segment.toolCall?.id ?? "";
4095
+ const pill = pillMarkup(segment);
4096
+ const quiet = isQuiet(segment) ? " data-quiet" : "";
4097
+ const toggle = contextConfig().getIcon("expand");
4098
+ const input = behind2.kind === "parts" ? describeInput(segment) : "";
4099
+ const outcome = behind2.kind === "parts" ? outcomeOf(segment) : null;
4100
+ if (behind2.kind === "none" || behind2.kind === "parts" && !input && !outcome) {
4101
+ return `
4102
+ <div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${quiet}><div class="aparte-tool-line">${pill}</div>
4103
+ </div>
4104
+ `;
4105
+ }
4106
+ const cfg = contextConfig();
4107
+ const copy = copyButtonMarkup("aparte-tool-copy");
4108
+ const open = segment.collapsed !== void 0 ? !segment.collapsed : cfg.getToolDisplay(name)?.open === true;
4109
+ if (behind2.kind === "body") {
4110
+ const tpl = document.createElement("template");
4111
+ tpl.innerHTML = `
4112
+ <details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${quiet}${open ? " open" : ""}>
4113
+ <summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
4114
+ <div class="aparte-tool-detail" data-details="app"></div>
4115
+ </details>`.trim();
4116
+ const row = tpl.content.firstElementChild;
4117
+ setBody(row, behind2.body);
4118
+ return row;
4119
+ }
4120
+ return `
4121
+ <details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${quiet}${open ? " open" : ""}>
4122
+ <summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
4123
+ <div class="aparte-tool-detail">
4124
+ ${input ? `
4125
+ <div class="aparte-tool-part" data-part="input">
4126
+ <div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolInput"))}</span>${copy}</div>
4127
+ <div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t("toolInput"))}"><pre><code>${escapeHtml(input)}</code></pre></div>
4128
+ </div>` : ""}
4129
+ ${outcome ? `
4130
+ <div class="aparte-tool-part" data-part="output">
4131
+ <div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t(outcome.key))}</span>${copy}</div>
4132
+ <div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t(outcome.key))}"><pre><code>${escapeHtml(outcome.text)}</code></pre></div>
4133
+ </div>` : ""}
4134
+ </div>
4135
+ </details>
4136
+ `;
4137
+ }
3878
4138
  const toolCallRenderer = {
3879
4139
  type: "tool_call",
3880
4140
  render: (segment) => {
3881
4141
  const name = segment.toolCall?.name ?? "tool";
3882
- const status = segment.status ?? "pending";
3883
- const toolCallId = segment.toolCall?.id ?? "";
3884
4142
  if (contextConfig().isToolHidden(name)) return placeholderMarkup(segment);
3885
4143
  const customRenderer = contextConfig().getToolRenderer(name);
3886
4144
  if (customRenderer) {
3887
4145
  injectToolRendererStyles(name, customRenderer);
3888
- return customRenderer.render(segment) || placeholderMarkup(segment);
3889
- }
3890
- const pill = pillMarkup(segment, status);
3891
- const toggle = contextConfig().getIcon("expand");
3892
- const input = describeInput(segment);
3893
- const outcome = outcomeOf(segment);
3894
- if (!input && !outcome) {
3895
- return `
3896
- <div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}">${pill}
3897
- </div>
3898
- `;
4146
+ const out = customRenderer.render(segment);
4147
+ if (out instanceof HTMLElement) {
4148
+ drawnBy.set(out, customRenderer);
4149
+ behind.set(out, customRenderer);
4150
+ }
4151
+ return out || placeholderMarkup(segment);
3899
4152
  }
3900
- const cfg = contextConfig();
3901
- const copy = copyButtonMarkup("aparte-tool-copy");
3902
- const open = segment.collapsed !== void 0 ? !segment.collapsed : cfg.getToolDisplay(name)?.open === true;
3903
- return `
3904
- <details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${open ? " open" : ""}>
3905
- <summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
3906
- <div class="aparte-tool-detail">
3907
- ${input ? `
3908
- <div class="aparte-tool-part" data-part="input">
3909
- <div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolInput"))}</span>${copy}</div>
3910
- <div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t("toolInput"))}"><pre><code>${escapeHtml(input)}</code></pre></div>
3911
- </div>` : ""}
3912
- ${outcome ? `
3913
- <div class="aparte-tool-part" data-part="output">
3914
- <div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t(outcome.key))}</span>${copy}</div>
3915
- <div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t(outcome.key))}"><pre><code>${escapeHtml(outcome.text)}</code></pre></div>
3916
- </div>` : ""}
3917
- </div>
3918
- </details>
3919
- `;
4153
+ return drawDefault(segment, behindOf(segment));
3920
4154
  },
3921
4155
  /**
3922
4156
  * The pill's glyphs, and the label it wears while a person is deciding.
@@ -3931,7 +4165,7 @@ const toolCallRenderer = {
3931
4165
  relabel: (element, segment) => {
3932
4166
  if (isPlaceholder(element)) return;
3933
4167
  const cfg = contextConfig();
3934
- const custom = cfg.getToolRenderer(segment.toolCall?.name);
4168
+ const custom = drawerOf(element, cfg.getToolRenderer(segment.toolCall?.name) ?? null);
3935
4169
  if (custom) {
3936
4170
  custom.relabel?.(element, segment);
3937
4171
  return;
@@ -3949,8 +4183,14 @@ const toolCallRenderer = {
3949
4183
  if (outputLabel && outcome) outputLabel.textContent = cfg.t(outcome.key);
3950
4184
  if (outcome) element.querySelector('[data-part="output"] .aparte-tool-part-body')?.setAttribute("aria-label", cfg.t(outcome.key));
3951
4185
  for (const button of element.querySelectorAll(".aparte-tool-copy")) relabelCopyButton(button);
4186
+ const appDetail = appDetailOf(element);
4187
+ if (appDetail) {
4188
+ const behind2 = behindOf(segment);
4189
+ if (behind2.kind === "body") relabelBody(appDetail, behind2.body);
4190
+ }
3952
4191
  const statusEl = element.querySelector(".aparte-tool-state");
3953
4192
  if (statusEl) statusEl.innerHTML = stateBadge(segment);
4193
+ setQuiet(element, segment);
3954
4194
  },
3955
4195
  /**
3956
4196
  * Patch in place. NEVER re-render, and never touch `open`.
@@ -3972,43 +4212,77 @@ const toolCallRenderer = {
3972
4212
  element.setAttribute("data-status", status);
3973
4213
  return;
3974
4214
  }
3975
- const input = describeInput(segment);
3976
- const outcome = outcomeOf(segment);
3977
- const wantsDetail = !!input || !!outcome;
3978
- const custom = contextConfig().getToolRenderer(segment.toolCall?.name);
3979
- if (custom?.update && !hidden && !isPlaceholder(element)) {
3980
- custom.update(element, segment);
4215
+ const custom = hidden ? null : contextConfig().getToolRenderer(segment.toolCall?.name) ?? null;
4216
+ const drawn = isPlaceholder(element) ? null : drawerOf(element, custom);
4217
+ if (drawn && drawn === custom && drawn.update && !isPlaceholder(element)) {
4218
+ drawn.update(element, segment);
3981
4219
  return;
3982
4220
  }
3983
- if (custom || hidden || isPlaceholder(element) || wantsDetail !== (element.tagName === "DETAILS")) {
3984
- const out = toolCallRenderer.render(segment);
4221
+ const rebuild = (out) => {
3985
4222
  const tpl = document.createElement("template");
3986
4223
  tpl.innerHTML = typeof out === "string" ? out.trim() : "";
3987
4224
  const rebuilt = typeof out === "string" ? tpl.content.firstElementChild : out;
3988
4225
  if (rebuilt) {
4226
+ if (!rebuilt.hasAttribute("data-segment-id")) rebuilt.setAttribute("data-segment-id", segment.id);
4227
+ toolCallRenderer.cleanup?.(element, segment);
3989
4228
  element.replaceWith(rebuilt);
3990
4229
  toolCallRenderer.setup?.(rebuilt, segment);
3991
4230
  }
4231
+ };
4232
+ if (custom || drawn || hidden || isPlaceholder(element)) {
4233
+ rebuild(toolCallRenderer.render(segment));
4234
+ return;
4235
+ }
4236
+ const behind2 = behindOf(segment);
4237
+ const input = behind2.kind === "parts" ? describeInput(segment) : "";
4238
+ const outcome = behind2.kind === "parts" ? outcomeOf(segment) : null;
4239
+ const wantsDetail = behind2.kind === "body" || !!input || !!outcome;
4240
+ const holdsAppDetail = !!appDetailOf(element);
4241
+ if (wantsDetail !== (element.tagName === "DETAILS") || wantsDetail && behind2.kind === "body" !== holdsAppDetail) {
4242
+ rebuild(drawDefault(segment, behind2));
3992
4243
  return;
3993
4244
  }
3994
4245
  element.setAttribute("data-status", status);
3995
4246
  const badge = element.querySelector(".aparte-tool-state");
3996
4247
  if (badge) badge.innerHTML = stateBadge(segment);
3997
- element.querySelector(".aparte-tool-spinner")?.toggleAttribute("hidden", status !== "pending");
4248
+ setQuiet(element, segment);
3998
4249
  setName(element, segment);
3999
4250
  setTarget(element, segment);
4000
4251
  setNote(element, segment);
4252
+ if (behind2.kind === "body") {
4253
+ setBody(element, behind2.body);
4254
+ return;
4255
+ }
4001
4256
  setPart(element, "input", input, "toolInput");
4002
4257
  setPart(element, "output", outcome?.text ?? "", outcome?.key ?? "toolOutput");
4003
4258
  highlightParts(element);
4004
4259
  },
4005
4260
  setup: (element, segment) => {
4261
+ const hidden = contextConfig().isToolHidden(segment.toolCall?.name ?? "tool");
4262
+ const customRenderer = hidden ? void 0 : contextConfig().getToolRenderer(segment.toolCall?.name);
4263
+ if (customRenderer) behind.set(element, customRenderer);
4006
4264
  if (isPlaceholder(element)) return;
4007
4265
  wireParts(element);
4008
4266
  highlightParts(element);
4009
- const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);
4267
+ if (customRenderer) drawnBy.set(element, customRenderer);
4010
4268
  customRenderer?.setup?.(element, segment);
4011
4269
  },
4270
+ /**
4271
+ * The row leaves for good: the registered renderer that drew it, if any, cleans up. A
4272
+ * cleanup that throws is reported and goes no further: the row is replaced or removed
4273
+ * all the same, and the stream that caused it is not interrupted.
4274
+ */
4275
+ cleanup: (element, segment) => {
4276
+ behind.delete(element);
4277
+ const custom = drawnBy.get(element);
4278
+ if (!custom) return;
4279
+ drawnBy.delete(element);
4280
+ try {
4281
+ custom.cleanup?.(element, segment);
4282
+ } catch (err) {
4283
+ console.error("[aparte] a tool renderer's cleanup threw", err);
4284
+ }
4285
+ },
4012
4286
  // CSS lives in styles/segment/ — see that folder for why a
4013
4287
  // built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
4014
4288
  // has no other way onto the page.
@@ -4032,8 +4306,51 @@ function effectiveRenderers(config) {
4032
4306
  }
4033
4307
  let styleElement = null;
4034
4308
  function registerSegmentRenderer(renderer, config = contextConfig()) {
4035
- registryFor(config).renderers.set(renderer.type, renderer);
4309
+ const reg = registryFor(config);
4310
+ if (reg.renderers.get(renderer.type) === renderer) return;
4311
+ reg.renderers.set(renderer.type, renderer);
4036
4312
  injectRendererStyles(config);
4313
+ notifyRenderersChanged(config);
4314
+ }
4315
+ const overrideStacks = /* @__PURE__ */ new WeakMap();
4316
+ function overrideSegmentRenderer(renderer, config = contextConfig()) {
4317
+ const type = renderer.type;
4318
+ const reg = registryFor(config);
4319
+ let byType = overrideStacks.get(config);
4320
+ if (!byType) {
4321
+ byType = /* @__PURE__ */ new Map();
4322
+ overrideStacks.set(config, byType);
4323
+ }
4324
+ const current = reg.renderers.get(type);
4325
+ let stack = byType.get(type);
4326
+ if (!stack) {
4327
+ stack = [{ renderer: current }];
4328
+ byType.set(type, stack);
4329
+ } else if (stack[stack.length - 1].renderer !== current) stack.splice(0, stack.length, { renderer: current });
4330
+ const entry = { renderer };
4331
+ stack.push(entry);
4332
+ registerSegmentRenderer(renderer, config);
4333
+ let restored = false;
4334
+ return () => {
4335
+ if (restored) return;
4336
+ restored = true;
4337
+ const i = stack.indexOf(entry);
4338
+ if (i <= 0) return;
4339
+ const wasTop = i === stack.length - 1;
4340
+ stack.splice(i, 1);
4341
+ if (stack.length === 1 && byType.get(type) === stack) byType.delete(type);
4342
+ if (!wasTop || reg.renderers.get(type) !== renderer) return;
4343
+ const top = stack[stack.length - 1].renderer;
4344
+ if (top) {
4345
+ registerSegmentRenderer(top, config);
4346
+ return;
4347
+ }
4348
+ unregisterSegmentRenderer(type, config);
4349
+ if (!effectiveRenderers(config).has(type) && reg.defaultsInstalled && !reg.defaultsDeclined) {
4350
+ const builtIn = DEFAULT_RENDERERS.find((r) => r.type === type);
4351
+ if (builtIn) registerSegmentRenderer(builtIn, config);
4352
+ }
4353
+ };
4037
4354
  }
4038
4355
  function declineDefaultRenderers(config = contextConfig()) {
4039
4356
  registryFor(config).defaultsDeclined = true;
@@ -4048,7 +4365,10 @@ function installDefaultRenderersOnce(config = contextConfig()) {
4048
4365
  injectRendererStyles(config);
4049
4366
  }
4050
4367
  function unregisterSegmentRenderer(type, config = contextConfig()) {
4051
- registryFor(config).renderers.delete(type);
4368
+ const reg = registryFor(config);
4369
+ if (!reg.renderers.has(type)) return;
4370
+ reg.renderers.delete(type);
4371
+ notifyRenderersChanged(config);
4052
4372
  }
4053
4373
  function getSegmentRenderer(type, config = contextConfig()) {
4054
4374
  return effectiveRenderers(config).get(type);
@@ -4108,6 +4428,71 @@ const DEFAULT_RENDERERS = [
4108
4428
  errorRenderer,
4109
4429
  toolCallRenderer
4110
4430
  ];
4431
+ const mountedRenderers = /* @__PURE__ */ new WeakSet();
4432
+ function isMountedRenderer(renderer) {
4433
+ return !!renderer && mountedRenderers.has(renderer);
4434
+ }
4435
+ function toToolCallView(segment) {
4436
+ return {
4437
+ id: segment.toolCall?.id ?? segment.id,
4438
+ name: segment.toolCall?.name ?? "tool",
4439
+ input: segment.toolCall?.input,
4440
+ status: segment.status ?? "pending",
4441
+ result: segment.result,
4442
+ structuredResult: segment.structuredResult,
4443
+ deferred: segment.deferred,
4444
+ refusal: segment.refusal,
4445
+ abortCause: segment.abortCause,
4446
+ segment
4447
+ };
4448
+ }
4449
+ function host(className, segmentId) {
4450
+ const el2 = document.createElement("div");
4451
+ el2.className = className;
4452
+ el2.setAttribute("data-segment-id", segmentId);
4453
+ return el2;
4454
+ }
4455
+ function defineMountedToolRenderer(mount) {
4456
+ const mounted = /* @__PURE__ */ new WeakMap();
4457
+ const renderer = {
4458
+ render: (segment) => host("aparte-tool-ui", segment.id),
4459
+ setup: (el2, segment) => {
4460
+ if (!mounted.has(el2)) mounted.set(el2, mount(el2, toToolCallView(segment)));
4461
+ },
4462
+ update: (el2, segment) => {
4463
+ mounted.get(el2)?.update(toToolCallView(segment));
4464
+ },
4465
+ cleanup: (el2) => {
4466
+ const view = mounted.get(el2);
4467
+ if (!view) return;
4468
+ mounted.delete(el2);
4469
+ view.destroy();
4470
+ }
4471
+ };
4472
+ mountedRenderers.add(renderer);
4473
+ return renderer;
4474
+ }
4475
+ function defineMountedSegmentRenderer(type, mount) {
4476
+ const mounted = /* @__PURE__ */ new WeakMap();
4477
+ const renderer = {
4478
+ type,
4479
+ render: (segment) => host("aparte-segment-ui", segment.id),
4480
+ setup: (el2, segment) => {
4481
+ if (!mounted.has(el2)) mounted.set(el2, mount(el2, segment));
4482
+ },
4483
+ update: (el2, segment) => {
4484
+ mounted.get(el2)?.update(segment);
4485
+ },
4486
+ cleanup: (el2) => {
4487
+ const view = mounted.get(el2);
4488
+ if (!view) return;
4489
+ mounted.delete(el2);
4490
+ view.destroy();
4491
+ }
4492
+ };
4493
+ mountedRenderers.add(renderer);
4494
+ return renderer;
4495
+ }
4111
4496
  function populateBubbleFromMessage(bubble, message, siblingInfo) {
4112
4497
  if (message.segments && message.segments.length > 0) {
4113
4498
  const existing = bubble.getSegments?.() ?? [];
@@ -4607,6 +4992,21 @@ function revokeAttachmentUrls(attachments) {
4607
4992
  if (attachment.url?.startsWith("blob:")) URL.revokeObjectURL(attachment.url);
4608
4993
  }
4609
4994
  }
4995
+ const known = /* @__PURE__ */ new Map();
4996
+ function rememberConversationStatus(id, status) {
4997
+ if (status) known.set(id, status);
4998
+ else known.delete(id);
4999
+ }
5000
+ function tellConversationStatus(id, status) {
5001
+ rememberConversationStatus(id, status);
5002
+ try {
5003
+ window.dispatchEvent(new CustomEvent("aparte-conversation-status", { detail: { id, status } }));
5004
+ } catch {
5005
+ }
5006
+ }
5007
+ function conversationStatusOf(id) {
5008
+ return known.get(id) ?? null;
5009
+ }
4610
5010
  const MAX_COMPACTION_SAVE_WAITS = 6;
4611
5011
  class AparteConversationController {
4612
5012
  _binding;
@@ -4615,6 +5015,10 @@ class AparteConversationController {
4615
5015
  _isStreaming = false;
4616
5016
  _isLoadingConversation = false;
4617
5017
  _ensureInFlight = null;
5018
+ /** The conversation the turn in flight started in, and what the lists were told of it. */
5019
+ _turn = null;
5020
+ /** The reply the turn in flight writes into, from its `aparte-message-start`. */
5021
+ _turnMessage = null;
4618
5022
  _onSendCapture = null;
4619
5023
  _onPathChanged = null;
4620
5024
  _onMessageStart = null;
@@ -4622,6 +5026,7 @@ class AparteConversationController {
4622
5026
  _onMessageError = null;
4623
5027
  _onMessageAborted = null;
4624
5028
  _onMessagePaused = null;
5029
+ _onMessageResumed = null;
4625
5030
  _onSelectConversation = null;
4626
5031
  _onCompactDone = null;
4627
5032
  _unsubscribeManager = null;
@@ -4702,11 +5107,38 @@ class AparteConversationController {
4702
5107
  "[aparte] This chat resolves its own config, but the conversation manager was registered on aparteGlobalConfig — so nothing is being persisted. Pass the same config to init(): `init(adapter, myConfig)`, using the config you gave the chat."
4703
5108
  );
4704
5109
  }
5110
+ /**
5111
+ * Tell every conversation list on the page what the turn is doing, through
5112
+ * `aparte-conversation-status` on `window` — the mirror of the list's
5113
+ * `aparte-conversation-select`, which this controller hears. Always about the
5114
+ * conversation the turn started in: the person may have opened another since. A turn
5115
+ * that starts before its conversation exists (a first message) is told once it does.
5116
+ */
5117
+ _setTurn(status) {
5118
+ if (!status) {
5119
+ if (this._turn) tellConversationStatus(this._turn.id, null);
5120
+ this._turn = null;
5121
+ return;
5122
+ }
5123
+ const id = this._turn?.id ?? (status === "running" ? this._activeId ?? this._manager()?.activeId ?? null : null);
5124
+ if (!id) return;
5125
+ this._turn = { id, status };
5126
+ tellConversationStatus(id, status);
5127
+ }
5128
+ /**
5129
+ * Whether a lifecycle event is about the turn in flight. A turn a newer send replaced
5130
+ * (a chip, a retry, an edit) says it stopped AFTER the new one started: its end must not
5131
+ * end the new turn's state. An event without a message id is taken as this turn's.
5132
+ */
5133
+ _ofThisTurn(e) {
5134
+ const id = e.detail?.messageId;
5135
+ return !id || !this._turnMessage || id === this._turnMessage;
5136
+ }
4705
5137
  /**
4706
5138
  * Attach event listeners to the binding host. Returns an unbind function.
4707
5139
  */
4708
5140
  bind() {
4709
- const host = this._binding.host;
5141
+ const host2 = this._binding.host;
4710
5142
  this._onSendCapture = (e) => {
4711
5143
  const evt = e;
4712
5144
  const targetId = evt.detail?.targetId;
@@ -4725,36 +5157,49 @@ class AparteConversationController {
4725
5157
  evt.detail.echoed = true;
4726
5158
  void this._ensureConversationAndPersist(userMsg);
4727
5159
  };
4728
- host.addEventListener("aparte-send", this._onSendCapture, { capture: true });
5160
+ host2.addEventListener("aparte-send", this._onSendCapture, { capture: true });
4729
5161
  this._onPathChanged = () => {
4730
5162
  if (!this._isLoadingConversation) {
4731
5163
  this._persistActive();
4732
5164
  }
4733
5165
  };
4734
- host.addEventListener("aparte-path-changed", this._onPathChanged);
4735
- this._onMessageStart = () => {
5166
+ host2.addEventListener("aparte-path-changed", this._onPathChanged);
5167
+ this._onMessageStart = (e) => {
4736
5168
  this._isStreaming = true;
5169
+ this._turnMessage = e.detail?.messageId ?? null;
5170
+ this._setTurn("running");
4737
5171
  };
4738
- this._onMessageDone = () => {
5172
+ const ended = (e) => {
5173
+ if (!this._ofThisTurn(e)) return;
4739
5174
  this._isStreaming = false;
5175
+ this._setTurn(null);
5176
+ this._turnMessage = null;
5177
+ };
5178
+ this._onMessageDone = (e) => {
5179
+ ended(e);
4740
5180
  queueMicrotask(() => this._persistActive());
4741
5181
  };
4742
- this._onMessageError = () => {
4743
- this._isStreaming = false;
5182
+ this._onMessageError = (e) => {
5183
+ ended(e);
4744
5184
  queueMicrotask(() => this._persistActive());
4745
5185
  };
4746
- this._onMessageAborted = () => {
4747
- this._isStreaming = false;
5186
+ this._onMessageAborted = (e) => {
5187
+ ended(e);
4748
5188
  queueMicrotask(() => this._persistActive());
4749
5189
  };
4750
- this._onMessagePaused = () => {
5190
+ this._onMessagePaused = (e) => {
5191
+ if (this._ofThisTurn(e)) this._setTurn("waiting");
4751
5192
  queueMicrotask(() => this._persistActive());
4752
5193
  };
4753
- host.addEventListener("aparte-message-start", this._onMessageStart);
4754
- host.addEventListener("aparte-message-done", this._onMessageDone);
4755
- host.addEventListener("aparte-message-error", this._onMessageError);
4756
- host.addEventListener("aparte-message-aborted", this._onMessageAborted);
4757
- host.addEventListener("aparte-message-paused", this._onMessagePaused);
5194
+ this._onMessageResumed = (e) => {
5195
+ if (this._ofThisTurn(e)) this._setTurn("running");
5196
+ };
5197
+ host2.addEventListener("aparte-message-start", this._onMessageStart);
5198
+ host2.addEventListener("aparte-message-done", this._onMessageDone);
5199
+ host2.addEventListener("aparte-message-error", this._onMessageError);
5200
+ host2.addEventListener("aparte-message-aborted", this._onMessageAborted);
5201
+ host2.addEventListener("aparte-message-paused", this._onMessagePaused);
5202
+ host2.addEventListener("aparte-message-resumed", this._onMessageResumed);
4758
5203
  this._onSelectConversation = (e) => {
4759
5204
  const evt = e;
4760
5205
  const targetId = evt.detail?.targetId;
@@ -4781,37 +5226,44 @@ class AparteConversationController {
4781
5226
  this._bound = false;
4782
5227
  this._subscribedTo = null;
4783
5228
  this._persistActive();
5229
+ if (this._turn?.status === "running") this._setTurn(null);
5230
+ this._turn = null;
5231
+ this._turnMessage = null;
4784
5232
  this._stopHydrationRetry?.();
4785
5233
  this._stopHydrationRetry = null;
4786
- const host = this._binding.host;
5234
+ const host2 = this._binding.host;
4787
5235
  if (this._onSendCapture) {
4788
- host.removeEventListener("aparte-send", this._onSendCapture, { capture: true });
5236
+ host2.removeEventListener("aparte-send", this._onSendCapture, { capture: true });
4789
5237
  this._onSendCapture = null;
4790
5238
  }
4791
5239
  if (this._onPathChanged) {
4792
- host.removeEventListener("aparte-path-changed", this._onPathChanged);
5240
+ host2.removeEventListener("aparte-path-changed", this._onPathChanged);
4793
5241
  this._onPathChanged = null;
4794
5242
  }
4795
5243
  if (this._onMessageStart) {
4796
- host.removeEventListener("aparte-message-start", this._onMessageStart);
5244
+ host2.removeEventListener("aparte-message-start", this._onMessageStart);
4797
5245
  this._onMessageStart = null;
4798
5246
  }
4799
5247
  if (this._onMessageDone) {
4800
- host.removeEventListener("aparte-message-done", this._onMessageDone);
5248
+ host2.removeEventListener("aparte-message-done", this._onMessageDone);
4801
5249
  this._onMessageDone = null;
4802
5250
  }
4803
5251
  if (this._onMessageError) {
4804
- host.removeEventListener("aparte-message-error", this._onMessageError);
5252
+ host2.removeEventListener("aparte-message-error", this._onMessageError);
4805
5253
  this._onMessageError = null;
4806
5254
  }
4807
5255
  if (this._onMessageAborted) {
4808
- host.removeEventListener("aparte-message-aborted", this._onMessageAborted);
5256
+ host2.removeEventListener("aparte-message-aborted", this._onMessageAborted);
4809
5257
  this._onMessageAborted = null;
4810
5258
  }
4811
5259
  if (this._onMessagePaused) {
4812
- host.removeEventListener("aparte-message-paused", this._onMessagePaused);
5260
+ host2.removeEventListener("aparte-message-paused", this._onMessagePaused);
4813
5261
  this._onMessagePaused = null;
4814
5262
  }
5263
+ if (this._onMessageResumed) {
5264
+ host2.removeEventListener("aparte-message-resumed", this._onMessageResumed);
5265
+ this._onMessageResumed = null;
5266
+ }
4815
5267
  if (this._onCompactDone && typeof window !== "undefined") {
4816
5268
  window.removeEventListener("aparte-compact-done", this._onCompactDone);
4817
5269
  this._onCompactDone = null;
@@ -4840,6 +5292,8 @@ class AparteConversationController {
4840
5292
  const isSwitch = id !== this._activeId;
4841
5293
  const manager = this._manager();
4842
5294
  if (isSwitch && this._isStreaming) {
5295
+ if (this._turn?.status === "waiting") this._turn = null;
5296
+ else this._setTurn(null);
4843
5297
  this._persistActive();
4844
5298
  try {
4845
5299
  window.dispatchEvent(new CustomEvent("aparte-abort", {
@@ -5021,6 +5475,7 @@ class AparteConversationController {
5021
5475
  }
5022
5476
  this._activeId = convId;
5023
5477
  this._options.onConversationCreated?.(convId);
5478
+ if (this._isStreaming && !this._turn) this._setTurn("running");
5024
5479
  } else if (this._activeId !== null && this._activeId !== convId) {
5025
5480
  await manager.addMessage(convId, userMsg).catch(() => {
5026
5481
  });
@@ -5083,6 +5538,12 @@ class AparteChatHost {
5083
5538
  _tokenBuffer = null;
5084
5539
  /** Handle of the scheduled coalesced sync, or null when none is pending. */
5085
5540
  _flushHandle = null;
5541
+ /**
5542
+ * Lists reported through `onMessagesChange` that the parent has not handed back yet, oldest
5543
+ * first — what {@link takeEcho} recognises. Bounded: a parent that never hands a list back
5544
+ * (an uncontrolled chat) must not make it grow for the life of the page.
5545
+ */
5546
+ _pendingEchoes = [];
5086
5547
  _conversationId = null;
5087
5548
  _controller;
5088
5549
  _teardown = [];
@@ -5092,6 +5553,34 @@ class AparteChatHost {
5092
5553
  this.options = options;
5093
5554
  this._conversationId = options.conversationId ?? null;
5094
5555
  }
5556
+ /** Every report goes through here, so {@link takeEcho} knows what the parent may hand back. */
5557
+ _report(next) {
5558
+ this._pendingEchoes.push(next);
5559
+ if (this._pendingEchoes.length > 64) this._pendingEchoes.shift();
5560
+ this.binding.onMessagesChange?.(next);
5561
+ }
5562
+ /**
5563
+ * Whether a list the parent hands back is this host's own report coming back — the echo of
5564
+ * `onMessagesChange` through a two-way binding (`bind:messages`, `v-model:messages`,
5565
+ * `[(messages)]`, a controlled React prop). A wrapper asks before applying the list it is
5566
+ * given, because applying an echo rolls back what the host did since without reporting (an
5567
+ * optimistic append).
5568
+ *
5569
+ * True for a list reported and not handed back yet, however late it lands — a parent whose
5570
+ * render comes after the host's next write hands back an EARLIER report — and that list and
5571
+ * every older one are taken. False for any other list, the parent's own word, which clears
5572
+ * them: a list the parent restores later (a reopened conversation, an undo) is applied,
5573
+ * where remembering every list ever reported swallowed it.
5574
+ */
5575
+ takeEcho(list) {
5576
+ const i = this._pendingEchoes.indexOf(list);
5577
+ if (i < 0) {
5578
+ this._pendingEchoes.length = 0;
5579
+ return false;
5580
+ }
5581
+ this._pendingEchoes.splice(0, i + 1);
5582
+ return true;
5583
+ }
5095
5584
  // ── lifecycle ──────────────────────────────────────────────────────────
5096
5585
  /**
5097
5586
  * Install the host-method surface + event listeners and create the
@@ -5101,19 +5590,19 @@ class AparteChatHost {
5101
5590
  bind() {
5102
5591
  if (this._bound) return () => this.unbind();
5103
5592
  this._bound = true;
5104
- const host = this.binding.host;
5105
- if (!host.id) host.id = this.binding.hostId;
5593
+ const host2 = this.binding.host;
5594
+ if (!host2.id) host2.id = this.binding.hostId;
5106
5595
  if (this.options.config) attachConfig(this.binding.host, this.options.config);
5107
5596
  const vp = this._vp();
5108
5597
  vp?.setFrameworkManagedDOM?.(true);
5109
5598
  if (this.options.layoutTransitionMs && this.options.layoutTransitionMs > 0) {
5110
5599
  vp?.configure?.({ layoutTransitionMs: this.options.layoutTransitionMs });
5111
5600
  }
5112
- this._installHostMethods(host);
5113
- this._installLifecycleListeners(host);
5114
- this._teardown.push(gateGlide(host));
5601
+ this._installHostMethods(host2);
5602
+ this._installLifecycleListeners(host2);
5603
+ this._teardown.push(gateGlide(host2));
5115
5604
  this._installViewportListeners();
5116
- this._createController(host);
5605
+ this._createController(host2);
5117
5606
  return () => this.unbind();
5118
5607
  }
5119
5608
  unbind() {
@@ -5159,10 +5648,10 @@ class AparteChatHost {
5159
5648
  return this._controller?.setConversationId(id) ?? Promise.resolve();
5160
5649
  }
5161
5650
  _unbindController;
5162
- _createController(host) {
5651
+ _createController(host2) {
5163
5652
  const convBinding = {
5164
5653
  hostId: this.binding.hostId,
5165
- host,
5654
+ host: host2,
5166
5655
  // Controller-driven flushes mirror AparteConversationManager state, not
5167
5656
  // user intent — deliberately NOT emitted as `messagesChange` to
5168
5657
  // avoid a feedback loop with hosts using a two-way `messages` input.
@@ -5269,7 +5758,7 @@ class AparteChatHost {
5269
5758
  const merged = { ...next[index], ...updates };
5270
5759
  next[index] = merged;
5271
5760
  this.binding.setMessages(next);
5272
- this.binding.onMessagesChange?.(next);
5761
+ this._report(next);
5273
5762
  if (updates.status) {
5274
5763
  this._bubbleById(messageId)?.updateMessage?.({ status: updates.status });
5275
5764
  }
@@ -5289,7 +5778,7 @@ class AparteChatHost {
5289
5778
  const newContent = options?.append ? (last.content || "") + content : content;
5290
5779
  const next = [...msgs.slice(0, -1), { ...last, content: newContent }];
5291
5780
  this.binding.setMessages(next);
5292
- this.binding.onMessagesChange?.(next);
5781
+ this._report(next);
5293
5782
  }
5294
5783
  /** Add a segment to the last message (immediate bubble + state sync). */
5295
5784
  addSegment(segment) {
@@ -5314,7 +5803,7 @@ class AparteChatHost {
5314
5803
  const segments = [...last.segments || [], stamped];
5315
5804
  const next = [...msgs.slice(0, -1), { ...last, segments }];
5316
5805
  this.binding.setMessages(next);
5317
- this.binding.onMessagesChange?.(next);
5806
+ this._report(next);
5318
5807
  }
5319
5808
  /** Update a segment in the last message in place. */
5320
5809
  updateSegment(segmentId, updates) {
@@ -5331,7 +5820,7 @@ class AparteChatHost {
5331
5820
  );
5332
5821
  const next = [...msgs.slice(0, -1), { ...last, segments }];
5333
5822
  this.binding.setMessages(next);
5334
- this.binding.onMessagesChange?.(next);
5823
+ this._report(next);
5335
5824
  }
5336
5825
  /** Remove a transient segment (a status row the host added itself). */
5337
5826
  removeSegment(segmentId) {
@@ -5345,7 +5834,7 @@ class AparteChatHost {
5345
5834
  renumberSegments(segments);
5346
5835
  const next = [...msgs.slice(0, -1), { ...last, segments }];
5347
5836
  this.binding.setMessages(next);
5348
- this.binding.onMessagesChange?.(next);
5837
+ this._report(next);
5349
5838
  }
5350
5839
  /**
5351
5840
  * Append text to a segment's content in the last message — the streaming path
@@ -5401,8 +5890,8 @@ class AparteChatHost {
5401
5890
  this._flushHandle = null;
5402
5891
  }
5403
5892
  const tokens = this._tokenBuffer;
5404
- const pending = this._segmentBuffer.size > 0 ? new Map(this._segmentBuffer) : null;
5405
- if (!tokens && !pending) return;
5893
+ const pending2 = this._segmentBuffer.size > 0 ? new Map(this._segmentBuffer) : null;
5894
+ if (!tokens && !pending2) return;
5406
5895
  this._tokenBuffer = null;
5407
5896
  this._segmentBuffer.clear();
5408
5897
  let msgs = this.binding.getMessages();
@@ -5416,11 +5905,11 @@ class AparteChatHost {
5416
5905
  changed = true;
5417
5906
  }
5418
5907
  }
5419
- if (pending) {
5908
+ if (pending2) {
5420
5909
  const last = msgs[msgs.length - 1];
5421
5910
  if (last?.segments) {
5422
5911
  const segments = last.segments.map((s) => {
5423
- const buffered = pending.get(s.id);
5912
+ const buffered = pending2.get(s.id);
5424
5913
  if (buffered === void 0 || !("content" in s)) return s;
5425
5914
  return {
5426
5915
  ...s,
@@ -5434,7 +5923,7 @@ class AparteChatHost {
5434
5923
  }
5435
5924
  if (!changed) return;
5436
5925
  this.binding.setMessages(msgs);
5437
- this.binding.onMessagesChange?.(msgs);
5926
+ this._report(msgs);
5438
5927
  }
5439
5928
  /** Read the current message list. */
5440
5929
  getMessages() {
@@ -5467,7 +5956,7 @@ class AparteChatHost {
5467
5956
  const idx = msgs.findIndex((m) => m.id === messageId);
5468
5957
  const next = idx >= 0 ? msgs.slice(0, idx) : msgs;
5469
5958
  this.binding.setMessages(next);
5470
- this.binding.onMessagesChange?.(next);
5959
+ this._report(next);
5471
5960
  }
5472
5961
  /** Keep up to and including a user message, drop later responses (retry). */
5473
5962
  truncateResponsesAfter(userMessageId) {
@@ -5476,7 +5965,7 @@ class AparteChatHost {
5476
5965
  const idx = msgs.findIndex((m) => m.id === userMessageId);
5477
5966
  const next = idx >= 0 ? msgs.slice(0, idx + 1) : msgs;
5478
5967
  this.binding.setMessages(next);
5479
- this.binding.onMessagesChange?.(next);
5968
+ this._report(next);
5480
5969
  }
5481
5970
  // ── manual token streaming (agnostic core of injectTokenStream) ─────────
5482
5971
  /**
@@ -5504,14 +5993,14 @@ class AparteChatHost {
5504
5993
  }
5505
5994
  ac.signal.addEventListener("abort", () => resolve("aborted"), { once: true });
5506
5995
  });
5507
- const { host, hostId } = this.binding;
5996
+ const { host: host2, hostId } = this.binding;
5508
5997
  const onAbort = (e) => {
5509
5998
  const target = e.detail?.targetId;
5510
5999
  if ((!target || target === hostId) && this._streamAbort === ac) this.stopTokenStream();
5511
6000
  };
5512
6001
  window.addEventListener("aparte-abort", onAbort);
5513
6002
  const replaced = () => this._streamAbort !== void 0 && this._streamAbort !== ac;
5514
- dispatchLifecycleEvent(host, "aparte-message-start", { messageId, role: "assistant" }, hostId);
6003
+ dispatchLifecycleEvent(host2, "aparte-message-start", { messageId, role: "assistant" }, hostId);
5515
6004
  try {
5516
6005
  for (; ; ) {
5517
6006
  const res = await Promise.race([iterator.next(), aborted]);
@@ -5524,7 +6013,7 @@ class AparteChatHost {
5524
6013
  if (!replaced()) {
5525
6014
  this._setStreamingId(null);
5526
6015
  dispatchLifecycleEvent(
5527
- host,
6016
+ host2,
5528
6017
  ac.signal.aborted ? "aparte-message-aborted" : "aparte-message-done",
5529
6018
  ac.signal.aborted ? { messageId } : { messageId, role: "assistant" },
5530
6019
  hostId
@@ -5534,7 +6023,7 @@ class AparteChatHost {
5534
6023
  this._flushStreamState();
5535
6024
  if (!replaced()) {
5536
6025
  this._setStreamingId(null);
5537
- dispatchLifecycleEvent(host, "aparte-message-error", { messageId, error: err }, hostId);
6026
+ dispatchLifecycleEvent(host2, "aparte-message-error", { messageId, error: err }, hostId);
5538
6027
  }
5539
6028
  throw err;
5540
6029
  } finally {
@@ -5692,22 +6181,22 @@ class AparteChatHost {
5692
6181
  const all = vp.querySelectorAll("aparte-chat-bubble");
5693
6182
  return all.length ? all[all.length - 1] : null;
5694
6183
  }
5695
- _installHostMethods(host) {
5696
- host["appendMessage"] = (m) => this.appendMessage(m);
5697
- host["updateMessage"] = (id, u) => this.updateMessage(id, u);
5698
- host["updateLastMessage"] = (c, o) => this.updateLastMessage(c, o);
5699
- host["addSegment"] = (s) => this.addSegment(s);
5700
- host["updateSegment"] = (id, u) => this.updateSegment(id, u);
5701
- host["removeSegment"] = (id) => this.removeSegment(id);
5702
- host["appendToSegment"] = (id, c) => this.appendToSegment(id, c);
5703
- host["getMessages"] = () => this.getMessages();
5704
- host["addBranch"] = (id) => this.addBranch(id);
5705
- host["addSiblingOf"] = (id, m) => this.addSiblingOf(id, m);
5706
- host["truncateFrom"] = (id) => this.truncateFrom(id);
5707
- host["truncateResponsesAfter"] = (id) => this.truncateResponsesAfter(id);
5708
- host["clearAll"] = (options) => this.clearMessages(options);
5709
- }
5710
- _installLifecycleListeners(host) {
6184
+ _installHostMethods(host2) {
6185
+ host2["appendMessage"] = (m) => this.appendMessage(m);
6186
+ host2["updateMessage"] = (id, u) => this.updateMessage(id, u);
6187
+ host2["updateLastMessage"] = (c, o) => this.updateLastMessage(c, o);
6188
+ host2["addSegment"] = (s) => this.addSegment(s);
6189
+ host2["updateSegment"] = (id, u) => this.updateSegment(id, u);
6190
+ host2["removeSegment"] = (id) => this.removeSegment(id);
6191
+ host2["appendToSegment"] = (id, c) => this.appendToSegment(id, c);
6192
+ host2["getMessages"] = () => this.getMessages();
6193
+ host2["addBranch"] = (id) => this.addBranch(id);
6194
+ host2["addSiblingOf"] = (id, m) => this.addSiblingOf(id, m);
6195
+ host2["truncateFrom"] = (id) => this.truncateFrom(id);
6196
+ host2["truncateResponsesAfter"] = (id) => this.truncateResponsesAfter(id);
6197
+ host2["clearAll"] = (options) => this.clearMessages(options);
6198
+ }
6199
+ _installLifecycleListeners(host2) {
5711
6200
  const onStart = (e) => {
5712
6201
  const d = e?.detail;
5713
6202
  if (d?.messageId) this._setStreamingId(d.messageId);
@@ -5727,7 +6216,7 @@ class AparteChatHost {
5727
6216
  (m) => m.id === d.messageId ? { ...m, usage: d.usage } : m
5728
6217
  );
5729
6218
  this.binding.setMessages(next);
5730
- this.binding.onMessagesChange?.(next);
6219
+ this._report(next);
5731
6220
  this._bubbleById(d.messageId)?.setUsage?.(d.usage);
5732
6221
  };
5733
6222
  const onEnd = (e) => {
@@ -5735,15 +6224,15 @@ class AparteChatHost {
5735
6224
  this.binding.onTypingChange?.(false);
5736
6225
  this._setStreamingId(null);
5737
6226
  };
5738
- host.addEventListener("aparte-message-start", onStart);
5739
- host.addEventListener("aparte-message-done", onDone);
5740
- host.addEventListener("aparte-message-error", onEnd);
5741
- host.addEventListener("aparte-message-aborted", onEnd);
6227
+ host2.addEventListener("aparte-message-start", onStart);
6228
+ host2.addEventListener("aparte-message-done", onDone);
6229
+ host2.addEventListener("aparte-message-error", onEnd);
6230
+ host2.addEventListener("aparte-message-aborted", onEnd);
5742
6231
  this._teardown.push(() => {
5743
- host.removeEventListener("aparte-message-start", onStart);
5744
- host.removeEventListener("aparte-message-done", onDone);
5745
- host.removeEventListener("aparte-message-error", onEnd);
5746
- host.removeEventListener("aparte-message-aborted", onEnd);
6232
+ host2.removeEventListener("aparte-message-start", onStart);
6233
+ host2.removeEventListener("aparte-message-done", onDone);
6234
+ host2.removeEventListener("aparte-message-error", onEnd);
6235
+ host2.removeEventListener("aparte-message-aborted", onEnd);
5747
6236
  });
5748
6237
  }
5749
6238
  _installViewportListeners() {
@@ -5760,7 +6249,7 @@ class AparteChatHost {
5760
6249
  return old?.usage !== void 0 ? { ...m, usage: old.usage } : m;
5761
6250
  });
5762
6251
  this.binding.setMessages(merged);
5763
- this.binding.onMessagesChange?.(merged);
6252
+ this._report(merged);
5764
6253
  if (evt.detail.siblings.some((s) => s.count > 1)) {
5765
6254
  this._pendingSiblings = evt.detail.siblings;
5766
6255
  this.binding.afterRender(() => this._applyPendingSiblings());
@@ -6381,6 +6870,9 @@ function remoteProvider(id) {
6381
6870
  }
6382
6871
  class AparteClient {
6383
6872
  _boundHandler = null;
6873
+ /** Between `start()` and `stop()`: the listeners are on `window`. The send handler alone is
6874
+ * not the sign — the constructor prepares it without listening. */
6875
+ _listening = false;
6384
6876
  _boundAbortHandler = null;
6385
6877
  _boundLoadedHandler = null;
6386
6878
  _boundRetryHandler = null;
@@ -6515,11 +7007,12 @@ class AparteClient {
6515
7007
  if (!this._boundLoadedHandler) {
6516
7008
  this._boundLoadedHandler = (e) => {
6517
7009
  if (!this._isForThisInstance(e)) return;
6518
- const host = e.target instanceof HTMLElement ? e.target : void 0;
6519
- void this.resumeDeferred(host ?? e.detail?.targetId);
7010
+ const host2 = e.target instanceof HTMLElement ? e.target : void 0;
7011
+ void this.resumeDeferred(host2 ?? e.detail?.targetId);
6520
7012
  };
6521
7013
  }
6522
7014
  window.addEventListener("aparte-conversation-loaded", this._boundLoadedHandler);
7015
+ this._listening = true;
6523
7016
  }
6524
7017
  /**
6525
7018
  * Stop answering: abort the reply in flight, take this client's `keyResolver` off the
@@ -6529,7 +7022,8 @@ class AparteClient {
6529
7022
  this.abort();
6530
7023
  this._releaseKeyResolver?.();
6531
7024
  this._releaseKeyResolver = null;
6532
- if (!this._boundHandler) return;
7025
+ if (!this._listening || !this._boundHandler) return;
7026
+ this._listening = false;
6533
7027
  window.removeEventListener("aparte-send", this._boundHandler);
6534
7028
  this._boundHandler = null;
6535
7029
  if (this._boundAbortHandler) {
@@ -6685,6 +7179,7 @@ class AparteClient {
6685
7179
  return;
6686
7180
  }
6687
7181
  dispatchLifecycleEvent(targetElement, "aparte-message-start", { messageId, role: "assistant" }, chatId);
7182
+ if (opts?.resume) dispatchLifecycleEvent(targetElement, "aparte-message-paused", { messageId }, chatId);
6688
7183
  if (!opts?.resume && provider.id) {
6689
7184
  this._updateMessage(targetElement, messageId, {
6690
7185
  model: {
@@ -6697,6 +7192,7 @@ class AparteClient {
6697
7192
  const streamController = new AbortController();
6698
7193
  if (this._streamController !== streamController) this._streamController?.abort();
6699
7194
  this._streamController = streamController;
7195
+ this._turnsInFlight.add(messageId);
6700
7196
  try {
6701
7197
  let result = opts?.resume ? { usage: void 0, suspended: opts.resume } : await this._streamLoop(targetElement, messageId, provider, baseRequest, authConfig, streamController);
6702
7198
  const maxResumes = this.options.maxTurns ?? 10;
@@ -6728,6 +7224,7 @@ class AparteClient {
6728
7224
  };
6729
7225
  const messages2 = this._resumeMessages(targetElement, messageId, baseRequest.messages);
6730
7226
  this._spendPause(targetElement, messageId);
7227
+ dispatchLifecycleEvent(targetElement, "aparte-message-resumed", { messageId }, chatId);
6731
7228
  result = await this._streamLoop(
6732
7229
  targetElement,
6733
7230
  messageId,
@@ -6754,8 +7251,15 @@ class AparteClient {
6754
7251
  }
6755
7252
  const aparteError = AparteError.from(error, AparteErrorCode.UNKNOWN_ERROR);
6756
7253
  this._handleLifecycleError(targetElement, messageId, aparteError);
7254
+ } finally {
7255
+ this._turnsInFlight.delete(messageId);
6757
7256
  }
6758
7257
  }
7258
+ /**
7259
+ * Replies a turn is running into right now, a pause it waits on included: a second
7260
+ * load of their conversation must not start them again (`resumeDeferred`).
7261
+ */
7262
+ _turnsInFlight = /* @__PURE__ */ new Set();
6759
7263
  /**
6760
7264
  * Handle aparte-retry — add a sibling branch to the assistant message and re-stream
6761
7265
  * using the same conversation history minus the retried reply.
@@ -7452,15 +7956,15 @@ ${content}
7452
7956
  * nobody answered — the turn was stopped, or the channel gave up — and there is no
7453
7957
  * resume. A channel that CRASHES fails the turn, as it does for a run in the page.
7454
7958
  */
7455
- async _answerDeferred(targetElement, pending, signal, rowEvents, approvedBefore = /* @__PURE__ */ new Set(), approvedNow = /* @__PURE__ */ new Set()) {
7959
+ async _answerDeferred(targetElement, pending2, signal, rowEvents, approvedBefore = /* @__PURE__ */ new Set(), approvedNow = /* @__PURE__ */ new Set()) {
7456
7960
  const outcomes = {};
7457
7961
  const resolver = this._approvalResolverFor(targetElement, true);
7458
7962
  const refuseTheRest = (after) => {
7459
- for (const rest of pending.slice(after + 1)) {
7963
+ for (const rest of pending2.slice(after + 1)) {
7460
7964
  outcomes[rest.toolCallId] = { kind: "decision", approved: false, reason: NOT_RUN_AFTER_A_REFUSAL, by: "earlier-refusal" };
7461
7965
  }
7462
7966
  };
7463
- for (const [index, p] of pending.entries()) {
7967
+ for (const [index, p] of pending2.entries()) {
7464
7968
  if (signal.aborted) break;
7465
7969
  const call = { id: p.toolCallId, name: p.name, input: p.input ?? {} };
7466
7970
  if (p.reason === "approval") {
@@ -7620,14 +8124,14 @@ ${content}
7620
8124
  * @param target The chat — an element, or its id. Defaults to the one `targetResolver` names, else the first on the page.
7621
8125
  */
7622
8126
  async resumeDeferred(target) {
7623
- const host = typeof target === "string" ? this._byId(target) : target ?? this.options.targetResolver?.() ?? null;
7624
- const targetElement = host ? this._asRenderTarget(host) : this._scanForTarget();
8127
+ const host2 = typeof target === "string" ? this._byId(target) : target ?? this.options.targetResolver?.() ?? null;
8128
+ const targetElement = host2 ? this._asRenderTarget(host2) : this._scanForTarget();
7625
8129
  if (!targetElement) return false;
7626
8130
  const messages = targetElement.getMessages?.() ?? [];
7627
8131
  const last = [...messages].reverse().find((m) => m.role === "assistant");
7628
8132
  const open = (last?.segments ?? []).filter(isOpenDeferred);
7629
8133
  const ticket = open.find((s) => s.deferred?.ticket)?.deferred?.ticket;
7630
- if (!last || open.length === 0 || !ticket || this._resuming.has(last.id)) return false;
8134
+ if (!last || open.length === 0 || !ticket || this._resuming.has(last.id) || this._turnsInFlight.has(last.id)) return false;
7631
8135
  const config = this._config.getModelConfig();
7632
8136
  const recorded = last.model && this._config.getAIProvider(last.model.providerId) ? last.model : void 0;
7633
8137
  const providerId = recorded?.providerId ?? config.defaultProvider;
@@ -7640,7 +8144,7 @@ ${content}
7640
8144
  this._resuming.add(last.id);
7641
8145
  try {
7642
8146
  const authConfig = provider.id ? await this._resolveAuth(provider.id) : void 0;
7643
- const pending = open.map((s) => ({
8147
+ const pending2 = open.map((s) => ({
7644
8148
  toolCallId: s.toolCall.id,
7645
8149
  name: s.toolCall.name,
7646
8150
  input: s.toolCall.input,
@@ -7648,7 +8152,7 @@ ${content}
7648
8152
  }));
7649
8153
  const base = this._resumeBase(messages, messages.indexOf(last));
7650
8154
  await this._streamTurn(targetElement, last.id, provider, base, recorded?.modelId ?? config.defaultModel ?? "", authConfig, {
7651
- resume: { pending, ticket },
8155
+ resume: { pending: pending2, ticket },
7652
8156
  ...recorded?.temperature !== void 0 ? { temperature: recorded.temperature } : {}
7653
8157
  });
7654
8158
  } finally {
@@ -8060,6 +8564,7 @@ const APARTE_DEFAULT_UI_EVENTS = [
8060
8564
  "aparte-message-error",
8061
8565
  "aparte-message-aborted",
8062
8566
  "aparte-message-paused",
8567
+ "aparte-message-resumed",
8063
8568
  "aparte-tool-approval-request"
8064
8569
  ];
8065
8570
  function applyElementProps(el2, props, transformValue = (value) => value) {
@@ -8134,8 +8639,95 @@ function installDialogTriggersOnce(doc = document) {
8134
8639
  doc.addEventListener("pointerup", onPointerUp, true);
8135
8640
  doc.addEventListener("click", onClick);
8136
8641
  }
8642
+ const ATTR = "defer-hydration";
8643
+ const waiting = /* @__PURE__ */ new WeakMap();
8644
+ const holderOf = /* @__PURE__ */ new WeakMap();
8645
+ const pending = /* @__PURE__ */ new WeakSet();
8646
+ const live = /* @__PURE__ */ new WeakSet();
8647
+ const observers = /* @__PURE__ */ new WeakMap();
8648
+ const patched = /* @__PURE__ */ new WeakSet();
8649
+ function isHeld(el2) {
8650
+ if (live.has(el2)) return false;
8651
+ if (holderOf.has(el2)) return true;
8652
+ const holder = el2.closest(`[${ATTR}]`);
8653
+ if (!holder) return false;
8654
+ holderOf.set(el2, holder);
8655
+ let list = waiting.get(holder);
8656
+ if (!list) {
8657
+ list = [];
8658
+ waiting.set(holder, list);
8659
+ const observer = new MutationObserver(() => {
8660
+ if (!holder.hasAttribute(ATTR)) release(holder);
8661
+ });
8662
+ observer.observe(holder, { attributes: true, attributeFilter: [ATTR] });
8663
+ observers.set(holder, observer);
8664
+ }
8665
+ list.push(el2);
8666
+ return true;
8667
+ }
8668
+ function release(holder) {
8669
+ observers.get(holder)?.disconnect();
8670
+ observers.delete(holder);
8671
+ const list = waiting.get(holder) ?? [];
8672
+ waiting.delete(holder);
8673
+ for (const el2 of list) {
8674
+ holderOf.delete(el2);
8675
+ if (el2.isConnected && !live.has(el2)) el2.connectedCallback?.();
8676
+ }
8677
+ }
8678
+ function releaseDeferredHydration(root) {
8679
+ if (!root) return;
8680
+ if (root.hasAttribute(ATTR)) root.removeAttribute(ATTR);
8681
+ else if (!waiting.has(root)) return;
8682
+ release(root);
8683
+ }
8684
+ function deferHydrationOf(ctor) {
8685
+ if (patched.has(ctor)) return;
8686
+ patched.add(ctor);
8687
+ const proto = ctor.prototype;
8688
+ const connected = proto.connectedCallback;
8689
+ const changed = proto.attributeChangedCallback;
8690
+ const disconnected = proto.disconnectedCallback;
8691
+ const observed = ctor.observedAttributes ?? [];
8692
+ proto.connectedCallback = function() {
8693
+ if (isHeld(this)) {
8694
+ pending.add(this);
8695
+ return;
8696
+ }
8697
+ if (pending.has(this)) {
8698
+ pending.delete(this);
8699
+ for (const name of observed) {
8700
+ const value = this.getAttribute(name);
8701
+ if (value !== null) changed?.call(this, name, null, value);
8702
+ }
8703
+ }
8704
+ live.add(this);
8705
+ connected?.call(this);
8706
+ };
8707
+ if (changed) {
8708
+ proto.attributeChangedCallback = function(name, oldValue, newValue) {
8709
+ if (isHeld(this)) {
8710
+ pending.add(this);
8711
+ return;
8712
+ }
8713
+ changed.call(this, name, oldValue, newValue);
8714
+ };
8715
+ }
8716
+ proto.disconnectedCallback = function() {
8717
+ const holder = holderOf.get(this);
8718
+ if (holder) {
8719
+ holderOf.delete(this);
8720
+ const list = waiting.get(holder);
8721
+ if (list) list.splice(list.indexOf(this), 1);
8722
+ return;
8723
+ }
8724
+ if (pending.has(this)) return;
8725
+ live.delete(this);
8726
+ disconnected?.call(this);
8727
+ };
8728
+ }
8137
8729
  export {
8138
- registerSegmentRenderer as $,
8730
+ parseMarkdownToSegments as $,
8139
8731
  APARTE_CONFIG_CHANGE as A,
8140
8732
  contextConfig as B,
8141
8733
  copyText as C,
@@ -8146,50 +8738,63 @@ export {
8146
8738
  cssEscape as H,
8147
8739
  declineDefaultRenderers as I,
8148
8740
  defaultSanitizer as J,
8149
- detachConfig as K,
8150
- escapeAttr as L,
8151
- escapeHtml as M,
8152
- filesToAttachments as N,
8153
- getAllRenderers as O,
8154
- getSegmentRenderer as P,
8155
- installDefaultRenderersOnce as Q,
8156
- installDialogTriggersOnce as R,
8157
- isAwaitingReply as S,
8158
- isFormatAdapter as T,
8159
- isSafeUrl as U,
8160
- isSegmentSettled as V,
8161
- parseAparteEventStream as W,
8162
- parseMarkdownToSegments as X,
8163
- populateBubbleFromMessage as Y,
8164
- readableToAsyncIterable as Z,
8165
- registerDefaultRenderers as _,
8741
+ deferHydrationOf as K,
8742
+ defineMountedSegmentRenderer as L,
8743
+ defineMountedToolRenderer as M,
8744
+ detachConfig as N,
8745
+ escapeAttr as O,
8746
+ escapeHtml as P,
8747
+ filesToAttachments as Q,
8748
+ getAllRenderers as R,
8749
+ getSegmentRenderer as S,
8750
+ installDefaultRenderersOnce as T,
8751
+ installDialogTriggersOnce as U,
8752
+ isAwaitingReply as V,
8753
+ isFormatAdapter as W,
8754
+ isSafeUrl as X,
8755
+ isSegmentSettled as Y,
8756
+ overrideSegmentRenderer as Z,
8757
+ parseAparteEventStream as _,
8166
8758
  APARTE_CONVERSATION_SCHEMA_VERSION as a,
8167
- requestUserInput as a0,
8168
- resolveConfig as a1,
8169
- revokeAttachmentUrls as a2,
8170
- runWithConfig as a3,
8171
- segmentDuration as a4,
8172
- segmentTiming as a5,
8173
- subscribeConfigChange as a6,
8174
- toolRefusal as a7,
8175
- unregisterSegmentRenderer as a8,
8176
- uuid as a9,
8177
- eventOrigin as aa,
8178
- activeElementIn as ab,
8179
- gateGlide as ac,
8180
- renderingVersionOf as ad,
8181
- mergeSegmentUpdate as ae,
8182
- writeStreamedMarkdown as af,
8183
- stampSegmentActivity as ag,
8184
- stampSegmentOnInsert as ah,
8185
- stampSegmentOnUpdate as ai,
8186
- renumberSegments as aj,
8187
- openSegmentIds as ak,
8188
- isTerminalStatus as al,
8189
- adoptMessageSegments as am,
8190
- APARTE_DERIVED_SEGMENTS as an,
8191
- chatBoundaryOf as ao,
8192
- rootOf as ap,
8759
+ populateBubbleFromMessage as a0,
8760
+ readableToAsyncIterable as a1,
8761
+ registerDefaultRenderers as a2,
8762
+ registerSegmentRenderer as a3,
8763
+ releaseDeferredHydration as a4,
8764
+ requestUserInput as a5,
8765
+ resolveConfig as a6,
8766
+ revokeAttachmentUrls as a7,
8767
+ runWithConfig as a8,
8768
+ segmentDuration as a9,
8769
+ conversationStatusOf as aA,
8770
+ chatBoundaryOf as aB,
8771
+ rootOf as aC,
8772
+ segmentTiming as aa,
8773
+ subscribeConfigChange as ab,
8774
+ toToolCallView as ac,
8775
+ toolRefusal as ad,
8776
+ unregisterSegmentRenderer as ae,
8777
+ uuid as af,
8778
+ eventOrigin as ag,
8779
+ activeElementIn as ah,
8780
+ gateGlide as ai,
8781
+ toolCallRenderer as aj,
8782
+ toolRendererThatDrew as ak,
8783
+ isMountedRenderer as al,
8784
+ renderingVersionOf as am,
8785
+ onRenderersChange as an,
8786
+ mergeSegmentUpdate as ao,
8787
+ writeStreamedMarkdown as ap,
8788
+ stampSegmentActivity as aq,
8789
+ stampSegmentOnInsert as ar,
8790
+ stampSegmentOnUpdate as as,
8791
+ renumberSegments as at,
8792
+ openSegmentIds as au,
8793
+ isTerminalStatus as av,
8794
+ adoptMessageSegments as aw,
8795
+ APARTE_DERIVED_SEGMENTS as ax,
8796
+ rememberConversationStatus as ay,
8797
+ tellConversationStatus as az,
8193
8798
  APARTE_DEFAULT_BUBBLE_ACTIONS as b,
8194
8799
  APARTE_DEFAULT_HOST_HANDLERS as c,
8195
8800
  APARTE_DEFAULT_ICON_FALLBACKS as d,
@@ -8216,4 +8821,4 @@ export {
8216
8821
  collectRendererStyles as y,
8217
8822
  contentToText as z
8218
8823
  };
8219
- //# sourceMappingURL=dialog-triggers-3G9TqG9g.js.map
8824
+ //# sourceMappingURL=defer-hydration-BwMqeRHq.js.map