@aparte/core 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/client/aparte-client.d.ts +25 -0
  2. package/dist/client/aparte-client.d.ts.map +1 -1
  3. package/dist/components/bubble/aparte-chat-bubble.d.ts +117 -0
  4. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
  5. package/dist/components/chat/aparte-chat.d.ts +70 -15
  6. package/dist/components/chat/aparte-chat.d.ts.map +1 -1
  7. package/dist/components/composer/aparte-composer-action.d.ts +48 -7
  8. package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
  9. package/dist/components/composer/aparte-composer-add-attachment.d.ts +37 -4
  10. package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
  11. package/dist/components/composer/aparte-composer-attachments.d.ts +35 -3
  12. package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
  13. package/dist/components/composer/aparte-composer-cancel.d.ts +25 -2
  14. package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
  15. package/dist/components/composer/aparte-composer-input.d.ts +54 -5
  16. package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
  17. package/dist/components/composer/aparte-composer-send.d.ts +47 -4
  18. package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
  19. package/dist/components/composer/aparte-composer-toolbar.d.ts +15 -4
  20. package/dist/components/composer/aparte-composer-toolbar.d.ts.map +1 -1
  21. package/dist/components/composer/aparte-composer.d.ts +139 -7
  22. package/dist/components/composer/aparte-composer.d.ts.map +1 -1
  23. package/dist/components/composer/index.d.ts +1 -1
  24. package/dist/components/composer/index.d.ts.map +1 -1
  25. package/dist/components/conversation-list/aparte-conversation-list.d.ts +50 -8
  26. package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
  27. package/dist/components/elicitation/aparte-elicitation.d.ts +45 -0
  28. package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
  29. package/dist/components/index.d.ts +1 -1
  30. package/dist/components/index.d.ts.map +1 -1
  31. package/dist/components/status/aparte-chat-status.d.ts +47 -5
  32. package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
  33. package/dist/components/viewport/aparte-chat-viewport.d.ts +78 -8
  34. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  35. package/dist/config/aparte-config.d.ts +22 -0
  36. package/dist/config/aparte-config.d.ts.map +1 -1
  37. package/dist/config/icon-provider.d.ts +17 -15
  38. package/dist/config/icon-provider.d.ts.map +1 -1
  39. package/dist/custom-elements.json +6249 -4138
  40. package/dist/elicitation/approval-panel.d.ts.map +1 -1
  41. package/dist/elicitation/panel.d.ts +23 -2
  42. package/dist/elicitation/panel.d.ts.map +1 -1
  43. package/dist/generated/element-attributes.d.ts +21 -15
  44. package/dist/generated/element-attributes.d.ts.map +1 -1
  45. package/dist/glyphs-QXfbL3n_.js +81 -0
  46. package/dist/glyphs-QXfbL3n_.js.map +1 -0
  47. package/dist/icons/extended.d.ts +153 -0
  48. package/dist/icons/extended.d.ts.map +1 -0
  49. package/dist/icons/glyphs.d.ts +98 -0
  50. package/dist/icons/glyphs.d.ts.map +1 -0
  51. package/dist/icons/index.d.ts +24 -0
  52. package/dist/icons/index.d.ts.map +1 -0
  53. package/dist/icons.d.ts +14 -0
  54. package/dist/icons.d.ts.map +1 -0
  55. package/dist/icons.js +157 -0
  56. package/dist/icons.js.map +1 -0
  57. package/dist/index.css +5082 -2119
  58. package/dist/index.d.ts +37 -3
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +167 -67
  61. package/dist/index.js.map +1 -1
  62. package/dist/index.node.d.ts +2 -2
  63. package/dist/index.node.d.ts.map +1 -1
  64. package/dist/index.node.js +1 -1
  65. package/dist/index.node.js.map +1 -1
  66. package/dist/{is-awaiting-reply-BWTDvQ__.js → is-awaiting-reply-DrO_oR32.js} +333 -147
  67. package/dist/is-awaiting-reply-DrO_oR32.js.map +1 -0
  68. package/dist/primitives/icon/aparte-icon.d.ts +49 -0
  69. package/dist/primitives/icon/aparte-icon.d.ts.map +1 -0
  70. package/dist/primitives/icon/index.d.ts +2 -0
  71. package/dist/primitives/icon/index.d.ts.map +1 -0
  72. package/dist/primitives/index.d.ts +1 -0
  73. package/dist/primitives/index.d.ts.map +1 -1
  74. package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts +20 -5
  75. package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -1
  76. package/dist/primitives/select/aparte-optgroup.d.ts +30 -7
  77. package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
  78. package/dist/primitives/select/aparte-option.d.ts +29 -3
  79. package/dist/primitives/select/aparte-option.d.ts.map +1 -1
  80. package/dist/primitives/select/aparte-select.d.ts +36 -11
  81. package/dist/primitives/select/aparte-select.d.ts.map +1 -1
  82. package/dist/renderers/segments/artifact/card.d.ts.map +1 -1
  83. package/dist/renderers/segments/thinking.d.ts.map +1 -1
  84. package/dist/renderers/segments/tool-call.d.ts.map +1 -1
  85. package/dist/types/element-map.d.ts +2 -0
  86. package/dist/types/element-map.d.ts.map +1 -1
  87. package/dist/types/index.d.ts +1 -1
  88. package/dist/types/index.d.ts.map +1 -1
  89. package/dist/types/segments.d.ts +92 -7
  90. package/dist/types/segments.d.ts.map +1 -1
  91. package/dist/types/tools.d.ts +2 -0
  92. package/dist/types/tools.d.ts.map +1 -1
  93. package/package.json +7 -1
  94. package/dist/is-awaiting-reply-BWTDvQ__.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { A as APARTE_ICON_GLYPHS } from "./glyphs-QXfbL3n_.js";
1
2
  function contentToText(content) {
2
3
  if (typeof content === "string") return content;
3
4
  return content.filter((p) => p.type === "text").map((p) => p.text).join("");
@@ -35,29 +36,7 @@ class AparteError extends Error {
35
36
  return new AparteError(message, defaultCode, void 0, error, status);
36
37
  }
37
38
  }
38
- const APARTE_DEFAULT_ICON_FALLBACKS = {
39
- copy: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`,
40
- check: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`,
41
- send: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>`,
42
- loading: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="aparte-icon-spin"><circle cx="12" cy="12" r="10" stroke-opacity="0.25"/><path d="M12 2a10 10 0 0 1 10 10"/></svg>`,
43
- error: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`,
44
- expand: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`,
45
- collapse: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>`,
46
- terminal: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`,
47
- paperclip: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>`,
48
- image: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`,
49
- file: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>`,
50
- scrollDown: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>`,
51
- retry: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>`,
52
- edit: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`,
53
- thumbUp: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>`,
54
- thumbDown: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/></svg>`,
55
- prevBranch: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>`,
56
- nextBranch: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>`,
57
- tool: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>`,
58
- close: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`,
59
- stop: `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="12" rx="2"/></svg>`
60
- };
39
+ const APARTE_DEFAULT_ICON_FALLBACKS = APARTE_ICON_GLYPHS;
61
40
  const APARTE_DEFAULT_LOCALE = {
62
41
  inputPlaceholder: "Type a message...",
63
42
  sendButton: "Send",
@@ -122,6 +101,14 @@ const APARTE_DEFAULT_LOCALE = {
122
101
  unarchiveConversation: "Unarchive conversation",
123
102
  direction: "ltr"
124
103
  };
104
+ const APARTE_DEFAULT_SKELETON_FALLBACKS = {
105
+ message: '<div class="aparte-skeleton-fallback">Loading...</div>',
106
+ code: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--code">Loading code...</div>',
107
+ thinking: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--tight">Thinking...</div>',
108
+ input: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--snug">...</div>',
109
+ list: '<div class="aparte-skeleton-fallback">Loading items...</div>',
110
+ text: '<div class="aparte-skeleton-fallback aparte-skeleton-fallback--tight">...</div>'
111
+ };
125
112
  function isFormatAdapter(p) {
126
113
  return typeof p.buildRequest === "function" && typeof p.parseStream === "function" && typeof p.defaultEndpoint === "string" && (typeof p.authHeaders === "function" || typeof p.authQuery === "function");
127
114
  }
@@ -1874,6 +1861,24 @@ class AparteConfig {
1874
1861
  getTools() {
1875
1862
  return Array.from(this._tools.values()).map((e) => e.tool);
1876
1863
  }
1864
+ /**
1865
+ * The registered tools' own `systemPrompt`s, joined, or `null` if none set one.
1866
+ *
1867
+ * `AparteTool.systemPrompt` has always been documented as "injected automatically when
1868
+ * this tool is registered", on the type and in the tools guide — and nothing read it.
1869
+ * A field that is documented and dead is worse than one that does not exist: a caller
1870
+ * writes it, sees the tool work (the model has the schema either way), and never learns
1871
+ * that the sentence explaining WHEN to reach for it was dropped. `@aparte/plugin-ask-user`
1872
+ * sets one, so a shipped plugin was losing its instructions.
1873
+ *
1874
+ * Registration order, blank line between, because two tools' instructions are two
1875
+ * paragraphs and not one run-on sentence. The app's own template stays a separate
1876
+ * message — see `resolveSystemPrompt`, which is about the app and not about the tools.
1877
+ */
1878
+ resolveToolSystemPrompts() {
1879
+ const prompts = Array.from(this._tools.values()).map((entry) => entry.tool.systemPrompt?.trim()).filter((prompt) => Boolean(prompt));
1880
+ return prompts.length ? prompts.join("\n\n") : null;
1881
+ }
1877
1882
  /** Get the handler for a tool by name */
1878
1883
  getToolHandler(name) {
1879
1884
  return this._tools.get(name)?.handler;
@@ -2123,16 +2128,15 @@ class AparteConfig {
2123
2128
  _defaultHighlightRenderer(code) {
2124
2129
  return `<pre><code>${escapeHtml(code)}</code></pre>`;
2125
2130
  }
2131
+ /**
2132
+ * ONE owner. This used to hold a second, hand-written copy of
2133
+ * `APARTE_DEFAULT_SKELETON_FALLBACKS`, and the two had already drifted — `message`
2134
+ * said "Loading message…" here and "Loading…" there, `text` the reverse. A duplicate
2135
+ * that disagrees with its original is worse than either: whichever one you read, you
2136
+ * cannot tell which one ships.
2137
+ */
2126
2138
  _defaultSkeletonRenderer(type) {
2127
- const fallbacks = {
2128
- message: '<div class="aparte-skeleton-fallback" style="padding:16px;color:#9ca3af;">Loading message...</div>',
2129
- code: '<div class="aparte-skeleton-fallback" style="padding:16px;background:#1e293b;color:#64748b;border-radius:8px;">Loading code...</div>',
2130
- text: '<div class="aparte-skeleton-fallback" style="padding:8px;color:#9ca3af;">Loading...</div>',
2131
- thinking: '<div class="aparte-skeleton-fallback" style="padding:8px;color:#9ca3af;">Thinking...</div>',
2132
- input: '<div class="aparte-skeleton-fallback" style="padding:12px;color:#9ca3af;">...</div>',
2133
- list: '<div class="aparte-skeleton-fallback" style="padding:16px;color:#9ca3af;">Loading items...</div>'
2134
- };
2135
- return fallbacks[type] || fallbacks.text;
2139
+ return APARTE_DEFAULT_SKELETON_FALLBACKS[type] ?? APARTE_DEFAULT_SKELETON_FALLBACKS.text;
2136
2140
  }
2137
2141
  }
2138
2142
  const GLOBAL_CONFIG_KEY = Symbol.for("aparte.globalConfig.1");
@@ -2196,14 +2200,6 @@ function subscribeConfigChange(el2, handler) {
2196
2200
  window.addEventListener(APARTE_CONFIG_CHANGE, listener);
2197
2201
  return () => window.removeEventListener(APARTE_CONFIG_CHANGE, listener);
2198
2202
  }
2199
- const APARTE_DEFAULT_SKELETON_FALLBACKS = {
2200
- message: '<div class="aparte-skeleton-fallback" style="padding:16px;color:#9ca3af;">Loading...</div>',
2201
- code: '<div class="aparte-skeleton-fallback" style="padding:16px;background:#1e293b;color:#64748b;border-radius:8px;">Loading code...</div>',
2202
- thinking: '<div class="aparte-skeleton-fallback" style="padding:8px;color:#9ca3af;">Thinking...</div>',
2203
- input: '<div class="aparte-skeleton-fallback" style="padding:12px;color:#9ca3af;">...</div>',
2204
- list: '<div class="aparte-skeleton-fallback" style="padding:16px;color:#9ca3af;">Loading items...</div>',
2205
- text: '<div class="aparte-skeleton-fallback" style="padding:8px;color:#9ca3af;">...</div>'
2206
- };
2207
2203
  function writeStreamedMarkdown(host, contentEl, content, streaming) {
2208
2204
  if (streaming) {
2209
2205
  if (host._aparteSmd === void 0) {
@@ -2276,7 +2272,21 @@ const thinkingRenderer = {
2276
2272
  * new class of risk. With no Markdown provider registered it degrades to the
2277
2273
  * zero-dependency default (escape + `<br>`), which is what it used to be.
2278
2274
  */
2279
- render: (segment) => `<details class="aparte-segment aparte-segment-thinking" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed === false ? "open" : ""}><summary class="aparte-thinking-header"><span class="aparte-thinking-label">${escapeHtml(segment.label || contextConfig().t("thinking"))}</span><span class="aparte-thinking-toggle"></span></summary><div class="aparte-thinking-content">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
2275
+ /*
2276
+ * WEARS THE ACCORDION RECIPE. A reasoning block is a disclosure — `<details>`, a
2277
+ * `<summary>` you press, a panel, a chevron that turns — which is exactly what
2278
+ * `surface/accordion.css` already draws, and this renderer used to draw a second
2279
+ * time under four classes of its own. Two costs, both visible: it looked unrelated
2280
+ * to every other disclosure in the library, and its chevron was the CHARACTER `▼`,
2281
+ * a fourth way to draw a chevron here and the only one that is not a glyph — so it
2282
+ * could not take `--aparte-icon-size`, could not be replaced through the icon
2283
+ * provider, and rendered in whatever the platform font felt like.
2284
+ *
2285
+ * What stays thinking's own is the left rail and the quieter tone, which is the
2286
+ * only part that is genuinely about reasoning rather than about disclosure.
2287
+ * `.aparte-thinking-label` stays too: `relabel` below queries it.
2288
+ */
2289
+ 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(segment.label || contextConfig().t("thinking"))}</span>${contextConfig().getIcon("expand")}</summary><div class="aparte-accordion__panel aparte-thinking-content">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
2280
2290
  /**
2281
2291
  * The default label is the only config-derived text here — and `segment.label`
2282
2292
  * still wins, exactly as in `render`, because that string is the app's.
@@ -2350,7 +2360,7 @@ const codeRenderer = {
2350
2360
  <div class="aparte-code-header">
2351
2361
  ${segment.filename ? `<span class="aparte-code-filename">${escapeHtml(segment.filename)}</span>` : `<span class="aparte-code-header-filler"></span>`}
2352
2362
  <span class="aparte-code-language">${escapeHtml(segment.language || "")}</span>
2353
- <button class="aparte-code-copy" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}">
2363
+ <button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-code-copy" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}">
2354
2364
  ${contextConfig().getIcon("copy")}
2355
2365
  </button>
2356
2366
  </div>
@@ -2419,7 +2429,7 @@ const errorRenderer = {
2419
2429
  return out;
2420
2430
  }
2421
2431
  return `
2422
- <div class="aparte-segment aparte-segment-error" data-segment-id="${escapeHtml(segment.id)}">
2432
+ <div class="aparte-segment aparte-alert aparte-alert--danger aparte-segment-error" data-segment-id="${escapeHtml(segment.id)}">
2423
2433
  <div class="aparte-error-icon-wrapper">
2424
2434
  ${contextConfig().getIcon("error") || "⚠"}
2425
2435
  </div>
@@ -2518,7 +2528,7 @@ function pillMarkup(segment, name, status) {
2518
2528
  <span class="aparte-tool-icon">${contextConfig().getIcon("tool")}</span>
2519
2529
  <span class="aparte-tool-name">${escapeHtml(name)}</span>
2520
2530
  </span>
2521
- <span class="aparte-tool-spinner" aria-hidden="true"${status === "pending" ? "" : " hidden"}></span>
2531
+ <span class="aparte-spinner aparte-tool-spinner" aria-hidden="true"${status === "pending" ? "" : " hidden"}></span>
2522
2532
  <span class="aparte-tool-state">${badge}</span>`;
2523
2533
  }
2524
2534
  const toolCallRenderer = {
@@ -2533,6 +2543,7 @@ const toolCallRenderer = {
2533
2543
  if (out) return out;
2534
2544
  }
2535
2545
  const pill = pillMarkup(segment, name, status);
2546
+ const toggle = contextConfig().getIcon("expand");
2536
2547
  const input = describeInput(segment);
2537
2548
  const result = segment.result;
2538
2549
  if (!input && !result) {
@@ -2544,7 +2555,7 @@ const toolCallRenderer = {
2544
2555
  const cfg = contextConfig();
2545
2556
  return `
2546
2557
  <details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}">
2547
- <summary class="aparte-tool-summary"><span class="aparte-tool-toggle"></span>${pill}</summary>
2558
+ <summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
2548
2559
  <div class="aparte-tool-detail">
2549
2560
  ${input ? `
2550
2561
  <div class="aparte-tool-part" data-part="input">
@@ -2619,7 +2630,7 @@ const toolCallRenderer = {
2619
2630
  const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);
2620
2631
  customRenderer?.setup?.(element, segment);
2621
2632
  },
2622
- // CSS lives in styles/aparte.css — see its "Segment renderers" section for why a
2633
+ // CSS lives in styles/segment/ — see that folder for why a
2623
2634
  // built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
2624
2635
  // has no other way onto the page.
2625
2636
  getStyles: () => ""
@@ -2630,9 +2641,9 @@ const pipelineWaitingRenderer = {
2630
2641
  render: (segment) => {
2631
2642
  return `
2632
2643
  <div class="aparte-segment aparte-segment-pipeline-waiting" data-segment-id="${escapeHtml(segment.id)}" aria-label="${escapeAttr(waitingLabel())}" role="status">
2633
- <span class="aparte-pw-dot"></span>
2634
- <span class="aparte-pw-dot"></span>
2635
- <span class="aparte-pw-dot"></span>
2644
+ <span class="aparte-dot aparte-pw-dot"></span>
2645
+ <span class="aparte-dot aparte-pw-dot"></span>
2646
+ <span class="aparte-dot aparte-pw-dot"></span>
2636
2647
  </div>`;
2637
2648
  },
2638
2649
  update: () => {
@@ -2657,7 +2668,7 @@ const pipelineWaitingRenderer = {
2657
2668
  });
2658
2669
  observer.observe(parent, { childList: true });
2659
2670
  },
2660
- // CSS lives in styles/aparte.css — see its "Segment renderers" section for why a
2671
+ // CSS lives in styles/segment/ — see that folder for why a
2661
2672
  // built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
2662
2673
  // has no other way onto the page.
2663
2674
  getStyles: () => ""
@@ -2863,7 +2874,7 @@ function renderBinaryFileArtifact(segment, kind) {
2863
2874
  if (cached && !isStreaming) {
2864
2875
  const previewBody = cached.previewHtml ? contextConfig().sanitizeHtml(cached.previewHtml) : `<div class="aparte-art-file__preview-empty">Preview not available for ${escapeHtml(kind)} yet</div>`;
2865
2876
  return `
2866
- <div class="aparte-segment aparte-segment-artifact-file"
2877
+ <div class="aparte-segment aparte-card aparte-segment-artifact-file"
2867
2878
  data-segment-id="${escapeHtml(segment.id)}"
2868
2879
  data-artifact-type="${escapeHtml(kind)}"
2869
2880
  data-state="ready">
@@ -2874,7 +2885,7 @@ function renderBinaryFileArtifact(segment, kind) {
2874
2885
  <div class="aparte-art-file__meta-sub" data-role="file-sub">${escapeHtml(formatBytes(cached.bytes))} · ${escapeHtml(kind.toUpperCase())}</div>
2875
2886
  </div>
2876
2887
  <div class="aparte-art-file__actions">
2877
- ${canRedownload ? `<button type="button" class="aparte-art-file__btn aparte-art-file__btn--primary" data-action="download">${downloadLabel}</button>` : ""}
2888
+ ${canRedownload ? `<button type="button" class="aparte-btn aparte-btn--primary aparte-btn--solid aparte-art-file__btn aparte-art-file__btn--primary" data-action="download">${downloadLabel}</button>` : ""}
2878
2889
  </div>
2879
2890
  </div>
2880
2891
  <div class="aparte-art-file__body">
@@ -2889,7 +2900,7 @@ function renderBinaryFileArtifact(segment, kind) {
2889
2900
  const cleanContent = stripCodeFences(segment.content || "");
2890
2901
  const subText = isStreaming ? contextConfig().t("generating") : contextConfig().t("rebuildingPreview");
2891
2902
  return `
2892
- <div class="aparte-segment aparte-segment-artifact-file"
2903
+ <div class="aparte-segment aparte-card aparte-segment-artifact-file"
2893
2904
  data-segment-id="${escapeHtml(segment.id)}"
2894
2905
  data-artifact-type="${escapeHtml(kind)}"
2895
2906
  data-state="${isStreaming ? "streaming" : "compiling"}">
@@ -2900,7 +2911,7 @@ function renderBinaryFileArtifact(segment, kind) {
2900
2911
  <div class="aparte-art-file__meta-sub" data-role="file-sub">${escapeHtml(subText)}</div>
2901
2912
  </div>
2902
2913
  <div class="aparte-art-file__actions">
2903
- ${canRedownload ? `<button type="button" class="aparte-art-file__btn aparte-art-file__btn--primary" data-action="download" disabled>${downloadLabel}</button>` : ""}
2914
+ ${canRedownload ? `<button type="button" class="aparte-btn aparte-btn--primary aparte-btn--solid aparte-art-file__btn aparte-art-file__btn--primary" data-action="download" disabled>${downloadLabel}</button>` : ""}
2904
2915
  </div>
2905
2916
  </div>
2906
2917
  <div class="aparte-art-file__body">
@@ -3058,7 +3069,7 @@ function showSandboxError(element, phase, errorMsg) {
3058
3069
  body.innerHTML = `
3059
3070
  <div class="aparte-art-file__error">
3060
3071
  <div class="aparte-art-file__error-title">${escapeHtml(contextConfig().t("sandboxError"))}</div>
3061
- <div class="aparte-art-file__error-msg">${escapeHtml(short)}</div>
3072
+ <div class="aparte-output aparte-art-file__error-msg">${escapeHtml(short)}</div>
3062
3073
  <div class="aparte-art-file__error-hint">${escapeHtml(contextConfig().t("sandboxErrorHint"))}</div>
3063
3074
  </div>
3064
3075
  `;
@@ -3083,8 +3094,9 @@ const artifactRenderer = {
3083
3094
  const codeLabel = cfg.t("code");
3084
3095
  const isBinary = BINARY_FILE_KINDS.has(kind);
3085
3096
  const cleanContent = stripCodeFences(segment.content || "");
3097
+ const cardId = escapeAttr(segment.id);
3086
3098
  return `
3087
- <div class="aparte-segment aparte-segment-artifact-card"
3099
+ <div class="aparte-segment aparte-card aparte-segment-artifact-card"
3088
3100
  data-segment-id="${escapeHtml(segment.id)}"
3089
3101
  data-artifact-type="${escapeHtml(kind)}"
3090
3102
  data-streaming="${isStreaming ? "true" : "false"}"
@@ -3093,31 +3105,31 @@ const artifactRenderer = {
3093
3105
  data-binary="${isBinary ? "true" : "false"}">
3094
3106
  <header class="aparte-art-card__header">
3095
3107
  <div class="aparte-art-card__title-block">
3096
- <span class="aparte-art-card__kind" data-kind="${escapeHtml(kind)}">${escapeHtml(displayLang)}</span>
3108
+ <span class="aparte-badge aparte-badge--outline aparte-art-card__kind" data-kind="${escapeHtml(kind)}">${escapeHtml(displayLang)}</span>
3097
3109
  <span class="aparte-art-card__title">${escapeHtml(title)}</span>
3098
- ${isStreaming ? '<span class="aparte-art-card__pulse" aria-label="Streaming"></span>' : ""}
3110
+ ${isStreaming ? `<span class="aparte-dot aparte-art-card__pulse" role="img" aria-label="${escapeAttr(cfg.t("generating"))}"></span>` : ""}
3099
3111
  </div>
3100
3112
  <div class="aparte-art-card__actions">
3101
- <button type="button" class="aparte-art-card__btn" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}" aria-label="${escapeAttr(contextConfig().t("copy"))}">
3113
+ <button type="button" class="aparte-btn aparte-btn--icon aparte-art-card__btn" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}" aria-label="${escapeAttr(contextConfig().t("copy"))}">
3102
3114
  ${contextConfig().getIcon("copy")}
3103
3115
  </button>
3104
- <button type="button" class="aparte-art-card__btn" data-action="download" title="${escapeAttr(downloadLabel)}" aria-label="${escapeAttr(downloadLabel)}" ${isStreaming ? "disabled" : ""}>
3105
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v9m0 0l-3-3m3 3l3-3M2 13h12"/></svg>
3116
+ <button type="button" class="aparte-btn aparte-btn--icon aparte-art-card__btn" data-action="download" title="${escapeAttr(downloadLabel)}" aria-label="${escapeAttr(downloadLabel)}" ${isStreaming ? "disabled" : ""}>
3117
+ ${contextConfig().getIcon("download")}
3106
3118
  </button>
3107
3119
  </div>
3108
3120
  </header>
3109
- <nav class="aparte-art-card__tabs" role="tablist">
3110
- <button type="button" role="tab" data-tab-target="code" aria-selected="true">${escapeHtml(codeLabel)}</button>
3111
- ${previewable ? `<button type="button" role="tab" data-tab-target="preview" aria-selected="false" ${isStreaming ? "disabled" : ""}>${escapeHtml(previewLabel)}</button>` : ""}
3121
+ <nav class="aparte-tabs aparte-tabs--underline aparte-art-card__tabs" role="tablist">
3122
+ <button type="button" class="aparte-tabs__tab" role="tab" id="aparte-art-${cardId}-tab-code" aria-controls="aparte-art-${cardId}-pane-code" aria-selected="true" tabindex="0" data-tab-target="code">${escapeHtml(codeLabel)}</button>
3123
+ ${previewable ? `<button type="button" class="aparte-tabs__tab" role="tab" id="aparte-art-${cardId}-tab-preview" aria-controls="aparte-art-${cardId}-pane-preview" aria-selected="false" tabindex="-1" data-tab-target="preview" ${isStreaming ? "disabled" : ""}>${escapeHtml(previewLabel)}</button>` : ""}
3112
3124
  </nav>
3113
3125
  <div class="aparte-art-card__body">
3114
- <div class="aparte-art-card__pane" data-pane="code">
3126
+ <div class="aparte-art-card__pane" role="tabpanel" id="aparte-art-${cardId}-pane-code" aria-labelledby="aparte-art-${cardId}-tab-code" tabindex="0" data-pane="code">
3115
3127
  <div class="aparte-code-content-wrapper">
3116
3128
  <pre><code class="language-${escapeHtml(displayLang)}">${escapeHtml(cleanContent)}</code></pre>
3117
3129
  </div>
3118
3130
  </div>
3119
3131
  ${previewable ? `
3120
- <div class="aparte-art-card__pane" data-pane="preview">
3132
+ <div class="aparte-art-card__pane" role="tabpanel" id="aparte-art-${cardId}-pane-preview" aria-labelledby="aparte-art-${cardId}-tab-preview" tabindex="0" data-pane="preview">
3121
3133
  <div class="aparte-art-card__pending">${escapeHtml(cfg.t("previewPending"))}</div>
3122
3134
  </div>
3123
3135
  ` : ""}
@@ -3172,17 +3184,38 @@ const artifactRenderer = {
3172
3184
  }).catch(() => {
3173
3185
  });
3174
3186
  }
3175
- element.querySelectorAll("[data-tab-target]").forEach((btn) => {
3176
- btn.addEventListener("click", () => {
3177
- const target = btn.getAttribute("data-tab-target");
3178
- if (!target) return;
3179
- if (target === "preview") mountPreviewFrame(element, segment);
3180
- element.setAttribute("data-tab", target);
3181
- element.querySelectorAll("[data-tab-target]").forEach((b) => {
3182
- b.setAttribute("aria-selected", b === btn ? "true" : "false");
3183
- });
3187
+ const tabs = [...element.querySelectorAll("[data-tab-target]")];
3188
+ const select = (btn, focus) => {
3189
+ const target = btn.getAttribute("data-tab-target");
3190
+ if (!target || btn.disabled) return;
3191
+ if (target === "preview") mountPreviewFrame(element, segment);
3192
+ element.setAttribute("data-tab", target);
3193
+ for (const b of tabs) {
3194
+ const on = b === btn;
3195
+ b.setAttribute("aria-selected", on ? "true" : "false");
3196
+ b.tabIndex = on ? 0 : -1;
3197
+ }
3198
+ if (focus) btn.focus();
3199
+ };
3200
+ for (const [i, btn] of tabs.entries()) {
3201
+ btn.addEventListener("click", () => select(btn, false));
3202
+ btn.addEventListener("keydown", (e) => {
3203
+ const step = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0;
3204
+ let next = -1;
3205
+ if (step) next = (i + step + tabs.length) % tabs.length;
3206
+ else if (e.key === "Home") next = 0;
3207
+ else if (e.key === "End") next = tabs.length - 1;
3208
+ if (next < 0) return;
3209
+ e.preventDefault();
3210
+ for (let n = 0; n < tabs.length; n++) {
3211
+ const candidate = tabs[(next + n * (step || 1) + tabs.length) % tabs.length];
3212
+ if (candidate && !candidate.disabled) {
3213
+ select(candidate, true);
3214
+ return;
3215
+ }
3216
+ }
3184
3217
  });
3185
- });
3218
+ }
3186
3219
  const copyBtn = element.querySelector('[data-action="copy"]');
3187
3220
  if (copyBtn) {
3188
3221
  copyBtn.addEventListener("click", () => {
@@ -3267,7 +3300,7 @@ const artifactRenderer = {
3267
3300
  });
3268
3301
  }
3269
3302
  },
3270
- // CSS lives in styles/aparte.css — see its "Segment renderers" section for why a
3303
+ // CSS lives in styles/segment/ — see that folder for why a
3271
3304
  // built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
3272
3305
  // has no other way onto the page.
3273
3306
  getStyles: () => ""
@@ -5269,6 +5302,7 @@ function readableToAsyncIterable(stream, signal) {
5269
5302
  }
5270
5303
  };
5271
5304
  }
5305
+ let steppedPanelSeq = 0;
5272
5306
  let _relabels = null;
5273
5307
  function onRelabel(fn) {
5274
5308
  _relabels?.push(fn);
@@ -5279,6 +5313,18 @@ function el$1(tag, className, text) {
5279
5313
  if (text != null) node.textContent = text;
5280
5314
  return node;
5281
5315
  }
5316
+ function optionBody(label, description) {
5317
+ const body = el$1("span", "aparte-elic-option-body");
5318
+ body.appendChild(el$1("span", "aparte-elic-option-title", label));
5319
+ if (description) body.appendChild(el$1("span", "aparte-elic-option-desc", description));
5320
+ return body;
5321
+ }
5322
+ function commandOption(label, description, recommended) {
5323
+ const button = el$1("button", "aparte-btn aparte-btn--block aparte-btn--surface aparte-elic-option aparte-elic-option--command" + (recommended ? " aparte-elic-option--recommended" : ""));
5324
+ button.type = "button";
5325
+ button.appendChild(optionBody(label, description));
5326
+ return button;
5327
+ }
5282
5328
  function fieldHeader(parent, field, skipTitle) {
5283
5329
  let titleId;
5284
5330
  if (field.title && field.title.trim() !== skipTitle?.trim()) {
@@ -5291,61 +5337,87 @@ function fieldHeader(parent, field, skipTitle) {
5291
5337
  return titleId;
5292
5338
  }
5293
5339
  function labelGroup(list, opts) {
5294
- list.setAttribute("role", opts.multiple ? "group" : "radiogroup");
5340
+ list.setAttribute("role", opts.multiple || opts.commands ? "group" : "radiogroup");
5295
5341
  if (opts.titleId) list.setAttribute("aria-labelledby", opts.titleId);
5296
5342
  else if (opts.fallbackLabel) list.setAttribute("aria-label", opts.fallbackLabel);
5297
5343
  }
5298
- function buildEnumField(field, onChange, fallbackLabel) {
5344
+ function buildEnumField(field, onChange, fallbackLabel, settle) {
5299
5345
  const wrap = el$1("div", "aparte-elic-field aparte-elic-enum");
5300
5346
  const titleId = fieldHeader(wrap, field, fallbackLabel);
5301
5347
  const list = el$1("div", "aparte-elic-options");
5302
- labelGroup(list, { multiple: field.multiple, titleId, fallbackLabel });
5303
5348
  const name = `elic-${uuid()}`;
5304
5349
  const type = field.multiple ? "checkbox" : "radio";
5350
+ const controlRecipe = type === "checkbox" ? "aparte-checkbox" : "aparte-radio";
5305
5351
  const allowOther = field.allowOther ?? contextConfig().getElicitationOptions().allowOther;
5306
5352
  const defaults = new Set(Array.isArray(field.default) ? field.default : field.default != null ? [field.default] : []);
5353
+ const settling = settle != null && !field.multiple && defaults.size === 0;
5354
+ labelGroup(list, { multiple: field.multiple, commands: settling, titleId, fallbackLabel });
5307
5355
  const buildOption = (value, label, description, recommended) => {
5308
- const row = el$1("label", "aparte-elic-option" + (recommended ? " aparte-elic-option--recommended" : ""));
5309
- const control = el$1("input", "aparte-elic-control");
5356
+ if (settling) {
5357
+ const button = commandOption(label, description, recommended);
5358
+ button.addEventListener("click", () => settle?.(value));
5359
+ return button;
5360
+ }
5361
+ const row = el$1("label", "aparte-field-choice aparte-elic-option" + (recommended ? " aparte-elic-option--recommended" : ""));
5362
+ const control = el$1("input", controlRecipe + " aparte-elic-control");
5310
5363
  control.type = type;
5311
5364
  control.name = name;
5312
5365
  control.value = value;
5313
5366
  if (defaults.has(value)) control.checked = true;
5314
- const body = el$1("span", "aparte-elic-option-body");
5315
- body.appendChild(el$1("span", "aparte-elic-option-title", label));
5316
- if (description) body.appendChild(el$1("span", "aparte-elic-option-desc", description));
5317
- row.append(control, body);
5367
+ row.append(control, optionBody(label, description));
5318
5368
  return row;
5319
5369
  };
5320
5370
  for (const opt of field.options) {
5321
5371
  list.appendChild(buildOption(opt.value, opt.label ?? opt.value, opt.description, opt.recommended));
5322
5372
  }
5323
5373
  let otherText = null;
5374
+ let otherOpen = false;
5324
5375
  if (allowOther) {
5325
- const row = el$1("label", "aparte-elic-option aparte-elic-option--other");
5326
- const control = el$1("input", "aparte-elic-control");
5327
- control.type = type;
5328
- control.name = name;
5329
- control.value = "__other__";
5330
- const body = el$1("span", "aparte-elic-option-body");
5331
5376
  const t = contextConfig();
5332
- const otherTitle = el$1("span", "aparte-elic-option-title", t.t("elicitationOther"));
5333
- body.appendChild(otherTitle);
5334
- otherText = el$1("input", "aparte-elic-other-input");
5377
+ otherText = el$1("input", "aparte-field aparte-elic-other-input");
5335
5378
  otherText.type = "text";
5336
5379
  otherText.placeholder = t.t("elicitationOtherPlaceholder");
5337
5380
  otherText.style.display = "none";
5338
5381
  otherText.setAttribute("aria-label", t.t("elicitationOtherLabel"));
5339
5382
  const otherInput = otherText;
5340
- onRelabel(() => {
5341
- const now = contextConfig();
5342
- otherTitle.textContent = now.t("elicitationOther");
5343
- otherInput.placeholder = now.t("elicitationOtherPlaceholder");
5344
- otherInput.setAttribute("aria-label", now.t("elicitationOtherLabel"));
5345
- });
5346
- body.appendChild(otherText);
5347
- row.append(control, body);
5348
- list.appendChild(row);
5383
+ if (settling) {
5384
+ const button = commandOption(t.t("elicitationOther"));
5385
+ button.classList.add("aparte-elic-option--other");
5386
+ button.setAttribute("aria-expanded", "false");
5387
+ button.addEventListener("click", () => {
5388
+ otherOpen = true;
5389
+ button.setAttribute("aria-expanded", "true");
5390
+ otherInput.style.display = "";
5391
+ otherInput.focus();
5392
+ onChange();
5393
+ });
5394
+ const otherTitle = button.querySelector(".aparte-elic-option-title");
5395
+ onRelabel(() => {
5396
+ const now = contextConfig();
5397
+ otherTitle.textContent = now.t("elicitationOther");
5398
+ otherInput.placeholder = now.t("elicitationOtherPlaceholder");
5399
+ otherInput.setAttribute("aria-label", now.t("elicitationOtherLabel"));
5400
+ });
5401
+ list.append(button, otherInput);
5402
+ } else {
5403
+ const row = el$1("label", "aparte-field-choice aparte-elic-option aparte-elic-option--other");
5404
+ const control = el$1("input", controlRecipe + " aparte-elic-control");
5405
+ control.type = type;
5406
+ control.name = name;
5407
+ control.value = "__other__";
5408
+ const body = el$1("span", "aparte-elic-option-body");
5409
+ const otherTitle = el$1("span", "aparte-elic-option-title", t.t("elicitationOther"));
5410
+ body.appendChild(otherTitle);
5411
+ onRelabel(() => {
5412
+ const now = contextConfig();
5413
+ otherTitle.textContent = now.t("elicitationOther");
5414
+ otherInput.placeholder = now.t("elicitationOtherPlaceholder");
5415
+ otherInput.setAttribute("aria-label", now.t("elicitationOtherLabel"));
5416
+ });
5417
+ body.appendChild(otherText);
5418
+ row.append(control, body);
5419
+ list.appendChild(row);
5420
+ }
5349
5421
  }
5350
5422
  wrap.appendChild(list);
5351
5423
  const controls = () => Array.from(list.querySelectorAll(".aparte-elic-control"));
@@ -5359,8 +5431,18 @@ function buildEnumField(field, onChange, fallbackLabel) {
5359
5431
  onChange();
5360
5432
  });
5361
5433
  otherText?.addEventListener("input", onChange);
5434
+ const typedOther = () => otherText?.value.trim() ?? "";
5435
+ if (settling) {
5436
+ return {
5437
+ el: wrap,
5438
+ getValue: () => typedOther(),
5439
+ isComplete: () => otherOpen && typedOther() !== "",
5440
+ focus: () => list.querySelector(".aparte-elic-option--command")?.focus(),
5441
+ offersSubmit: () => otherOpen
5442
+ };
5443
+ }
5362
5444
  const getValue = () => {
5363
- const otherVal = other()?.checked && otherText?.value.trim() ? otherText.value.trim() : "";
5445
+ const otherVal = other()?.checked && typedOther() ? typedOther() : "";
5364
5446
  if (field.multiple) {
5365
5447
  const vals = controls().filter((c) => c.checked && c.value !== "__other__").map((c) => c.value);
5366
5448
  if (otherVal) vals.push(otherVal);
@@ -5380,15 +5462,42 @@ function buildEnumField(field, onChange, fallbackLabel) {
5380
5462
  focus: () => controls()[0]?.focus()
5381
5463
  };
5382
5464
  }
5383
- function buildBooleanField(field, onChange, fallbackLabel) {
5465
+ function buildBooleanField(field, onChange, fallbackLabel, settle) {
5384
5466
  const wrap = el$1("div", "aparte-elic-field aparte-elic-boolean");
5385
5467
  const titleId = fieldHeader(wrap, field, fallbackLabel);
5386
5468
  const list = el$1("div", "aparte-elic-options");
5387
- labelGroup(list, { titleId, fallbackLabel });
5388
5469
  const name = `elic-${uuid()}`;
5470
+ const settling = settle != null && field.default == null;
5471
+ labelGroup(list, { commands: settling, titleId, fallbackLabel });
5472
+ if (settling) {
5473
+ const t2 = contextConfig();
5474
+ const mkCommand = (val, label, defaulted) => {
5475
+ const button = commandOption(label);
5476
+ button.addEventListener("click", () => settle?.(val));
5477
+ if (defaulted) {
5478
+ const title = button.querySelector(".aparte-elic-option-title");
5479
+ onRelabel(() => {
5480
+ title.textContent = contextConfig().t(defaulted);
5481
+ });
5482
+ }
5483
+ list.appendChild(button);
5484
+ };
5485
+ mkCommand(true, field.trueLabel ?? t2.t("elicitationYes"), field.trueLabel ? void 0 : "elicitationYes");
5486
+ mkCommand(false, field.falseLabel ?? t2.t("elicitationNo"), field.falseLabel ? void 0 : "elicitationNo");
5487
+ wrap.appendChild(list);
5488
+ return {
5489
+ el: wrap,
5490
+ // Never read: the click settles, so nothing downstream asks this shape for
5491
+ // a value. `false` rather than a throw keeps the field total.
5492
+ getValue: () => false,
5493
+ isComplete: () => false,
5494
+ focus: () => list.querySelector(".aparte-elic-option--command")?.focus(),
5495
+ offersSubmit: () => false
5496
+ };
5497
+ }
5389
5498
  const mk = (val, label, defaulted) => {
5390
- const row = el$1("label", "aparte-elic-option");
5391
- const control = el$1("input", "aparte-elic-control");
5499
+ const row = el$1("label", "aparte-field-choice aparte-elic-option");
5500
+ const control = el$1("input", "aparte-radio aparte-elic-control");
5392
5501
  control.type = "radio";
5393
5502
  control.name = name;
5394
5503
  control.value = val;
@@ -5418,7 +5527,7 @@ function buildBooleanField(field, onChange, fallbackLabel) {
5418
5527
  function buildStringField(field, onChange, fallbackLabel) {
5419
5528
  const wrap = el$1("div", "aparte-elic-field aparte-elic-string");
5420
5529
  fieldHeader(wrap, field, fallbackLabel);
5421
- const input = field.multiline ? el$1("textarea", "aparte-elic-text") : el$1("input", "aparte-elic-text");
5530
+ const input = field.multiline ? el$1("textarea", "aparte-field aparte-field--textarea aparte-elic-text") : el$1("input", "aparte-field aparte-elic-text");
5422
5531
  if (!field.multiline) input.type = "text";
5423
5532
  if (field.placeholder) input.setAttribute("placeholder", field.placeholder);
5424
5533
  if (field.default) input.value = field.default;
@@ -5446,7 +5555,7 @@ function buildStringField(field, onChange, fallbackLabel) {
5446
5555
  focus: () => input.focus()
5447
5556
  };
5448
5557
  }
5449
- function buildField(field, onChange, fallbackLabel, key) {
5558
+ function buildField(field, onChange, fallbackLabel, key, settle) {
5450
5559
  const custom = contextConfig().getElicitationFieldRenderer()?.(field, { key, notifyChange: onChange });
5451
5560
  if (custom) {
5452
5561
  return {
@@ -5458,9 +5567,10 @@ function buildField(field, onChange, fallbackLabel, key) {
5458
5567
  }
5459
5568
  switch (field.type) {
5460
5569
  case "enum":
5461
- return buildEnumField(field, onChange, fallbackLabel);
5570
+ return buildEnumField(field, onChange, fallbackLabel, settle);
5462
5571
  case "boolean":
5463
- return buildBooleanField(field, onChange, fallbackLabel);
5572
+ return buildBooleanField(field, onChange, fallbackLabel, settle);
5573
+ // No `settle`: text is written, not chosen, so it always needs a commit.
5464
5574
  case "string":
5465
5575
  return buildStringField(field, onChange, fallbackLabel);
5466
5576
  }
@@ -5482,6 +5592,16 @@ function buildPanel(message, schema, onChange) {
5482
5592
  const panel = el$1("div", "aparte-elic-panel");
5483
5593
  const asked = typeof message === "function" ? message() : message;
5484
5594
  if (asked) panel.appendChild(el$1("p", "aparte-elic-message", asked));
5595
+ let settleCb = null;
5596
+ let settled = false;
5597
+ const settle = (content) => {
5598
+ if (settled) return;
5599
+ settled = true;
5600
+ settleCb?.(content);
5601
+ };
5602
+ const onSettle = (cb) => {
5603
+ settleCb = cb;
5604
+ };
5485
5605
  const body = el$1("div", "aparte-elic-body");
5486
5606
  panel.appendChild(body);
5487
5607
  const dismiss = el$1("div", "aparte-elic-dismiss");
@@ -5522,15 +5642,20 @@ function buildPanel(message, schema, onChange) {
5522
5642
  mode: () => "submit",
5523
5643
  canProceed: isComplete,
5524
5644
  proceed: () => {
5525
- }
5645
+ },
5646
+ // A form never settles from inside: its questions are collected and
5647
+ // submitted together, and answering the last one on the click would be
5648
+ // WCAG's F36 word for word. Registered so the shape is total; never fired.
5649
+ onSettle
5526
5650
  };
5527
5651
  if (fields.length < 2 || cfg.getElicitationOptions().layout !== "stepped") return api;
5528
5652
  panel.classList.add("aparte-elic-panel--stepped");
5529
5653
  let current = 0;
5530
- const steps = el$1("div", "aparte-elic-steps");
5654
+ const scope = `aparte-elic-${++steppedPanelSeq}`;
5655
+ const steps = el$1("div", "aparte-tabs aparte-tabs--underline aparte-elic-steps");
5531
5656
  steps.setAttribute("role", "tablist");
5532
5657
  const chips = fields.map((f, i) => {
5533
- const chip = el$1("button", "aparte-elic-step");
5658
+ const chip = el$1("button", "aparte-tabs__tab aparte-elic-step");
5534
5659
  chip.type = "button";
5535
5660
  chip.setAttribute("role", "tab");
5536
5661
  chip.textContent = f.header?.trim() || String(i + 1);
@@ -5538,6 +5663,22 @@ function buildPanel(message, schema, onChange) {
5538
5663
  show(i);
5539
5664
  f.field.focus();
5540
5665
  });
5666
+ chip.id = `${scope}-tab-${i}`;
5667
+ f.field.el.id = `${scope}-panel-${i}`;
5668
+ f.field.el.setAttribute("role", "tabpanel");
5669
+ f.field.el.setAttribute("aria-labelledby", chip.id);
5670
+ chip.setAttribute("aria-controls", f.field.el.id);
5671
+ chip.addEventListener("keydown", (e) => {
5672
+ const step = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0;
5673
+ let next = -1;
5674
+ if (step) next = (i + step + fields.length) % fields.length;
5675
+ else if (e.key === "Home") next = 0;
5676
+ else if (e.key === "End") next = fields.length - 1;
5677
+ if (next < 0) return;
5678
+ e.preventDefault();
5679
+ show(next);
5680
+ chips[next]?.focus();
5681
+ });
5541
5682
  steps.appendChild(chip);
5542
5683
  return chip;
5543
5684
  });
@@ -5545,6 +5686,7 @@ function buildPanel(message, schema, onChange) {
5545
5686
  syncNav = () => {
5546
5687
  chips.forEach((chip, i) => {
5547
5688
  chip.setAttribute("aria-selected", String(i === current));
5689
+ chip.tabIndex = i === current ? 0 : -1;
5548
5690
  chip.toggleAttribute("data-answered", fields[i].field.isComplete());
5549
5691
  });
5550
5692
  };
@@ -5565,7 +5707,7 @@ function buildPanel(message, schema, onChange) {
5565
5707
  }
5566
5708
  };
5567
5709
  }
5568
- const field = buildField(schema, onChange, asked);
5710
+ const field = buildField(schema, onChange, asked, void 0, settle);
5569
5711
  body.appendChild(field.el);
5570
5712
  return {
5571
5713
  el: panel,
@@ -5573,10 +5715,11 @@ function buildPanel(message, schema, onChange) {
5573
5715
  getContent: () => field.getValue(),
5574
5716
  isComplete: () => field.isComplete(),
5575
5717
  focus: () => field.focus(),
5576
- mode: () => "submit",
5718
+ mode: () => field.offersSubmit?.() ?? true ? "submit" : "none",
5577
5719
  canProceed: () => field.isComplete(),
5578
5720
  proceed: () => {
5579
- }
5721
+ },
5722
+ onSettle
5580
5723
  };
5581
5724
  }
5582
5725
  function el(tag, className, text) {
@@ -5606,7 +5749,8 @@ function buildApprovalPanel(message, options, onChange) {
5606
5749
  const labelled = [];
5607
5750
  const textOf = (option) => typeof option.label === "function" ? option.label() : option.label;
5608
5751
  for (const option of options) {
5609
- const button = el("button", `aparte-approval-option aparte-approval-option--${option.tone ?? "affirm"}`);
5752
+ const tone = option.tone ?? "affirm";
5753
+ const button = el("button", `aparte-btn aparte-btn--block aparte-btn--surface aparte-approval-option aparte-approval-option--${tone}`);
5610
5754
  button.type = "button";
5611
5755
  button.textContent = textOf(option);
5612
5756
  button.addEventListener("click", () => fire({ option: option.value }));
@@ -5710,6 +5854,27 @@ class AparteClient {
5710
5854
  }
5711
5855
  this._setupListeners();
5712
5856
  }
5857
+ /**
5858
+ * What every fresh user action does before it starts a turn.
5859
+ *
5860
+ * Clearing the abort flag has to happen at the START of the action rather than inside
5861
+ * `_streamTurn`, which runs after the auth and file-reading awaits — a reset there
5862
+ * erased an abort that arrived while the user was still waiting for a large attachment
5863
+ * to be read.
5864
+ *
5865
+ * Cancelling the previous turn's tool controllers used to happen only on `aparte-send`.
5866
+ * Retry and edit reset the flag and left them running, so a tool handler from the turn
5867
+ * being superseded stayed alive — with its timeout still counting — while its reply was
5868
+ * already off the active path. A cold audit found the asymmetry; three callers, one
5869
+ * rule, no third copy to fall out of step.
5870
+ */
5871
+ _beginUserTurn() {
5872
+ this._isAborted = false;
5873
+ for (const controller of this._activeToolControllers) {
5874
+ controller.abort();
5875
+ }
5876
+ this._activeToolControllers.clear();
5877
+ }
5713
5878
  /**
5714
5879
  * Sets up the event listeners.
5715
5880
  * This is called once in the constructor.
@@ -5720,11 +5885,7 @@ class AparteClient {
5720
5885
  const event = e;
5721
5886
  if (event.type !== "aparte-send") return;
5722
5887
  if (!this._isForThisInstance(event)) return;
5723
- this._isAborted = false;
5724
- for (const controller of this._activeToolControllers) {
5725
- controller.abort();
5726
- }
5727
- this._activeToolControllers.clear();
5888
+ this._beginUserTurn();
5728
5889
  void this._handleSend(event);
5729
5890
  };
5730
5891
  }
@@ -6074,7 +6235,7 @@ ${summary}`,
6074
6235
  }
6075
6236
  async _handleRetry(event) {
6076
6237
  const { messageId, targetId } = event.detail ?? {};
6077
- this._isAborted = false;
6238
+ this._beginUserTurn();
6078
6239
  if (!messageId) return;
6079
6240
  const targetElement = this._resolveTarget(targetId);
6080
6241
  if (!targetElement) {
@@ -6101,9 +6262,7 @@ ${summary}`,
6101
6262
  if (!provider) return;
6102
6263
  const authConfig = await this._resolveAuth(providerId);
6103
6264
  const chatMessages = this._messagesToChatMessages(historyMessages);
6104
- const systemMessages = [];
6105
- const userSystemPrompt = this._config.resolveSystemPrompt();
6106
- if (userSystemPrompt) systemMessages.push({ role: "system", content: userSystemPrompt });
6265
+ const systemMessages = this._systemMessages();
6107
6266
  await this._streamTurn(
6108
6267
  targetElement,
6109
6268
  newMessageId,
@@ -6114,13 +6273,30 @@ ${summary}`,
6114
6273
  { temperature: 0.4 }
6115
6274
  );
6116
6275
  }
6276
+ /**
6277
+ * The system messages a turn opens with: the app's own template, then the registered
6278
+ * tools' `systemPrompt`s.
6279
+ *
6280
+ * A helper rather than three copies. The two lines it replaces were written out at each
6281
+ * of the three turn entry points (send, retry, edit), which is exactly how the tool half
6282
+ * would have been added to two of them and forgotten in the third — the same shape as
6283
+ * the abort-flag asymmetry `_beginUserTurn` exists to prevent.
6284
+ */
6285
+ _systemMessages() {
6286
+ const messages = [];
6287
+ const appPrompt = this._config.resolveSystemPrompt();
6288
+ if (appPrompt) messages.push({ role: "system", content: appPrompt });
6289
+ const toolPrompts = this._config.resolveToolSystemPrompts();
6290
+ if (toolPrompts) messages.push({ role: "system", content: toolPrompts });
6291
+ return messages;
6292
+ }
6117
6293
  /**
6118
6294
  * Handle aparte-edit — update the user message in place, truncate all subsequent
6119
6295
  * messages, then re-stream a fresh assistant response.
6120
6296
  */
6121
6297
  async _handleEdit(event) {
6122
6298
  const { messageId, content: newContent, targetId } = event.detail ?? {};
6123
- this._isAborted = false;
6299
+ this._beginUserTurn();
6124
6300
  if (!messageId || newContent === void 0) return;
6125
6301
  const targetElement = this._resolveTarget(targetId);
6126
6302
  if (!targetElement) {
@@ -6140,9 +6316,7 @@ ${summary}`,
6140
6316
  }
6141
6317
  const historyMessages = editIdx >= 0 ? allMessages.slice(0, editIdx + 1) : allMessages;
6142
6318
  const chatMessages = this._messagesToChatMessages(historyMessages);
6143
- const systemMessages = [];
6144
- const userSystemPrompt = this._config.resolveSystemPrompt();
6145
- if (userSystemPrompt) systemMessages.push({ role: "system", content: userSystemPrompt });
6319
+ const systemMessages = this._systemMessages();
6146
6320
  const config = this._config.getModelConfig();
6147
6321
  const providerId = config.defaultProvider;
6148
6322
  if (!providerId) return;
@@ -6311,11 +6485,7 @@ ${summary}`,
6311
6485
  * Build the initial messages array, prepending system prompts and conversation history.
6312
6486
  */
6313
6487
  _buildMessages(userContent, target, parts) {
6314
- const messages = [];
6315
- const userSystemPrompt = this._config.resolveSystemPrompt();
6316
- if (userSystemPrompt) {
6317
- messages.push({ role: "system", content: userSystemPrompt });
6318
- }
6488
+ const messages = this._systemMessages();
6319
6489
  const historyOption = this.options.history ?? "viewport";
6320
6490
  const viewportMessages = target?.getMessages?.() ?? [];
6321
6491
  if (historyOption === "viewport") {
@@ -6771,6 +6941,21 @@ ${content}
6771
6941
  ...wholeParser.parse(response).segments,
6772
6942
  ...wholeParser.finalize()
6773
6943
  ];
6944
+ const stringHint = baseRequest._meta?.artifactHint;
6945
+ if (stringHint) {
6946
+ const codeIdx = parsed.findIndex((s) => s.type === "code");
6947
+ if (codeIdx !== -1) {
6948
+ const codeSeg = parsed[codeIdx];
6949
+ parsed[codeIdx] = {
6950
+ id: codeSeg.id,
6951
+ type: "artifact",
6952
+ mimeType: stringHint.mimeType,
6953
+ artifactType: stringHint.kind,
6954
+ title: codeSeg.filename ?? stringHint.kind,
6955
+ content: codeSeg.content
6956
+ };
6957
+ }
6958
+ }
6774
6959
  if (parsed.length > 0) {
6775
6960
  for (const segment of parsed) targetElement.addSegment?.(segment);
6776
6961
  this._updateMessage(targetElement, messageId, { status: "completed" });
@@ -7107,7 +7292,7 @@ ${content}
7107
7292
  details: error.code
7108
7293
  };
7109
7294
  if (target.updateMessage) {
7110
- const rendered = target.getMessages?.()?.find((m) => m.id === messageId)?.segments ?? [];
7295
+ const rendered = target.getMessage?.(messageId)?.segments ?? target.getMessages?.()?.find((m) => m.id === messageId)?.segments ?? [];
7111
7296
  target.updateMessage(messageId, {
7112
7297
  status: "error",
7113
7298
  segments: [...rendered, errorSegment]
@@ -7464,13 +7649,14 @@ export {
7464
7649
  unregisterSegmentRenderer as a5,
7465
7650
  uuid as a6,
7466
7651
  mergeSegmentUpdate as a7,
7467
- stampSegmentActivity as a8,
7468
- stampSegmentOnInsert as a9,
7469
- stampSegmentOnUpdate as aa,
7470
- renumberSegments as ab,
7471
- openSegmentIds as ac,
7472
- isTerminalStatus as ad,
7473
- adoptMessageSegments as ae,
7652
+ writeStreamedMarkdown as a8,
7653
+ stampSegmentActivity as a9,
7654
+ stampSegmentOnInsert as aa,
7655
+ stampSegmentOnUpdate as ab,
7656
+ renumberSegments as ac,
7657
+ openSegmentIds as ad,
7658
+ isTerminalStatus as ae,
7659
+ adoptMessageSegments as af,
7474
7660
  APARTE_DEFAULT_BUBBLE_ACTIONS as b,
7475
7661
  APARTE_DEFAULT_HOST_HANDLERS as c,
7476
7662
  APARTE_DEFAULT_ICON_FALLBACKS as d,
@@ -7497,4 +7683,4 @@ export {
7497
7683
  buildElicitationPanel as y,
7498
7684
  collectRendererStyles as z
7499
7685
  };
7500
- //# sourceMappingURL=is-awaiting-reply-BWTDvQ__.js.map
7686
+ //# sourceMappingURL=is-awaiting-reply-DrO_oR32.js.map