@aparte/core 0.9.0 → 0.11.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 (109) hide show
  1. package/dist/client/aparte-client.d.ts +54 -33
  2. package/dist/client/aparte-client.d.ts.map +1 -1
  3. package/dist/client/stream-adapter.d.ts +6 -1
  4. package/dist/client/stream-adapter.d.ts.map +1 -1
  5. package/dist/components/bubble/aparte-chat-bubble.d.ts +49 -7
  6. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
  7. package/dist/components/chat/aparte-chat.d.ts +8 -4
  8. package/dist/components/chat/aparte-chat.d.ts.map +1 -1
  9. package/dist/components/composer/aparte-composer-action.d.ts +5 -5
  10. package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
  11. package/dist/components/composer/aparte-composer-add-attachment.d.ts +16 -4
  12. package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
  13. package/dist/components/composer/aparte-composer-attachments.d.ts +5 -3
  14. package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
  15. package/dist/components/composer/aparte-composer-cancel.d.ts +10 -2
  16. package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
  17. package/dist/components/composer/aparte-composer-input.d.ts +8 -5
  18. package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
  19. package/dist/components/composer/aparte-composer-send.d.ts +34 -2
  20. package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
  21. package/dist/components/composer/aparte-composer-toolbar.d.ts +3 -3
  22. package/dist/components/composer/aparte-composer.d.ts +54 -10
  23. package/dist/components/composer/aparte-composer.d.ts.map +1 -1
  24. package/dist/components/conversation-list/aparte-conversation-list.d.ts +19 -12
  25. package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
  26. package/dist/components/elicitation/aparte-elicitation.d.ts +22 -0
  27. package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
  28. package/dist/components/status/aparte-chat-status.d.ts +9 -0
  29. package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
  30. package/dist/components/viewport/aparte-chat-viewport.d.ts +18 -5
  31. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  32. package/dist/config/aparte-config.d.ts +54 -2
  33. package/dist/config/aparte-config.d.ts.map +1 -1
  34. package/dist/config/config-subscribe.d.ts +4 -0
  35. package/dist/config/config-subscribe.d.ts.map +1 -0
  36. package/dist/config/index.d.ts +1 -0
  37. package/dist/config/index.d.ts.map +1 -1
  38. package/dist/config/locale.d.ts +141 -0
  39. package/dist/config/locale.d.ts.map +1 -1
  40. package/dist/config/sanitize.d.ts.map +1 -1
  41. package/dist/custom-elements.json +5905 -4694
  42. package/dist/elicitation/approval-panel.d.ts +42 -0
  43. package/dist/elicitation/approval-panel.d.ts.map +1 -0
  44. package/dist/elicitation/index.d.ts +4 -1
  45. package/dist/elicitation/index.d.ts.map +1 -1
  46. package/dist/elicitation/panel.d.ts +14 -1
  47. package/dist/elicitation/panel.d.ts.map +1 -1
  48. package/dist/elicitation/types.d.ts +116 -9
  49. package/dist/elicitation/types.d.ts.map +1 -1
  50. package/dist/generated/element-attributes.d.ts +173 -0
  51. package/dist/generated/element-attributes.d.ts.map +1 -0
  52. package/dist/host/aparte-chat-host.d.ts +3 -1
  53. package/dist/host/aparte-chat-host.d.ts.map +1 -1
  54. package/dist/index.css +752 -392
  55. package/dist/index.d.ts +5 -4
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +538 -290
  58. package/dist/index.js.map +1 -1
  59. package/dist/index.node.d.ts +5 -4
  60. package/dist/index.node.d.ts.map +1 -1
  61. package/dist/index.node.js +62 -57
  62. package/dist/index.node.js.map +1 -1
  63. package/dist/interop/element-props.d.ts +26 -1
  64. package/dist/interop/element-props.d.ts.map +1 -1
  65. package/dist/{index-hQQljwfX.js → is-awaiting-reply-Cb7H3YfN.js} +1159 -972
  66. package/dist/is-awaiting-reply-Cb7H3YfN.js.map +1 -0
  67. package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
  68. package/dist/primitives/select/aparte-optgroup.d.ts +3 -0
  69. package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
  70. package/dist/primitives/select/aparte-option.d.ts +1 -0
  71. package/dist/primitives/select/aparte-option.d.ts.map +1 -1
  72. package/dist/primitives/select/aparte-select.d.ts +22 -18
  73. package/dist/primitives/select/aparte-select.d.ts.map +1 -1
  74. package/dist/renderers/highlight-stream.d.ts +17 -0
  75. package/dist/renderers/highlight-stream.d.ts.map +1 -0
  76. package/dist/renderers/segment-renderers.d.ts.map +1 -1
  77. package/dist/renderers/segments/artifact/binary-file.d.ts.map +1 -1
  78. package/dist/renderers/segments/artifact/card.d.ts.map +1 -1
  79. package/dist/renderers/segments/artifact/shared.d.ts +19 -2
  80. package/dist/renderers/segments/artifact/shared.d.ts.map +1 -1
  81. package/dist/renderers/segments/code.d.ts.map +1 -1
  82. package/dist/renderers/segments/error.d.ts.map +1 -1
  83. package/dist/renderers/segments/pipeline-waiting.d.ts.map +1 -1
  84. package/dist/renderers/segments/thinking.d.ts.map +1 -1
  85. package/dist/renderers/segments/tool-call.d.ts.map +1 -1
  86. package/dist/types/element-attributes.d.ts +42 -0
  87. package/dist/types/element-attributes.d.ts.map +1 -0
  88. package/dist/types/event-map.d.ts +2 -4
  89. package/dist/types/event-map.d.ts.map +1 -1
  90. package/dist/types/events.d.ts +0 -22
  91. package/dist/types/events.d.ts.map +1 -1
  92. package/dist/types/index.d.ts +4 -3
  93. package/dist/types/index.d.ts.map +1 -1
  94. package/dist/types/models.d.ts +1 -6
  95. package/dist/types/models.d.ts.map +1 -1
  96. package/dist/types/segments.d.ts +87 -74
  97. package/dist/types/segments.d.ts.map +1 -1
  98. package/dist/types/tools.d.ts +15 -21
  99. package/dist/types/tools.d.ts.map +1 -1
  100. package/dist/utils/segments.d.ts +83 -5
  101. package/dist/utils/segments.d.ts.map +1 -1
  102. package/package.json +3 -2
  103. package/dist/index-hQQljwfX.js.map +0 -1
  104. package/dist/renderers/segments/file-tree.d.ts +0 -3
  105. package/dist/renderers/segments/file-tree.d.ts.map +0 -1
  106. package/dist/renderers/segments/progress.d.ts +0 -3
  107. package/dist/renderers/segments/progress.d.ts.map +0 -1
  108. package/dist/renderers/segments/terminal.d.ts +0 -3
  109. package/dist/renderers/segments/terminal.d.ts.map +0 -1
package/dist/index.css CHANGED
@@ -22,7 +22,6 @@
22
22
  --aparte-surface-2: #f1ebdf;
23
23
  /* Code blocks, headers */
24
24
  --aparte-surface-3: #e7dfce;
25
- /* Skeletons, disabled */
26
25
 
27
26
  /* Text */
28
27
  --aparte-text: #241f2b;
@@ -31,7 +30,6 @@
31
30
 
32
31
  /* Borders */
33
32
  --aparte-border: #e5ddce;
34
- --aparte-border-focus: var(--aparte-primary);
35
33
  --aparte-border-width: 1px;
36
34
 
37
35
  /* Status Colors */
@@ -49,18 +47,6 @@
49
47
  --aparte-radius-full: 9999px;
50
48
 
51
49
  /* Component-specific radius (overridable per app) */
52
- --aparte-radius-input: var(--aparte-radius-lg);
53
- --aparte-radius-input-footer: var(--aparte-radius-lg);
54
- --aparte-radius-send-btn: var(--aparte-radius-md);
55
- --aparte-radius-action-btn: var(--aparte-radius-sm);
56
- --aparte-radius-select: var(--aparte-radius-md);
57
- --aparte-radius-avatar: var(--aparte-radius-md);
58
- --aparte-radius-bubble: var(--aparte-radius-lg);
59
- --aparte-radius-code: var(--aparte-radius-lg);
60
- --aparte-radius-thinking: var(--aparte-radius-lg);
61
- --aparte-radius-terminal: var(--aparte-radius-lg);
62
- --aparte-radius-error: var(--aparte-radius-md);
63
- --aparte-radius-file-tree: var(--aparte-radius-lg);
64
50
 
65
51
  /* Spacing scale — every gap / padding / margin / offset routes through these,
66
52
  so there are no raw px spacings in the rules. Incidental, pixel-tuned values
@@ -100,8 +86,6 @@
100
86
 
101
87
  /* Text/icon over a --aparte-primary (brass) fill — send button, run button. */
102
88
  --aparte-on-primary: #ffffff;
103
- /* Focus ring — derived from the accent so it themes automatically. */
104
- --aparte-focus-ring: 0 0 0 3px color-mix(in srgb, var(--aparte-primary) 15%, transparent);
105
89
  /* Outline width for keyboard focus + drag-over indicators. */
106
90
  --aparte-focus-outline-width: 2px;
107
91
  /* Scrollbars rendered by the library. */
@@ -118,36 +102,21 @@
118
102
  no bubble background; consumers can flip these to get a chat-app look. */
119
103
  --aparte-message-content-bg-user: #efe7f6;
120
104
  --aparte-message-content-bg-assistant: transparent;
121
- --aparte-message-content-text-user: var(--aparte-text);
122
- --aparte-message-content-text-assistant: var(--aparte-text);
123
105
  --aparte-message-content-padding: 10px 14px;
124
106
  --aparte-message-content-radius: 14px;
125
107
 
126
108
  /* Aliases used by some segment renderers (artifact card, tool pill).
127
109
  Mapped to existing tokens so consumers that override --aparte-surface-1/2
128
110
  get cohesive results without touching every rule. */
129
- --aparte-surface: var(--aparte-surface-1);
130
- --aparte-surface-hover: var(--aparte-surface-2);
131
- --aparte-text-secondary: var(--aparte-text-muted);
132
- --aparte-accent: var(--aparte-primary);
133
- --aparte-success-border: var(--aparte-success);
134
- --aparte-success-surface: color-mix(in oklch, var(--aparte-success) 12%, transparent);
135
- --aparte-error-border-soft: var(--aparte-error);
136
- --aparte-error-surface: color-mix(in oklch, var(--aparte-error) 12%, transparent);
137
111
 
138
112
  /* Code typography — exposed so apps can swap in Geist Mono / Cascadia / … */
139
113
  --aparte-code-font-family: ui-monospace, 'Cascadia Code', 'JetBrains Mono', 'Geist Mono', SFMono-Regular, Consolas, 'Liberation Mono', monospace;
140
114
 
141
115
  /* Attachments */
142
- --aparte-attachment-chip-bg: var(--aparte-surface-2);
143
- --aparte-attachment-chip-border: var(--aparte-border);
144
- --aparte-attachment-chip-radius: var(--aparte-radius-sm);
145
- --aparte-attachment-chip-color: var(--aparte-text);
146
116
  --aparte-attachment-image-size: 72px;
147
117
  --aparte-attachments-max-height: 140px;
148
118
 
149
119
  /* Attachment thumbnail tile (composer preview + sent-message strip) */
150
- --aparte-thumb-radius: var(--aparte-radius-lg);
151
120
  --aparte-thumb-name-color: #ffffff;
152
121
  --aparte-thumb-name-scrim: linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0));
153
122
  --aparte-thumb-name-padding: 14px 5px 4px;
@@ -160,46 +129,27 @@
160
129
  /* Action Bar */
161
130
  --aparte-action-bar-gap: 4px;
162
131
  --aparte-action-bar-btn-size: 28px;
163
- --aparte-action-bar-btn-color: var(--aparte-text-muted);
164
- --aparte-action-bar-btn-hover-bg: var(--aparte-surface-2);
165
- --aparte-action-bar-btn-hover-color: var(--aparte-text);
166
132
 
167
133
  /* Branch Picker */
168
134
  --aparte-branch-picker-gap: 4px;
169
135
  --aparte-branch-picker-btn-size: 20px;
170
136
  --aparte-branch-picker-btn-icon-size: 16px;
171
- --aparte-branch-picker-btn-color: var(--aparte-text-muted);
172
- --aparte-branch-picker-btn-hover-color: var(--aparte-text);
173
137
  --aparte-branch-picker-label-size: 12px;
174
- --aparte-branch-picker-label-color: var(--aparte-text-muted);
175
138
  --aparte-branch-picker-label-min-width: 32px;
176
139
 
177
140
  /* Avatars */
178
141
  --aparte-avatar-size: 32px;
179
142
  --aparte-avatar-font-size: 14px;
180
- --aparte-avatar-radius: var(--aparte-radius-avatar);
181
- --aparte-avatar-bg-user: var(--aparte-primary);
182
- --aparte-avatar-text-user: var(--aparte-text-inverse);
183
- --aparte-avatar-bg-assistant: var(--aparte-surface-3);
184
- --aparte-avatar-text-assistant: var(--aparte-text-inverse);
185
143
  --aparte-avatar-image-user: none;
186
144
  --aparte-avatar-image-assistant: none;
187
145
  --aparte-avatar-image-size: 90%;
188
146
 
189
147
  /* Text Sizes */
190
148
  --aparte-name-font-size: 14px;
191
- --aparte-name-color: var(--aparte-text);
192
149
  --aparte-timestamp-font-size: 12px;
193
- --aparte-timestamp-color: var(--aparte-text-muted);
194
150
  --aparte-content-font-size: 15px;
195
- --aparte-content-color: var(--aparte-text);
196
151
 
197
152
  /* Input */
198
- --aparte-input-bg: var(--aparte-surface-1);
199
- --aparte-input-border: var(--aparte-border);
200
- --aparte-input-text: var(--aparte-text);
201
- --aparte-input-placeholder: var(--aparte-text-muted);
202
- --aparte-input-focus-border: var(--aparte-primary);
203
153
  --aparte-input-gap: 8px;
204
154
  --aparte-input-editor-font-size: 15px;
205
155
  --aparte-input-editor-max-height: 200px;
@@ -212,7 +162,6 @@
212
162
  --aparte-select-min-width: 120px;
213
163
 
214
164
  /* Status Indicator */
215
- --aparte-status-color: var(--aparte-text-muted);
216
165
  --aparte-status-bg: transparent;
217
166
  --aparte-status-dot-size: 6px;
218
167
 
@@ -221,11 +170,6 @@
221
170
  --aparte-waiting-dot-gap: 4px;
222
171
 
223
172
  /* Thinking Segment */
224
- --aparte-thinking-border: var(--aparte-border);
225
- --aparte-thinking-bg: var(--aparte-surface-2);
226
- --aparte-thinking-text: var(--aparte-text-muted);
227
- --aparte-thinking-content: var(--aparte-text);
228
- --aparte-thinking-content-bg: var(--aparte-surface-1);
229
173
  --aparte-thinking-content-max-height: 300px;
230
174
  --aparte-thinking-content-line-height: 1.55;
231
175
  --aparte-thinking-toggle-size: 9px;
@@ -248,6 +192,11 @@
248
192
  --aparte-elic-option-desc-size: 0.78rem;
249
193
  --aparte-elic-control-size: 15px;
250
194
  --aparte-elic-input-radius: 6px;
195
+ /* The tool-call row's corner. Declared here rather than left as an inline
196
+ fallback in the renderer: an undeclared custom property still WORKS, which is
197
+ what makes it dangerous — it is absent from the generated CSS reference, so a
198
+ consumer has no way to discover the knob exists. */
199
+ --aparte-tool-row-radius: 6px;
251
200
  --aparte-elic-textarea-min-height: 64px;
252
201
  --aparte-elic-skip-size: 0.8rem;
253
202
  --aparte-elic-step-size: 0.78rem;
@@ -265,49 +214,16 @@
265
214
  --aparte-elic-dismiss-room: 72px;
266
215
 
267
216
  /* Code Blocks */
268
- --aparte-code-bg: var(--aparte-surface-1);
269
- --aparte-code-header-bg: var(--aparte-surface-2);
270
- --aparte-code-border: var(--aparte-border);
271
- --aparte-code-header-text: var(--aparte-text-muted);
272
217
  --aparte-code-copy-hover-bg: rgba(0, 0, 0, 0.05);
273
218
  --aparte-code-copy-icon-size: 14px;
274
219
  --aparte-code-inline-font-size: 0.875em;
275
220
 
276
- /* Terminal */
277
- --aparte-terminal-bg: #1e293b;
278
- --aparte-terminal-text: #e2e8f0;
279
- --aparte-terminal-icon: #64748b;
280
- --aparte-terminal-hover: #334155;
281
- --aparte-terminal-running: #22c55e;
282
- --aparte-terminal-output-bg: #0f172a;
283
- --aparte-terminal-output-text: #94a3b8;
284
- --aparte-terminal-copy-btn-size: 26px;
285
- --aparte-terminal-font-size: 13px;
286
- --aparte-terminal-output-max-height: 200px;
287
-
288
- /* Progress */
289
- --aparte-progress-bar-bg: var(--aparte-surface-3);
290
- --aparte-progress-bar-fill: var(--aparte-primary);
291
- --aparte-progress-label: var(--aparte-text);
221
+
292
222
 
293
223
  /* Scroll-to-bottom button */
294
224
  --aparte-scroll-btn-size: 36px;
295
- --aparte-scroll-btn-bg: var(--aparte-surface-1);
296
- --aparte-scroll-btn-color: var(--aparte-text);
297
- --aparte-scroll-btn-border: var(--aparte-border);
298
225
  --aparte-scroll-btn-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
299
- --aparte-scroll-btn-hover-bg: var(--aparte-surface-2);
300
226
 
301
- /* File Tree */
302
- --aparte-file-tree-bg: var(--aparte-surface-1);
303
- --aparte-file-tree-border: var(--aparte-border);
304
- --aparte-file-tree-header-bg: var(--aparte-surface-2);
305
- --aparte-file-tree-hover: var(--aparte-surface-2);
306
- --aparte-file-tree-title: var(--aparte-text-muted);
307
- /* Git-status file colours reuse the semantic status tokens by default. */
308
- --aparte-file-status-added: var(--aparte-success);
309
- --aparte-file-status-modified: var(--aparte-info);
310
- --aparte-file-status-deleted: var(--aparte-error);
311
227
 
312
228
  /* Error segment */
313
229
  --aparte-error-bg: #fef2f2;
@@ -318,7 +234,7 @@
318
234
  --aparte-error-icon-size: 20px;
319
235
  --aparte-error-rail-width: 4px;
320
236
 
321
- /* Prose (segment-content) */
237
+ /* Prose (aparte-segment-content) */
322
238
  --aparte-prose-p-margin: 0.4em 0;
323
239
  --aparte-prose-h1-size: 1.4em;
324
240
  --aparte-prose-h2-size: 1.25em;
@@ -326,9 +242,7 @@
326
242
  --aparte-prose-list-indent: 1.5em;
327
243
  --aparte-prose-list-margin: 0.4em 0;
328
244
  --aparte-prose-li-margin: 0.15em 0;
329
- --aparte-prose-blockquote-border: var(--aparte-border);
330
245
  --aparte-prose-blockquote-border-width: 3px;
331
- --aparte-prose-code-bg: var(--aparte-surface-2);
332
246
 
333
247
  /* Viewport */
334
248
  --aparte-viewport-padding: 16px;
@@ -337,35 +251,20 @@
337
251
  bubble and the composer — this restores the symmetry rather than inventing a
338
252
  value. Set it to 0 for a flush composer (a full-bleed mobile shell with a docked
339
253
  keyboard is the legitimate case). */
340
- --aparte-chat-bottom-gap: var(--aparte-space-8, 16px);
341
254
 
342
- /* Content */
343
- --aparte-content-line-height: var(--aparte-line-height-loose, 1.7);
344
- --aparte-cursor-color: var(--aparte-primary);
345
255
 
346
256
  /* Status */
347
257
  --aparte-status-font-size: 13px;
348
258
 
349
259
  /* Input (composer) */
350
260
  --aparte-input-font-size: 14px;
351
- --aparte-input-line-height: var(--aparte-line-height-normal, 1.5);
352
261
  --aparte-input-padding-x: 12px;
353
262
  --aparte-input-padding-y: 10px;
354
263
 
355
264
  /* Conversation List */
356
265
  --aparte-conv-list-gap: 2px;
357
266
  --aparte-conv-item-padding: 7px 10px;
358
- --aparte-conv-item-radius: var(--aparte-radius-md);
359
- --aparte-conv-item-color: var(--aparte-text-muted);
360
- --aparte-conv-item-color-active: var(--aparte-text);
361
- --aparte-conv-item-bg-hover: var(--aparte-surface-3);
362
- --aparte-conv-item-bg-active: var(--aparte-surface-3);
363
267
  --aparte-conv-item-font-size: 0.8125rem;
364
- --aparte-conv-item-font-weight-active: var(--aparte-font-weight-medium, 500);
365
- --aparte-conv-delete-color: var(--aparte-text-muted);
366
- --aparte-conv-delete-bg-hover: var(--aparte-error);
367
- --aparte-conv-delete-color-hover: var(--aparte-text-inverse);
368
- --aparte-conv-delete-radius: var(--aparte-radius-sm);
369
268
  --aparte-conv-item-gap: 6px;
370
269
  --aparte-conv-action-btn-size: 20px;
371
270
  }
@@ -389,9 +288,6 @@
389
288
  --aparte-text: #f2eee7;
390
289
  --aparte-text-muted: #a89bb6;
391
290
  --aparte-text-inverse: #17141c;
392
- --aparte-name-color: #f2eee7;
393
- --aparte-content-color: #ece8e2;
394
- --aparte-timestamp-color: #7a7088;
395
291
 
396
292
  /* Borders */
397
293
  --aparte-border: #342a40;
@@ -400,39 +296,8 @@
400
296
  --aparte-primary: #d9a24b;
401
297
  --aparte-primary-hover: #e7c588;
402
298
 
403
- /* Input */
404
- --aparte-input-bg: #211b28;
405
- --aparte-input-border: #342a40;
406
- --aparte-input-text: #f2eee7;
407
- --aparte-input-placeholder: #7a7088;
408
-
409
- /* Status */
410
- --aparte-status-color: #94a3b8;
411
-
412
- /* Thinking */
413
- --aparte-thinking-border: #334155;
414
- --aparte-thinking-bg: #1e293b;
415
- --aparte-thinking-text: #94a3b8;
416
- --aparte-thinking-content: #e5e7eb;
417
- --aparte-thinking-content-bg: #0f172a;
418
-
419
- /* Code */
420
- --aparte-code-bg: #1e293b;
421
- --aparte-code-header-bg: #334155;
422
- --aparte-code-border: #475569;
423
- --aparte-code-header-text: #94a3b8;
424
-
425
- /* Progress */
426
- --aparte-progress-bar-bg: #334155;
299
+ /* Scroll-to-bottom button */
427
300
  --aparte-scroll-btn-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
428
- --aparte-progress-label: #e5e7eb;
429
-
430
- /* File Tree */
431
- --aparte-file-tree-bg: #1e293b;
432
- --aparte-file-tree-border: #334155;
433
- --aparte-file-tree-header-bg: #0f172a;
434
- --aparte-file-tree-hover: #334155;
435
- --aparte-file-tree-title: #94a3b8;
436
301
 
437
302
  /* Error segment */
438
303
  --aparte-error-bg: #2d1b1b;
@@ -440,15 +305,150 @@
440
305
  --aparte-error-solid: #f87171;
441
306
  --aparte-error-text: #fca5a5;
442
307
  --aparte-error-title: #fecaca;
308
+ }
443
309
 
444
- /* Prose */
445
- --aparte-prose-blockquote-border: #475569;
446
- --aparte-prose-code-bg: #334155;
310
+ /* =========================================
311
+ Derived layer — re-anchored, on purpose
312
+
313
+ Every value below READS another variable. A custom property is substituted
314
+ where it is DECLARED, so a derived value declared only on `:root` is computed
315
+ once against the root palette and merely inherits after that: overriding
316
+ `--aparte-primary` on a subtree moved the send button and nothing else, and
317
+ `[data-aparte-theme="dark"]` — which overrides eight masters — left every
318
+ derived value on its light substitution.
319
+
320
+ So the layer is declared again at each anchor where a palette can change:
321
+ the document root, a shadow host, any themed subtree, any aparté host, and
322
+ the `aparte-chat` element itself (per-instance theming). Substitution then
323
+ re-runs there against that element's own masters.
324
+
325
+ Only DERIVED values may live here. The literal palette stays in the block
326
+ above with `:root, :host` alone — widening THAT list would re-declare the
327
+ light literals on an `<aparte-chat>` nested in a dark wrapper, where a local
328
+ declaration beats the inherited dark value, and the chat would go light.
329
+ `scripts/check-derived-vars.mjs` holds both halves of that rule.
330
+ ========================================= */
331
+ :root,
332
+ :host,
333
+ [data-aparte-theme],
334
+ [data-aparte-host],
335
+ aparte-chat {
336
+ /* Borders */
337
+ --aparte-border-focus: var(--aparte-primary);
447
338
 
448
- /* Conversation List (dark overrides) */
449
- --aparte-conv-item-bg-hover: #334155;
450
- --aparte-conv-item-bg-active: #334155;
451
- --aparte-conv-delete-color: #64748b;
339
+ /* Component-specific radius (overridable per app) */
340
+ --aparte-radius-input: var(--aparte-radius-lg);
341
+ --aparte-radius-input-footer: var(--aparte-radius-lg);
342
+ --aparte-radius-send-btn: var(--aparte-radius-md);
343
+ --aparte-radius-action-btn: var(--aparte-radius-sm);
344
+ --aparte-radius-select: var(--aparte-radius-md);
345
+ --aparte-radius-avatar: var(--aparte-radius-md);
346
+ --aparte-radius-bubble: var(--aparte-radius-lg);
347
+ --aparte-radius-code: var(--aparte-radius-lg);
348
+ --aparte-radius-thinking: var(--aparte-radius-lg);
349
+ --aparte-radius-error: var(--aparte-radius-md);
350
+
351
+ /* Focus ring — derived from the accent so it themes automatically. */
352
+ --aparte-focus-ring: 0 0 0 3px color-mix(in srgb, var(--aparte-primary) 15%, transparent);
353
+
354
+ /* Messages */
355
+ --aparte-message-content-text-user: var(--aparte-text);
356
+ --aparte-message-content-text-assistant: var(--aparte-text);
357
+ --aparte-surface: var(--aparte-surface-1);
358
+ --aparte-surface-hover: var(--aparte-surface-2);
359
+ --aparte-text-secondary: var(--aparte-text-muted);
360
+ --aparte-accent: var(--aparte-primary);
361
+ --aparte-success-border: var(--aparte-success);
362
+ --aparte-success-surface: color-mix(in oklch, var(--aparte-success) 12%, transparent);
363
+ --aparte-error-border-soft: var(--aparte-error);
364
+ --aparte-error-surface: color-mix(in oklch, var(--aparte-error) 12%, transparent);
365
+
366
+ /* Attachments */
367
+ --aparte-attachment-chip-bg: var(--aparte-surface-2);
368
+ --aparte-attachment-chip-border: var(--aparte-border);
369
+ --aparte-attachment-chip-radius: var(--aparte-radius-sm);
370
+ --aparte-attachment-chip-color: var(--aparte-text);
371
+
372
+ /* Attachment thumbnail tile (composer preview + sent-message strip) */
373
+ --aparte-thumb-radius: var(--aparte-radius-lg);
374
+
375
+ /* Action Bar */
376
+ --aparte-action-bar-btn-color: var(--aparte-text-muted);
377
+ --aparte-action-bar-btn-hover-bg: var(--aparte-surface-2);
378
+ --aparte-action-bar-btn-hover-color: var(--aparte-text);
379
+
380
+ /* Branch Picker */
381
+ --aparte-branch-picker-btn-color: var(--aparte-text-muted);
382
+ --aparte-branch-picker-btn-hover-color: var(--aparte-text);
383
+ --aparte-branch-picker-label-color: var(--aparte-text-muted);
384
+
385
+ /* Avatars */
386
+ --aparte-avatar-radius: var(--aparte-radius-avatar);
387
+ --aparte-avatar-bg-user: var(--aparte-primary);
388
+ --aparte-avatar-text-user: var(--aparte-text-inverse);
389
+ --aparte-avatar-bg-assistant: var(--aparte-surface-3);
390
+ --aparte-avatar-text-assistant: var(--aparte-text-inverse);
391
+
392
+ /* Text Sizes */
393
+ --aparte-name-color: var(--aparte-text);
394
+ --aparte-timestamp-color: var(--aparte-text-muted);
395
+ --aparte-content-color: var(--aparte-text);
396
+
397
+ /* Input */
398
+ --aparte-input-bg: var(--aparte-surface-1);
399
+ --aparte-input-border: var(--aparte-border);
400
+ --aparte-input-text: var(--aparte-text);
401
+ --aparte-input-placeholder: var(--aparte-text-muted);
402
+ --aparte-input-focus-border: var(--aparte-primary);
403
+
404
+ /* Status Indicator */
405
+ --aparte-status-color: var(--aparte-text-muted);
406
+
407
+ /* Thinking Segment */
408
+ --aparte-thinking-border: var(--aparte-border);
409
+ --aparte-thinking-bg: var(--aparte-surface-2);
410
+ --aparte-thinking-text: var(--aparte-text-muted);
411
+ --aparte-thinking-content: var(--aparte-text);
412
+ --aparte-thinking-content-bg: var(--aparte-surface-1);
413
+
414
+ /* Code Blocks */
415
+ --aparte-code-bg: var(--aparte-surface-1);
416
+ --aparte-code-header-bg: var(--aparte-surface-2);
417
+ --aparte-code-border: var(--aparte-border);
418
+ --aparte-code-header-text: var(--aparte-text-muted);
419
+
420
+
421
+ /* Scroll-to-bottom button */
422
+ --aparte-scroll-btn-bg: var(--aparte-surface-1);
423
+ --aparte-scroll-btn-color: var(--aparte-text);
424
+ --aparte-scroll-btn-border: var(--aparte-border);
425
+ --aparte-scroll-btn-hover-bg: var(--aparte-surface-2);
426
+
427
+ /* Prose (aparte-segment-content) */
428
+ --aparte-prose-blockquote-border: var(--aparte-border);
429
+ --aparte-prose-code-bg: var(--aparte-surface-2);
430
+
431
+ /* Viewport */
432
+ --aparte-chat-bottom-gap: var(--aparte-space-8, 16px);
433
+
434
+ /* Content */
435
+ --aparte-content-line-height: var(--aparte-line-height-loose, 1.7);
436
+ --aparte-cursor-color: var(--aparte-primary);
437
+
438
+ /* Input (composer) */
439
+ --aparte-input-line-height: var(--aparte-line-height-normal, 1.5);
440
+
441
+ /* Conversation List */
442
+ --aparte-conv-item-radius: var(--aparte-radius-md);
443
+ --aparte-conv-item-color: var(--aparte-text-muted);
444
+ --aparte-conv-item-color-active: var(--aparte-text);
445
+ --aparte-conv-item-bg-hover: var(--aparte-surface-3);
446
+ --aparte-conv-item-bg-active: var(--aparte-surface-3);
447
+ --aparte-conv-item-font-weight-active: var(--aparte-font-weight-medium, 500);
448
+ --aparte-conv-delete-color: var(--aparte-text-muted);
449
+ --aparte-conv-delete-bg-hover: var(--aparte-error);
450
+ --aparte-conv-delete-color-hover: var(--aparte-text-inverse);
451
+ --aparte-conv-delete-radius: var(--aparte-radius-sm);
452
452
  }
453
453
 
454
454
  /* =========================================
@@ -671,6 +671,91 @@ textarea.aparte-elic-text {
671
671
  z-index: 1;
672
672
  }
673
673
 
674
+ /* ── An approval: a decision, not a value ───────────────────────────────────
675
+ The options are buttons, not radios, because a decision has two primary actions
676
+ of opposite polarity and the composer's one button cannot carry both. So they
677
+ settle on the first click, and approving costs one gesture rather than two.
678
+
679
+ The colours are the ones the inline Approve / Reject pair used, moved here from
680
+ the runtime-injected `getStyles()` string in the tool_call renderer. That move is
681
+ the point: injected CSS is invisible to a consumer's own rules, absent from the
682
+ generated CSS reference, and unreachable by `check:derived-vars`. The elicitation
683
+ panel made the same move for the same reason. */
684
+ /* A COLUMN, not a row of pills.
685
+ The panel lives in a composer, which is narrow — two or three labels side by side
686
+ either wrap into an unreadable jumble or get squeezed. And the labels are not short:
687
+ a scope option reads "Approve, and always for this tool". A stacked list is also what
688
+ every reference implementation of this shows, for the same reason: one target per
689
+ line, full width, so the choice is read rather than aimed at. */
690
+ .aparte-approval-options {
691
+ display: flex;
692
+ flex-direction: column;
693
+ gap: var(--aparte-approval-gap, 4px);
694
+ margin: 0 6px;
695
+ }
696
+
697
+ .aparte-approval-option {
698
+ font: inherit;
699
+ font-size: var(--aparte-approval-option-size, 0.85rem);
700
+ font-weight: var(--aparte-font-weight-medium, 500);
701
+ line-height: 1.35;
702
+ /* Left-aligned and full width: the eye reads down a list, and a centred label in a
703
+ wide target is harder to scan than a flush one. */
704
+ text-align: start;
705
+ width: 100%;
706
+ padding: var(--aparte-approval-option-padding, 8px 10px);
707
+ border-radius: var(--aparte-approval-option-radius, 8px);
708
+ cursor: pointer;
709
+ border: 1px solid var(--aparte-border, rgba(128, 128, 128, 0.3));
710
+ background: transparent;
711
+ color: var(--aparte-text);
712
+ }
713
+
714
+ .aparte-approval-option:hover {
715
+ background: var(--aparte-surface-2, rgba(128, 128, 128, 0.08));
716
+ }
717
+
718
+ /* Tone is carried by a leading rule, not by colouring the label or ringing the whole
719
+ button. Both of those were tried: a green word on a dark panel reads as a link, and a
720
+ coloured border on every option turns the list into a traffic light. A 2px edge says
721
+ which way an option goes while leaving the text plain. */
722
+ /* The SOLID colours, not the `-border` pair. Those carry ~40% alpha because they were
723
+ made for a 1px box outline over a surface; on a 2px rule against a light background
724
+ the deny edge came out a pale pink that read as decoration. A rule wants a colour. */
725
+ .aparte-approval-option--affirm {
726
+ border-inline-start: 2px solid var(--aparte-success, rgb(21, 128, 61));
727
+ }
728
+
729
+ .aparte-approval-option--deny {
730
+ border-inline-start: 2px solid var(--aparte-error, rgb(185, 28, 28));
731
+ }
732
+
733
+ /* The refusal's reason, in the user's words. Quiet by default and one line tall: it is
734
+ the exception, not the expected act, and a box sized for a paragraph invites one. */
735
+ .aparte-approval-instruction {
736
+ font: inherit;
737
+ font-size: var(--aparte-elic-input-size, 0.85rem);
738
+ color: var(--aparte-text);
739
+ background: transparent;
740
+ border: none;
741
+ border-top: 1px solid var(--aparte-border, rgba(128, 128, 128, 0.3));
742
+ border-radius: 0;
743
+ padding: 8px 10px 2px;
744
+ margin: 4px 6px 0;
745
+ resize: none;
746
+ overflow: hidden;
747
+ min-height: 1.6em;
748
+ }
749
+
750
+ .aparte-approval-instruction:focus {
751
+ outline: none;
752
+ border-top-color: var(--aparte-accent, rgba(128, 128, 128, 0.6));
753
+ }
754
+
755
+ .aparte-approval-instruction::placeholder {
756
+ color: var(--aparte-text-muted, rgba(128, 128, 128, 0.75));
757
+ }
758
+
674
759
  .aparte-elic-skip {
675
760
  border: none;
676
761
  background: transparent;
@@ -1475,7 +1560,7 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
1475
1560
  font-size: var(--aparte-font-size-md, 13px);
1476
1561
  }
1477
1562
 
1478
- .aparte-input-upper.has-content {
1563
+ .aparte-input-upper.aparte-has-content {
1479
1564
  display: flex;
1480
1565
  }
1481
1566
 
@@ -1702,7 +1787,7 @@ aparte-chat-status:not([visible]) {
1702
1787
  ========================================= */
1703
1788
 
1704
1789
  /* Text */
1705
- .segment-text {
1790
+ .aparte-segment-text {
1706
1791
  word-wrap: break-word;
1707
1792
  }
1708
1793
 
@@ -1713,60 +1798,60 @@ aparte-chat-status:not([visible]) {
1713
1798
  ========================================= */
1714
1799
 
1715
1800
  .aparte-content,
1716
- .segment-content {
1801
+ .aparte-segment-content {
1717
1802
  white-space: normal;
1718
1803
  }
1719
1804
 
1720
1805
  .aparte-content p,
1721
- .segment-content p,
1722
- .thinking-content p {
1806
+ .aparte-segment-content p,
1807
+ .aparte-thinking-content p {
1723
1808
  margin: var(--aparte-prose-p-margin, 0.4em 0);
1724
1809
  }
1725
1810
 
1726
1811
  .aparte-content p:first-child,
1727
- .segment-content p:first-child,
1728
- .thinking-content p:first-child {
1812
+ .aparte-segment-content p:first-child,
1813
+ .aparte-thinking-content p:first-child {
1729
1814
  margin-top: 0;
1730
1815
  }
1731
1816
 
1732
1817
  .aparte-content p:last-child,
1733
- .segment-content p:last-child,
1734
- .thinking-content p:last-child {
1818
+ .aparte-segment-content p:last-child,
1819
+ .aparte-thinking-content p:last-child {
1735
1820
  margin-bottom: 0;
1736
1821
  }
1737
1822
 
1738
- .aparte-content h1, .segment-content h1, .thinking-content h1 { font-size: var(--aparte-prose-h1-size, 1.4em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.6em 0 0.2em; }
1739
- .aparte-content h2, .segment-content h2, .thinking-content h2 { font-size: var(--aparte-prose-h2-size, 1.25em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.5em 0 0.2em; }
1740
- .aparte-content h3, .segment-content h3, .thinking-content h3 { font-size: var(--aparte-prose-h3-size, 1.1em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.4em 0 0.15em; }
1741
- .aparte-content h4, .segment-content h4, .thinking-content h4,
1742
- .aparte-content h5, .segment-content h5, .thinking-content h5,
1743
- .aparte-content h6, .segment-content h6, .thinking-content h6 { font-size: 1em; font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.3em 0 0.1em; }
1823
+ .aparte-content h1, .aparte-segment-content h1, .aparte-thinking-content h1 { font-size: var(--aparte-prose-h1-size, 1.4em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.6em 0 0.2em; }
1824
+ .aparte-content h2, .aparte-segment-content h2, .aparte-thinking-content h2 { font-size: var(--aparte-prose-h2-size, 1.25em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.5em 0 0.2em; }
1825
+ .aparte-content h3, .aparte-segment-content h3, .aparte-thinking-content h3 { font-size: var(--aparte-prose-h3-size, 1.1em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.4em 0 0.15em; }
1826
+ .aparte-content h4, .aparte-segment-content h4, .aparte-thinking-content h4,
1827
+ .aparte-content h5, .aparte-segment-content h5, .aparte-thinking-content h5,
1828
+ .aparte-content h6, .aparte-segment-content h6, .aparte-thinking-content h6 { font-size: 1em; font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.3em 0 0.1em; }
1744
1829
 
1745
1830
  .aparte-content ul,
1746
- .segment-content ul,
1747
- .thinking-content ul {
1831
+ .aparte-segment-content ul,
1832
+ .aparte-thinking-content ul {
1748
1833
  list-style: disc;
1749
1834
  padding-left: var(--aparte-prose-list-indent, 1.5em);
1750
1835
  margin: var(--aparte-prose-list-margin, 0.4em 0);
1751
1836
  }
1752
1837
 
1753
1838
  .aparte-content ol,
1754
- .segment-content ol,
1755
- .thinking-content ol {
1839
+ .aparte-segment-content ol,
1840
+ .aparte-thinking-content ol {
1756
1841
  list-style: decimal;
1757
1842
  padding-left: var(--aparte-prose-list-indent, 1.5em);
1758
1843
  margin: var(--aparte-prose-list-margin, 0.4em 0);
1759
1844
  }
1760
1845
 
1761
1846
  .aparte-content li,
1762
- .segment-content li,
1763
- .thinking-content li {
1847
+ .aparte-segment-content li,
1848
+ .aparte-thinking-content li {
1764
1849
  margin: var(--aparte-prose-li-margin, 0.15em 0);
1765
1850
  }
1766
1851
 
1767
1852
  .aparte-content blockquote,
1768
- .segment-content blockquote,
1769
- .thinking-content blockquote {
1853
+ .aparte-segment-content blockquote,
1854
+ .aparte-thinking-content blockquote {
1770
1855
  border-left: var(--aparte-prose-blockquote-border-width, 3px) solid var(--aparte-prose-blockquote-border, var(--aparte-border, #e5e7eb));
1771
1856
  padding-left: 1em;
1772
1857
  margin: 0.4em 0;
@@ -1775,20 +1860,20 @@ aparte-chat-status:not([visible]) {
1775
1860
  }
1776
1861
 
1777
1862
  .aparte-content strong,
1778
- .segment-content strong,
1779
- .thinking-content strong {
1863
+ .aparte-segment-content strong,
1864
+ .aparte-thinking-content strong {
1780
1865
  font-weight: var(--aparte-font-weight-semibold, 600);
1781
1866
  }
1782
1867
 
1783
1868
  .aparte-content em,
1784
- .segment-content em,
1785
- .thinking-content em {
1869
+ .aparte-segment-content em,
1870
+ .aparte-thinking-content em {
1786
1871
  font-style: italic;
1787
1872
  }
1788
1873
 
1789
1874
  .aparte-content code,
1790
- .segment-content code,
1791
- .thinking-content code {
1875
+ .aparte-segment-content code,
1876
+ .aparte-thinking-content code {
1792
1877
  font-family: var(--aparte-code-font-family, ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace);
1793
1878
  font-size: var(--aparte-code-inline-font-size, 0.875em);
1794
1879
  background: var(--aparte-prose-code-bg, var(--aparte-code-bg, #f3f4f6));
@@ -1797,29 +1882,29 @@ aparte-chat-status:not([visible]) {
1797
1882
  }
1798
1883
 
1799
1884
  .aparte-content a,
1800
- .segment-content a,
1801
- .thinking-content a {
1885
+ .aparte-segment-content a,
1886
+ .aparte-thinking-content a {
1802
1887
  color: var(--aparte-primary, #6366f1);
1803
1888
  text-decoration: underline;
1804
1889
  }
1805
1890
 
1806
1891
  .aparte-content hr,
1807
- .segment-content hr,
1808
- .thinking-content hr {
1892
+ .aparte-segment-content hr,
1893
+ .aparte-thinking-content hr {
1809
1894
  border: none;
1810
1895
  border-top: var(--aparte-border-width, 1px) solid var(--aparte-border, #e5e7eb);
1811
1896
  margin: 0.75em 0;
1812
1897
  }
1813
1898
 
1814
1899
  /* Thinking */
1815
- .segment-thinking {
1900
+ .aparte-segment-thinking {
1816
1901
  /* Understated disclosure: a muted left rail, not a filled card. */
1817
1902
  border-left: var(--aparte-thinking-rail-width, 2px) solid var(--aparte-thinking-border, #e5e7eb);
1818
1903
  margin: var(--aparte-space-5, 10px) 0;
1819
1904
  background: transparent;
1820
1905
  }
1821
1906
 
1822
- .thinking-header {
1907
+ .aparte-thinking-header {
1823
1908
  display: flex;
1824
1909
  align-items: center;
1825
1910
  gap: var(--aparte-space-3, 6px);
@@ -1831,27 +1916,27 @@ aparte-chat-status:not([visible]) {
1831
1916
  list-style: none;
1832
1917
  }
1833
1918
 
1834
- .thinking-header::-webkit-details-marker {
1919
+ .aparte-thinking-header::-webkit-details-marker {
1835
1920
  display: none;
1836
1921
  }
1837
1922
 
1838
- .thinking-toggle {
1923
+ .aparte-thinking-toggle {
1839
1924
  margin-inline-start: auto;
1840
1925
  /* Logical property */
1841
1926
  transition: transform 0.2s;
1842
1927
  opacity: 0.55;
1843
1928
  }
1844
1929
 
1845
- details[open] .thinking-toggle {
1930
+ details[open] .aparte-thinking-toggle {
1846
1931
  transform: rotate(180deg);
1847
1932
  }
1848
1933
 
1849
- .thinking-toggle::after {
1934
+ .aparte-thinking-toggle::after {
1850
1935
  content: '▼';
1851
1936
  font-size: var(--aparte-thinking-toggle-size, 9px);
1852
1937
  }
1853
1938
 
1854
- .thinking-content {
1939
+ .aparte-thinking-content {
1855
1940
  padding: var(--aparte-space-2, 4px) 0 var(--aparte-space-1, 2px) var(--aparte-space-5, 10px);
1856
1941
  font-size: var(--aparte-font-size-md, 13px);
1857
1942
  line-height: var(--aparte-thinking-content-line-height, 1.55);
@@ -1862,14 +1947,14 @@ details[open] .thinking-toggle {
1862
1947
  }
1863
1948
 
1864
1949
  /* Code */
1865
- .segment-code {
1950
+ .aparte-segment-code {
1866
1951
  margin: var(--aparte-space-6, 12px) 0;
1867
1952
  border-radius: var(--aparte-radius-code);
1868
1953
  overflow: hidden;
1869
1954
  border: var(--aparte-border-width, 1px) solid var(--aparte-code-border, #e5e7eb);
1870
1955
  }
1871
1956
 
1872
- .code-header {
1957
+ .aparte-code-header {
1873
1958
  display: flex;
1874
1959
  align-items: center;
1875
1960
  padding: var(--aparte-space-4, 8px) var(--aparte-space-6, 12px);
@@ -1879,25 +1964,25 @@ details[open] .thinking-toggle {
1879
1964
  color: var(--aparte-code-header-text, #4b5563);
1880
1965
  }
1881
1966
 
1882
- .code-filename {
1967
+ .aparte-code-filename {
1883
1968
  font-weight: var(--aparte-font-weight-semibold, 600);
1884
1969
  margin-inline-end: auto;
1885
1970
  /* Logical property: pushes language + copy to the right when filename is present */
1886
1971
  }
1887
1972
 
1888
- .code-header-filler {
1973
+ .aparte-code-header-filler {
1889
1974
  flex: 1;
1890
1975
  /* No-filename placeholder — keeps language + copy right-aligned */
1891
1976
  }
1892
1977
 
1893
- .code-language {
1978
+ .aparte-code-language {
1894
1979
  margin-inline-end: var(--aparte-space-4, 8px);
1895
1980
  /* Logical property */
1896
1981
  text-transform: uppercase;
1897
1982
  opacity: 0.7;
1898
1983
  }
1899
1984
 
1900
- .code-copy {
1985
+ .aparte-code-copy {
1901
1986
  background: transparent;
1902
1987
  border: none;
1903
1988
  cursor: pointer;
@@ -1909,147 +1994,29 @@ details[open] .thinking-toggle {
1909
1994
  justify-content: center;
1910
1995
  }
1911
1996
 
1912
- .code-copy:hover {
1997
+ .aparte-code-copy:hover {
1913
1998
  background: var(--aparte-code-copy-hover-bg, rgba(0, 0, 0, 0.05));
1914
1999
  }
1915
2000
 
1916
- .code-copy svg {
2001
+ .aparte-code-copy svg {
1917
2002
  width: var(--aparte-code-copy-icon-size, 14px);
1918
2003
  height: var(--aparte-code-copy-icon-size, 14px);
1919
2004
  }
1920
2005
 
1921
- .code-content-wrapper pre {
2006
+ .aparte-code-content-wrapper pre {
1922
2007
  margin: 0;
1923
2008
  padding: var(--aparte-space-6, 12px);
1924
2009
  /* Wrap long lines instead of a horizontal scrollbar. `pre-wrap` honours
1925
2010
  newlines + indentation but soft-wraps overflowing lines; `overflow-wrap:
1926
2011
  anywhere` breaks unbreakable tokens (long URLs, hashes). Applies to both
1927
2012
  the plain <pre><code> initial render and the shiki <pre class="shiki">
1928
- replacement, since both sit inside .code-content-wrapper. */
2013
+ replacement, since both sit inside .aparte-code-content-wrapper. */
1929
2014
  white-space: pre-wrap;
1930
2015
  overflow-wrap: anywhere;
1931
2016
  overflow-x: hidden;
1932
2017
  background: var(--aparte-code-bg, #ffffff);
1933
2018
  }
1934
2019
 
1935
- /* Terminal */
1936
- .segment-terminal {
1937
- margin: var(--aparte-space-6, 12px) 0;
1938
- font-family: var(--aparte-code-font-family, 'Fira Code', 'Monaco', 'Consolas', monospace);
1939
- font-size: var(--aparte-terminal-font-size, 13px);
1940
- }
1941
-
1942
- .terminal-command-block {
1943
- display: flex;
1944
- align-items: center;
1945
- gap: var(--aparte-space-5, 10px);
1946
- padding: var(--aparte-space-5, 10px) var(--aparte-space-7, 14px);
1947
- background: var(--aparte-terminal-bg, #1e293b);
1948
- border-radius: var(--aparte-radius-terminal);
1949
- color: var(--aparte-terminal-text, #e2e8f0);
1950
- }
1951
-
1952
- .terminal-icon {
1953
- flex-shrink: 0;
1954
- color: var(--aparte-terminal-icon, #64748b);
1955
- display: flex;
1956
- align-items: center;
1957
- }
1958
-
1959
- .terminal-command {
1960
- flex: 1;
1961
- word-break: break-all;
1962
- background: transparent;
1963
- color: inherit;
1964
- font-family: inherit;
1965
- }
1966
-
1967
- .terminal-actions {
1968
- display: flex;
1969
- align-items: center;
1970
- gap: var(--aparte-space-3, 6px);
1971
- flex-shrink: 0;
1972
- }
1973
-
1974
- .terminal-run-btn {
1975
- padding: var(--aparte-space-2, 4px) var(--aparte-space-6, 12px);
1976
- background: var(--aparte-primary, #6366f1);
1977
- color: var(--aparte-on-primary, white);
1978
- border: none;
1979
- border-radius: var(--aparte-radius-sm);
1980
- font-size: var(--aparte-font-size-sm, 12px);
1981
- font-weight: var(--aparte-font-weight-medium, 500);
1982
- cursor: pointer;
1983
- transition: background 0.15s;
1984
- }
1985
-
1986
- .terminal-run-btn:hover {
1987
- background: var(--aparte-primary-hover, #4f46e5);
1988
- }
1989
-
1990
- .terminal-copy-btn {
1991
- width: var(--aparte-terminal-copy-btn-size, 26px);
1992
- height: var(--aparte-terminal-copy-btn-size, 26px);
1993
- padding: 0;
1994
- background: transparent;
1995
- border: none;
1996
- color: var(--aparte-terminal-icon, #64748b);
1997
- cursor: pointer;
1998
- border-radius: var(--aparte-radius-sm);
1999
- display: flex;
2000
- align-items: center;
2001
- justify-content: center;
2002
- }
2003
-
2004
- .terminal-copy-btn:hover {
2005
- background: var(--aparte-terminal-hover, #334155);
2006
- color: var(--aparte-terminal-text, #e2e8f0);
2007
- }
2008
-
2009
- .terminal-running {
2010
- display: flex;
2011
- align-items: center;
2012
- gap: var(--aparte-space-3, 6px);
2013
- color: var(--aparte-terminal-running, #22c55e);
2014
- font-size: var(--aparte-font-size-sm, 12px);
2015
- }
2016
-
2017
- .terminal-running .spinner {
2018
- width: var(--aparte-space-6, 12px);
2019
- height: var(--aparte-space-6, 12px);
2020
- border: 2px solid currentColor;
2021
- border-top-color: transparent;
2022
- border-radius: 50%;
2023
- animation: aparte-spin 0.8s linear infinite;
2024
- }
2025
-
2026
- @keyframes aparte-spin {
2027
- to {
2028
- transform: rotate(360deg);
2029
- }
2030
- }
2031
-
2032
- .terminal-output {
2033
- margin-top: var(--aparte-space-4, 8px);
2034
- padding: var(--aparte-space-5, 10px) var(--aparte-space-7, 14px);
2035
- background: var(--aparte-terminal-output-bg, #0f172a);
2036
- border-radius: var(--aparte-radius-md);
2037
- color: var(--aparte-terminal-output-text, #94a3b8);
2038
- white-space: pre-wrap;
2039
- line-height: var(--aparte-line-height-normal, 1.5);
2040
- max-height: var(--aparte-terminal-output-max-height, 200px);
2041
- overflow-y: auto;
2042
- }
2043
-
2044
- .terminal-error {
2045
- margin-top: var(--aparte-space-3, 6px);
2046
- padding: var(--aparte-space-3, 6px) var(--aparte-space-5, 10px);
2047
- background: var(--aparte-error-bg, #fef2f2);
2048
- border-radius: var(--aparte-radius-sm);
2049
- color: var(--aparte-error-text, #dc2626);
2050
- font-size: var(--aparte-font-size-sm, 12px);
2051
- }
2052
-
2053
2020
  /* Errored bubble state — the whole message carries `data-error` while it holds
2054
2021
  an error segment (set by the bubble in _reflectError). Kept subtle by default
2055
2022
  since the error segment itself is already styled; this is a theming hook —
@@ -2059,7 +2026,7 @@ details[open] .thinking-toggle {
2059
2026
  }
2060
2027
 
2061
2028
  /* Error */
2062
- .segment-error {
2029
+ .aparte-segment-error {
2063
2030
  display: flex;
2064
2031
  gap: var(--aparte-space-6, 12px);
2065
2032
  padding: var(--aparte-space-6, 12px) var(--aparte-space-8, 16px);
@@ -2072,7 +2039,7 @@ details[open] .thinking-toggle {
2072
2039
  font-family: var(--aparte-font-family, system-ui, sans-serif);
2073
2040
  }
2074
2041
 
2075
- .error-icon-wrapper {
2042
+ .aparte-error-icon-wrapper {
2076
2043
  flex-shrink: 0;
2077
2044
  color: var(--aparte-error-solid, #dc2626);
2078
2045
  display: flex;
@@ -2080,29 +2047,29 @@ details[open] .thinking-toggle {
2080
2047
  padding-top: var(--aparte-space-1, 2px);
2081
2048
  }
2082
2049
 
2083
- .error-icon-wrapper svg {
2050
+ .aparte-error-icon-wrapper svg {
2084
2051
  width: var(--aparte-error-icon-size, 20px);
2085
2052
  height: var(--aparte-error-icon-size, 20px);
2086
2053
  }
2087
2054
 
2088
- .error-content {
2055
+ .aparte-error-content {
2089
2056
  flex: 1;
2090
2057
  min-width: 0;
2091
2058
  }
2092
2059
 
2093
- .error-title {
2060
+ .aparte-error-title {
2094
2061
  font-weight: var(--aparte-font-weight-semibold, 600);
2095
2062
  font-size: var(--aparte-font-size-lg, 14px);
2096
2063
  margin-bottom: var(--aparte-space-2, 4px);
2097
2064
  color: var(--aparte-error-title, #7f1d1d);
2098
2065
  }
2099
2066
 
2100
- .error-message {
2067
+ .aparte-error-message {
2101
2068
  font-size: var(--aparte-font-size-lg, 14px);
2102
2069
  line-height: var(--aparte-line-height-normal, 1.5);
2103
2070
  }
2104
2071
 
2105
- .error-details {
2072
+ .aparte-error-details {
2106
2073
  margin-top: var(--aparte-space-4, 8px);
2107
2074
  padding-top: var(--aparte-space-4, 8px);
2108
2075
  border-top: var(--aparte-border-width, 1px) solid var(--aparte-error-border, #fecaca);
@@ -2112,78 +2079,454 @@ details[open] .thinking-toggle {
2112
2079
  white-space: pre-wrap;
2113
2080
  }
2114
2081
 
2115
- /* Progress */
2116
- .segment-progress {
2117
- margin: var(--aparte-space-6, 12px) 0;
2118
- }
2119
2082
 
2120
- .progress-header {
2121
- display: flex;
2122
- justify-content: space-between;
2123
- align-items: center;
2124
- margin-bottom: var(--aparte-space-2, 4px);
2125
- color: var(--aparte-progress-label, #374151);
2126
- font-size: var(--aparte-font-size-lg, 14px);
2127
- }
2083
+ /* ─────────────────────────────────────────────────────────────────────────────
2084
+ Segment renderers — tool call · artifact card · pipeline waiting
2085
+
2086
+ These three shipped their CSS from `getStyles()` until now: a template literal
2087
+ in the renderer's own .ts file, injected into <head> at runtime. That seam is
2088
+ for a CONSUMER's renderer — something registered through registerSegmentRenderer
2089
+ or registerToolRenderer cannot edit this file, so `getStyles()` is its only way
2090
+ onto the page. A BUILT-IN has this file, and two reasons to use it:
2091
+
2092
+ - check:derived-vars reads this path and nothing else. A declaration whose
2093
+ value reads another variable has to sit on the 5-anchor layer or it cannot
2094
+ follow a palette a subtree overrides, and one hidden in a renderer was
2095
+ never checked at all.
2096
+ - it is read as CSS, by tools and by people. A backtick closes a template
2097
+ literal, and one did — the artifact card's own comment recorded it
2098
+ happening — then it happened three more times in one sitting, the worst of
2099
+ them rendering a `safe-text` source marker into an assistant's bubble as
2100
+ prose, because inside a template literal a `//` comment is just text.
2101
+
2102
+ NOT a reason, though it was the first one I wrote here: reaching the public CSS
2103
+ reference. `gen-css-vars.mjs` walks all of core's source for `var(--aparte-*)`
2104
+ reads, so a knob a renderer read was already listed with its fallback as the
2105
+ default — the move changed that file by zero lines. Worth recording, because the
2106
+ plausible version of this argument is the one that gets repeated.
2107
+
2108
+ Moving them changes no contract either: core's entry imports this file and
2109
+ package.json marks every .css a side effect, so importing @aparte/core has
2110
+ always brought this sheet along.
2111
+ ───────────────────────────────────────────────────────────────────────────── */
2128
2112
 
2129
- .progress-bar {
2130
- height: var(--aparte-space-2, 4px);
2131
- background: var(--aparte-progress-bar-bg, #e5e7eb);
2132
- border-radius: var(--aparte-radius-xs);
2133
- margin-top: var(--aparte-space-4, 8px);
2134
- overflow: hidden;
2113
+ /* Tool call */
2114
+ .aparte-segment-tool-call { display: block; padding: 2px 0; }
2115
+ /* The row is the summary. list-style:none plus the ::-webkit- pseudo is the
2116
+ pair that actually removes the native marker across engines. */
2117
+ /* A ROW, not a badge.
2118
+ It was a rounded chip, which is what a tag looks like — and a tool call is not
2119
+ a tag, it is a step of the turn. Every current implementation draws it as a
2120
+ line: the disclosure control, the call's identity, and its state at the far
2121
+ end. The chip also had to carry a border and a fill, so it competed with the
2122
+ reply text beside it for attention it did not deserve. */
2123
+ .aparte-tool-summary {
2124
+ display: flex; align-items: center; gap: 7px;
2125
+ padding: 3px 6px; margin-inline-start: -6px;
2126
+ border-radius: var(--aparte-tool-row-radius, 6px);
2127
+ cursor: pointer; list-style: none;
2128
+ }
2129
+ .aparte-tool-summary:hover { background: var(--aparte-surface-2, rgba(128,128,128,0.07)); }
2130
+ .aparte-tool-summary::-webkit-details-marker { display: none; }
2131
+ .aparte-tool-toggle {
2132
+ width: 0; height: 0; flex: none; margin-inline-start: -1px;
2133
+ border-inline-start: 5px solid currentColor;
2134
+ border-block: 4px solid transparent;
2135
+ opacity: 0.5; transition: transform 0.15s;
2136
+ }
2137
+ .aparte-tool-summary:hover .aparte-tool-toggle { opacity: 0.9; }
2138
+ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(90deg); }
2139
+ /* A rule down the leading edge, so the disclosure reads as belonging to the
2140
+ pill above it rather than continuing the message text below. Without it
2141
+ "Output" and the assistant's next sentence sat in one undifferentiated
2142
+ column. */
2143
+ .aparte-tool-detail {
2144
+ display: flex; flex-direction: column; gap: 8px;
2145
+ /* Aligned under the row's icon, so the rule reads as descending from the
2146
+ call rather than floating beside it. */
2147
+ margin: 4px 0 6px 3px; padding: 2px 0 2px 12px;
2148
+ border-inline-start: 1px solid var(--aparte-border, rgba(128,128,128,0.25));
2149
+ }
2150
+ .aparte-tool-part-label {
2151
+ display: block; font-size: 0.68rem; text-transform: uppercase;
2152
+ letter-spacing: 0.04em; opacity: 0.55; margin-bottom: 2px;
2153
+ }
2154
+ .aparte-tool-part-body pre { margin: 0; }
2155
+ /* Always in the DOM so update can leave the node alone and toggle hidden
2156
+ instead — an attribute a test can read, where a CSS-only rule is invisible
2157
+ to jsdom. */
2158
+ .aparte-tool-spinner[hidden] { display: none; }
2159
+ /* The call's identity: its icon and its name, and no decoration of its own.
2160
+ No border, no fill, no tint at any status — the row is the container now, and
2161
+ the colour lives on the state badge alone. A resolved call used to be filled
2162
+ green, which made a finished step shout louder than the reply it belongs to. */
2163
+ .aparte-tool-label {
2164
+ display: inline-flex;
2165
+ align-items: center;
2166
+ gap: 6px;
2167
+ min-width: 0;
2168
+ font-size: 0.8rem;
2169
+ font-weight: 500;
2170
+ color: var(--aparte-text, inherit);
2171
+ user-select: none;
2172
+ }
2173
+ /* A fixed square, and the glyph centred in it. The icon was baseline-aligned
2174
+ inside an inline box, so it sat low against the name and moved with whatever
2175
+ glyph the icon provider returned. */
2176
+ .aparte-tool-icon {
2177
+ display: grid;
2178
+ place-items: center;
2179
+ width: 1em; height: 1em;
2180
+ flex: none;
2181
+ opacity: 0.7;
2182
+ }
2183
+ .aparte-tool-icon svg { width: 1em; height: 1em; display: block; }
2184
+ .aparte-tool-name { font-family: var(--aparte-code-font-family, ui-monospace, monospace); }
2185
+ /* The state, as a word with its glyph. Colour lives HERE and nowhere else.
2186
+ Pushed to the far end by DOM order plus an auto inline-start margin, which is
2187
+ the placement rule this repo already settled on: no left/right in a name, so
2188
+ a right-to-left locale mirrors it for free. */
2189
+ .aparte-tool-state {
2190
+ display: inline-flex; align-items: center; gap: 4px;
2191
+ margin-inline-start: auto;
2192
+ font-size: 0.72rem; font-weight: 500;
2193
+ color: var(--aparte-text-muted, rgba(128,128,128,0.75));
2194
+ user-select: none;
2195
+ }
2196
+ .aparte-tool-state svg { width: 0.9em; height: 0.9em; display: block; }
2197
+ [data-status="resolved"] .aparte-tool-state { color: var(--aparte-success, rgb(21,128,61)); }
2198
+ [data-status="rejected"] .aparte-tool-state,
2199
+ [data-status="aborted"] .aparte-tool-state { color: var(--aparte-error, rgb(185,28,28)); }
2200
+ /* The Approve / Reject button rules were here. They moved with the buttons, to
2201
+ .aparte-approval-option in this same file - and what stayed behind was a
2202
+ leftover: a rule tinting .aparte-tool-label's border while a decision was pending,
2203
+ which stopped painting anything the moment the row redesign took that border
2204
+ off .aparte-tool-label. It reached for a variable that was never declared, and it
2205
+ survived because a template literal is not a stylesheet anyone greps. The
2206
+ state is named in words by .aparte-tool-state now, and the decision is at the
2207
+ composer. */
2208
+ .aparte-tool-spinner {
2209
+ width: 10px; height: 10px;
2210
+ border: 1.5px solid currentColor;
2211
+ border-top-color: transparent;
2212
+ border-radius: 50%;
2213
+ display: inline-block;
2214
+ animation: tool-spin 0.7s linear infinite;
2215
+ }
2216
+ .aparte-tool-state { font-size: 0.75rem; }
2217
+ @keyframes tool-spin { to { transform: rotate(360deg); } }
2218
+
2219
+ /* Artifact card */
2220
+ .aparte-segment-artifact-card {
2221
+ display: flex; flex-direction: column;
2222
+ margin: 8px 0;
2223
+ border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
2224
+ border-radius: 12px;
2225
+ background: var(--aparte-surface, #fff);
2226
+ overflow: hidden;
2227
+ font: inherit;
2228
+ color: var(--aparte-text, inherit);
2229
+ }
2230
+ .aparte-art-card__header {
2231
+ display: flex; align-items: center; justify-content: space-between;
2232
+ padding: 8px 10px;
2233
+ border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
2234
+ background: var(--aparte-surface-2, rgba(0,0,0,0.02));
2235
+ min-height: 36px;
2236
+ }
2237
+ .aparte-art-card__title-block { display: flex; align-items: center; gap: 8px; min-width: 0; }
2238
+ .aparte-art-card__kind {
2239
+ font-size: 0.7rem; font-weight: 600;
2240
+ text-transform: uppercase; letter-spacing: 0.04em;
2241
+ padding: 2px 6px; border-radius: 4px;
2242
+ background: var(--aparte-surface, #fff);
2243
+ border: 1px solid var(--aparte-border, rgba(0,0,0,0.1));
2244
+ color: var(--aparte-text-muted, rgba(0,0,0,0.6));
2245
+ }
2246
+ .aparte-art-card__title {
2247
+ font-size: 0.85rem; font-weight: 500;
2248
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
2249
+ }
2250
+ .aparte-art-card__pulse {
2251
+ width: 8px; height: 8px; border-radius: 50%;
2252
+ background: var(--aparte-accent, #3b82f6);
2253
+ animation: aparte-art-card-pulse 1.2s ease-in-out infinite;
2254
+ }
2255
+ @keyframes aparte-art-card-pulse {
2256
+ 0%, 100% { opacity: 0.35; transform: scale(0.85); }
2257
+ 50% { opacity: 1; transform: scale(1.1); }
2258
+ }
2259
+ .aparte-art-card__actions { display: flex; gap: 4px; }
2260
+ .aparte-art-card__btn {
2261
+ display: inline-flex; align-items: center; justify-content: center;
2262
+ width: 28px; height: 28px;
2263
+ background: transparent;
2264
+ border: 1px solid transparent;
2265
+ border-radius: 6px;
2266
+ color: var(--aparte-text-muted, rgba(0,0,0,0.55));
2267
+ cursor: pointer;
2268
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
2269
+ }
2270
+ .aparte-art-card__btn:hover:not(:disabled) {
2271
+ background: var(--aparte-surface-hover, rgba(0,0,0,0.04));
2272
+ border-color: var(--aparte-border, rgba(0,0,0,0.1));
2273
+ color: var(--aparte-text, inherit);
2274
+ }
2275
+ .aparte-art-card__btn:disabled { opacity: 0.4; cursor: not-allowed; }
2276
+ .aparte-art-card__tabs {
2277
+ /* justify-content and the padding are DECLARED, not left to a default.
2278
+ Core is light DOM on purpose - no shadow root, no ::part(), any selector
2279
+ reaches in - and the corollary is that a component must state what its
2280
+ layout depends on, because an undeclared property has nothing to override
2281
+ a host rule with. A consuming page with a bare nav selector setting
2282
+ justify-content: space-between and padding-top (this library's own docs
2283
+ site had exactly that) otherwise pushes these two tabs to opposite ends of
2284
+ the card and pads the row out.
2285
+
2286
+ flex-end, and Code first in the DOM. The card OPENS on Code - mounting the
2287
+ preview would execute model-authored code with no gesture (ratified
2288
+ decision #8) - and a selected tab sitting second reads backwards. Right,
2289
+ because the header above puts the artifact's identity on the left and its
2290
+ copy/download buttons on the right, so this keeps every control in one
2291
+ column. DOM order is also keyboard order, so the tab a reader lands on
2292
+ first is the one already showing. */
2293
+ display: flex; justify-content: flex-end; align-items: stretch; gap: 2px;
2294
+ padding: 4px 8px 0;
2295
+ border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
2296
+ background: var(--aparte-surface-2, rgba(0,0,0,0.02));
2297
+ }
2298
+ .aparte-art-card__tabs button {
2299
+ padding: 6px 12px;
2300
+ font-size: 0.78rem;
2301
+ background: transparent;
2302
+ border: 1px solid transparent;
2303
+ border-bottom: none;
2304
+ border-radius: 6px 6px 0 0;
2305
+ color: var(--aparte-text-muted, rgba(0,0,0,0.6));
2306
+ cursor: pointer;
2307
+ transition: background 0.12s, color 0.12s;
2308
+ }
2309
+ .aparte-art-card__tabs button[aria-selected="true"] {
2310
+ background: var(--aparte-surface, #fff);
2311
+ border-color: var(--aparte-border, rgba(0,0,0,0.08));
2312
+ color: var(--aparte-text, inherit);
2313
+ font-weight: 500;
2314
+ }
2315
+ .aparte-art-card__tabs button:disabled { opacity: 0.4; cursor: not-allowed; }
2316
+ /* The card's heights, as variables with ONE owner each.
2317
+ They were four hardcoded numbers in the only part of this card that did not
2318
+ use a variable - everything else here already reads var(--aparte-code-bg) and
2319
+ friends - and two of them had to agree while a third contradicted a fourth:
2320
+ the code pane repeated the body's 600px, and the "press Preview" placeholder
2321
+ was 120px tall inside a body whose min-height said 80, so that minimum applied
2322
+ to nothing.
2323
+ The frame stays a FIXED height rather than an aspect ratio, which is what
2324
+ embeds of arbitrary HTML actually do - CodeSandbox documents 500px, StackBlitz
2325
+ takes a height parameter - because a frame with an opaque origin cannot be
2326
+ measured and a 16/10 ratio on a wide card is enormous. The vh cap is the part
2327
+ that was missing: a fixed 480px should not own a phone screen.
2328
+ Each default lives in its read, as var(--x, literal), the way every other
2329
+ value in this file already does - not in a declaration block on top of the
2330
+ fallbacks, which would be two owners of one number again. It also means the
2331
+ docs' CSS-variable generator finds them: its sweep looks for reads that
2332
+ carry a fallback, so a read without one is a public knob nobody documents. */
2333
+ .aparte-art-card__body {
2334
+ position: relative;
2335
+ /* The placeholder is the tallest thing this box can hold while empty, so it
2336
+ IS the minimum - one number instead of two that disagreed. */
2337
+ min-height: var(--aparte-artifact-pending-height, 120px);
2338
+ max-height: var(--aparte-artifact-body-max, 600px);
2339
+ overflow: hidden;
2135
2340
  }
2136
-
2137
- .progress-fill {
2138
- height: 100%;
2139
- background: var(--aparte-progress-bar-fill, #6366f1);
2140
- border-radius: var(--aparte-radius-xs);
2141
- transition: width 0.3s ease;
2341
+ .aparte-art-card__pane { display: none; height: 100%; }
2342
+ .aparte-segment-artifact-card[data-tab="code"] .aparte-art-card__pane[data-pane="code"] { display: block; }
2343
+ .aparte-segment-artifact-card[data-tab="preview"] .aparte-art-card__pane[data-pane="preview"] { display: block; }
2344
+ .aparte-art-card__pane[data-pane="code"] {
2345
+ /* The body already caps this; repeating the number was the second owner. */
2346
+ max-height: var(--aparte-artifact-body-max, 600px); overflow: auto;
2142
2347
  }
2143
-
2144
- /* File Tree */
2145
- .segment-file-tree {
2146
- background: var(--aparte-file-tree-bg, #f8fafc);
2147
- border: var(--aparte-border-width, 1px) solid var(--aparte-file-tree-border, #e2e8f0);
2148
- border-radius: var(--aparte-radius-file-tree);
2149
- margin: var(--aparte-space-6, 12px) 0;
2150
- overflow: hidden;
2151
- font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
2152
- font-size: var(--aparte-font-size-md, 13px);
2348
+ .aparte-art-card__pane[data-pane="code"] pre {
2349
+ margin: 0; padding: 12px;
2350
+ font-size: 0.82rem;
2351
+ background: var(--aparte-code-bg, #fafafa);
2153
2352
  }
2154
-
2155
- .file-tree-title {
2156
- padding: var(--aparte-space-5, 10px) var(--aparte-space-7, 14px);
2157
- background: var(--aparte-file-tree-header-bg, #f1f5f9);
2158
- border-bottom: var(--aparte-border-width, 1px) solid var(--aparte-file-tree-border, #e2e8f0);
2159
- font-weight: var(--aparte-font-weight-medium, 500);
2160
- color: var(--aparte-file-tree-title, #475569);
2353
+ .aparte-art-card__frame {
2354
+ display: block;
2355
+ width: 100%;
2356
+ height: min(var(--aparte-artifact-frame-height, 480px), var(--aparte-artifact-frame-max, 70vh));
2357
+ border: 0;
2358
+ background: #fff;
2359
+ }
2360
+ .aparte-art-card__pending {
2361
+ display: flex; align-items: center; justify-content: center;
2362
+ height: var(--aparte-artifact-pending-height, 120px);
2363
+ color: var(--aparte-text-muted, rgba(0,0,0,0.5));
2364
+ font-size: 0.85rem;
2365
+ font-style: italic;
2161
2366
  }
2162
-
2163
- .file-tree-content {
2164
- padding: var(--aparte-space-4, 8px) 0;
2367
+ /* ── Binary file artifact (xlsx/pdf/docx) ──────────────────── */
2368
+ .aparte-segment-artifact-file {
2369
+ display: flex; flex-direction: column;
2370
+ margin: 8px 0;
2371
+ border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
2372
+ border-radius: 12px;
2373
+ background: var(--aparte-surface, #fff);
2374
+ overflow: hidden;
2375
+ color: var(--aparte-text, inherit);
2376
+ font: inherit;
2165
2377
  }
2166
-
2167
- .file-node {
2168
- display: flex;
2169
- align-items: center;
2170
- gap: var(--aparte-space-4, 8px);
2171
- padding: var(--aparte-space-2, 4px) var(--aparte-space-7, 14px);
2172
- cursor: default;
2378
+ .aparte-art-file__card {
2379
+ display: flex; align-items: center; gap: 12px;
2380
+ padding: 12px 14px;
2381
+ background: var(--aparte-surface-2, rgba(0,0,0,0.02));
2382
+ border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
2173
2383
  }
2174
-
2175
- .file-node:hover {
2176
- background: var(--aparte-file-tree-hover, #f1f5f9);
2384
+ .aparte-art-file__body {
2385
+ position: relative;
2386
+ }
2387
+ .aparte-art-file__code-pane {
2388
+ max-height: var(--aparte-artifact-file-code-max, 360px); overflow: auto;
2389
+ background: var(--aparte-code-bg, #fafafa);
2390
+ }
2391
+ .aparte-art-file__code-pane pre {
2392
+ margin: 0; padding: 12px;
2393
+ font-size: 0.82rem;
2394
+ font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
2395
+ }
2396
+ .aparte-art-file__preview-pane {
2397
+ max-height: var(--aparte-artifact-file-preview-max, 460px);
2398
+ overflow: auto;
2399
+ background: #fff;
2400
+ /* Preview is a document view — force light scheme regardless of
2401
+ the app theme, with a dark text colour so cells stay readable. */
2402
+ color: #1f2937;
2403
+ }
2404
+ .aparte-art-file__icon {
2405
+ width: 40px; height: 40px;
2406
+ border-radius: 8px;
2407
+ display: flex; align-items: center; justify-content: center;
2408
+ background: linear-gradient(135deg, #1d6f42, #0f5132);
2409
+ color: #fff;
2410
+ font-weight: 700;
2411
+ font-size: 0.78rem;
2412
+ letter-spacing: 0.04em;
2413
+ flex-shrink: 0;
2414
+ }
2415
+ .aparte-art-file__icon[data-kind="pdf"] { background: linear-gradient(135deg, #c0392b, #7d1f17); }
2416
+ .aparte-art-file__icon[data-kind="docx"] { background: linear-gradient(135deg, #1e5288, #0f3060); }
2417
+ .aparte-art-file__meta { flex: 1 1 auto; min-width: 0; }
2418
+ .aparte-art-file__meta-name {
2419
+ font-weight: 600; font-size: 0.92rem;
2420
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2421
+ }
2422
+ .aparte-art-file__meta-sub {
2423
+ font-size: 0.78rem;
2424
+ color: var(--aparte-text-muted, rgba(0,0,0,0.55));
2425
+ }
2426
+ .aparte-art-file__actions { display: flex; gap: 6px; flex-shrink: 0; }
2427
+ .aparte-art-file__btn {
2428
+ border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
2429
+ background: var(--aparte-surface, #fff);
2430
+ color: var(--aparte-text, inherit);
2431
+ padding: 6px 10px;
2432
+ border-radius: 6px;
2433
+ font-size: 0.8rem;
2434
+ cursor: pointer;
2435
+ transition: background 0.12s ease;
2436
+ }
2437
+ .aparte-art-file__btn:hover { background: var(--aparte-surface-2, rgba(0,0,0,0.05)); }
2438
+ .aparte-art-file__btn--primary {
2439
+ background: var(--aparte-primary, #6366f1);
2440
+ color: #fff;
2441
+ border-color: var(--aparte-primary, #6366f1);
2442
+ }
2443
+ .aparte-art-file__btn--primary:hover { filter: brightness(1.07); }
2444
+ /* Hide the download button until the sandbox has produced the buffer.
2445
+ Avoids confusing the user with a disabled-but-styled-primary button
2446
+ during streaming / compiling. */
2447
+ .aparte-segment-artifact-file:not([data-state="ready"]) .aparte-art-file__btn[data-action="download"] {
2448
+ display: none;
2449
+ }
2450
+ .aparte-art-file__preview-pane table {
2451
+ border-collapse: collapse;
2452
+ width: 100%;
2453
+ font-size: 0.82rem;
2454
+ font-family: inherit;
2455
+ }
2456
+ .aparte-art-file__preview-pane th,
2457
+ .aparte-art-file__preview-pane td {
2458
+ border: 1px solid rgba(0,0,0,0.10);
2459
+ padding: 6px 10px;
2460
+ text-align: left;
2461
+ white-space: nowrap;
2462
+ color: #1f2937;
2463
+ background: #fff;
2464
+ }
2465
+ .aparte-art-file__preview-pane tr:nth-child(odd) td { background: #f9fafb; }
2466
+ .aparte-art-file__preview-pane tr:first-child td {
2467
+ background: #f3f4f6;
2468
+ font-weight: 600;
2469
+ position: sticky; top: 0;
2470
+ color: #111827;
2471
+ }
2472
+ .aparte-art-file__preview-empty {
2473
+ padding: 20px; text-align: center;
2474
+ color: var(--aparte-text-muted, rgba(0,0,0,0.5));
2475
+ font-size: 0.85rem; font-style: italic;
2476
+ }
2477
+ .aparte-segment-artifact-file[data-state="error"] .aparte-art-file__icon {
2478
+ background: linear-gradient(135deg, #c0392b, #7d1f17);
2479
+ }
2480
+ .aparte-art-file__error {
2481
+ padding: 16px 18px;
2482
+ background: rgba(239, 68, 68, 0.05);
2483
+ border-top: 1px solid rgba(239, 68, 68, 0.2);
2484
+ color: var(--aparte-text, inherit);
2485
+ }
2486
+ .aparte-art-file__error-title {
2487
+ font-weight: 600;
2488
+ font-size: 0.9rem;
2489
+ margin-bottom: 6px;
2490
+ color: #b91c1c;
2491
+ }
2492
+ .aparte-art-file__error-msg {
2493
+ font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
2494
+ font-size: 0.78rem;
2495
+ padding: 6px 10px;
2496
+ background: rgba(0,0,0,0.04);
2497
+ border-radius: 4px;
2498
+ margin-bottom: 8px;
2499
+ color: #7f1d1d;
2500
+ word-break: break-word;
2501
+ }
2502
+ .aparte-art-file__error-hint {
2503
+ font-size: 0.78rem;
2504
+ color: var(--aparte-text-muted, rgba(0,0,0,0.55));
2505
+ font-style: italic;
2177
2506
  }
2178
2507
 
2179
- .file-node.file-status-added .file-name { color: var(--aparte-file-status-added, #16a34a); }
2180
- .file-node.file-status-modified .file-name { color: var(--aparte-file-status-modified, #0284c7); }
2181
- .file-node.file-status-deleted .file-name { color: var(--aparte-file-status-deleted, #dc2626); text-decoration: line-through; opacity: 0.7; }
2182
-
2183
- .file-icon {
2184
- font-size: var(--aparte-font-size-lg, 14px);
2185
- flex-shrink: 0;
2186
- opacity: 0.7;
2508
+ /* Pipeline waiting */
2509
+ .aparte-segment-pipeline-waiting {
2510
+ display: flex;
2511
+ align-items: center;
2512
+ gap: 5px;
2513
+ padding: 6px 2px;
2514
+ min-height: 28px;
2515
+ }
2516
+ .aparte-pw-dot {
2517
+ display: inline-block;
2518
+ width: 8px;
2519
+ height: 8px;
2520
+ border-radius: 50%;
2521
+ background: var(--aparte-text-muted, #6b7280);
2522
+ opacity: 0.3;
2523
+ animation: pw-pulse 1.2s ease-in-out infinite;
2524
+ }
2525
+ .aparte-pw-dot:nth-child(2) { animation-delay: 0.2s; }
2526
+ .aparte-pw-dot:nth-child(3) { animation-delay: 0.4s; }
2527
+ @keyframes pw-pulse {
2528
+ 0%, 80%, 100% { opacity: 0.3; transform: scale(0.85); }
2529
+ 40% { opacity: 1; transform: scale(1.1); }
2187
2530
  }
2188
2531
 
2189
2532
  /* ─────────────────────────────────────────────────────────────────────────────
@@ -2281,13 +2624,13 @@ aparte-composer-action {
2281
2624
  }
2282
2625
 
2283
2626
  /* ── Drag-over state on root ── */
2284
- aparte-composer.is-dragover .aparte-composer-shell {
2627
+ aparte-composer.aparte-is-dragover .aparte-composer-shell {
2285
2628
  outline: var(--aparte-focus-outline-width, 2px) dashed var(--aparte-primary);
2286
2629
  outline-offset: -2px;
2287
2630
  border-radius: var(--aparte-radius-input, 12px);
2288
2631
  }
2289
2632
  /* fallback: no shell */
2290
- aparte-composer.is-dragover:not(:has(.aparte-composer-shell)) {
2633
+ aparte-composer.aparte-is-dragover:not(:has(.aparte-composer-shell)) {
2291
2634
  outline: var(--aparte-focus-outline-width, 2px) dashed var(--aparte-primary);
2292
2635
  outline-offset: -2px;
2293
2636
  border-radius: var(--aparte-radius-input, 12px);
@@ -2349,6 +2692,23 @@ aparte-composer.is-dragover:not(:has(.aparte-composer-shell)) {
2349
2692
  justify-content: center;
2350
2693
  }
2351
2694
 
2695
+ /* A panel is mounted INSIDE that row, so its own buttons inherited the 44x44 sizing
2696
+ above and rendered as circles with their labels spilling out. The rule above means
2697
+ "the composer's own controls"; anything inside a panel is CONTENT, and a panel is
2698
+ free to contain a button — the approval options are the first that do, and they will
2699
+ not be the last. */
2700
+ [data-aparte-panel] button {
2701
+ width: auto;
2702
+ height: auto;
2703
+ flex-shrink: 1;
2704
+ /* The row also makes its controls centred flex boxes, which beats `text-align` — so a
2705
+ left-aligned label in a panel came out centred until this line existed. Undo the
2706
+ whole treatment, not the half of it that was visible. */
2707
+ display: block;
2708
+ justify-content: initial;
2709
+ align-items: initial;
2710
+ }
2711
+
2352
2712
  /* The composer's bottom row (mode picker, model selector, token counter…).
2353
2713
  `aparte-composer-toolbar` is the element consumers write — one name for vanilla and
2354
2714
  every wrapper. `.aparte-composer-footer` is the class the same row used to be built
@@ -2585,15 +2945,15 @@ aparte-conversation-list {
2585
2945
  Provider-HTML overflow guards
2586
2946
  ========================================= */
2587
2947
 
2588
- /* Markdown providers inject arbitrary HTML into .segment-text, and the bubble
2948
+ /* Markdown providers inject arbitrary HTML into .aparte-segment-text, and the bubble
2589
2949
  clips overflow (`aparte-chat-bubble { overflow: hidden }`) — without these
2590
2950
  guards a wide image or table is silently cut at the bubble edge. */
2591
- .segment-text img {
2951
+ .aparte-segment-text img {
2592
2952
  max-width: 100%;
2593
2953
  height: auto;
2594
2954
  }
2595
2955
 
2596
- .segment-text table {
2956
+ .aparte-segment-text table {
2597
2957
  display: block;
2598
2958
  max-width: 100%;
2599
2959
  overflow-x: auto;