@aparte/core 0.13.1 → 0.14.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.
- package/README.md +2 -2
- package/dist/client/aparte-client.d.ts +36 -35
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +37 -145
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +10 -2
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +6 -6
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +9 -1
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +12 -3
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/context/aparte-context.d.ts +81 -0
- package/dist/components/context/aparte-context.d.ts.map +1 -0
- package/dist/components/context/index.d.ts +3 -0
- package/dist/components/context/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/suggestions/aparte-suggestions.d.ts +95 -0
- package/dist/components/suggestions/aparte-suggestions.d.ts.map +1 -0
- package/dist/components/suggestions/index.d.ts +3 -0
- package/dist/components/suggestions/index.d.ts.map +1 -0
- package/dist/components/viewport/aparte-chat-viewport.d.ts +35 -7
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +1 -21
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/index.d.ts +0 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/locale.d.ts +30 -1
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/conversations/index.d.ts +1 -1
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/types.d.ts +9 -57
- package/dist/conversations/types.d.ts.map +1 -1
- package/dist/custom-elements.json +7818 -7479
- package/dist/generated/element-attributes.d.ts +34 -6
- package/dist/generated/element-attributes.d.ts.map +1 -1
- package/dist/{glyphs-QXfbL3n_.js → glyphs-B3BZ2TIb.js} +21 -21
- package/dist/glyphs-B3BZ2TIb.js.map +1 -0
- package/dist/host/aparte-chat-host.d.ts +1 -1
- package/dist/host/aparte-chat-host.d.ts.map +1 -1
- package/dist/icons/extended.d.ts +87 -85
- package/dist/icons/extended.d.ts.map +1 -1
- package/dist/icons/glyphs.d.ts +53 -50
- package/dist/icons/glyphs.d.ts.map +1 -1
- package/dist/icons.js +41 -41
- package/dist/icons.js.map +1 -1
- package/dist/index.css +512 -170
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +593 -183
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +6 -4
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +56 -56
- package/dist/index.node.js.map +1 -1
- package/dist/{is-awaiting-reply-CWp1ffHh.js → is-awaiting-reply-BZ_iJ3s3.js} +175 -994
- package/dist/is-awaiting-reply-BZ_iJ3s3.js.map +1 -0
- package/dist/parsers/aparte-stream-parser.d.ts +6 -17
- package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +2 -2
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/runtime/message-repository.d.ts +7 -0
- package/dist/runtime/message-repository.d.ts.map +1 -1
- package/dist/transport/types.d.ts +8 -0
- package/dist/transport/types.d.ts.map +1 -1
- package/dist/types/chat.d.ts +32 -38
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/types/errors.d.ts +16 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/event-map.d.ts +4 -0
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/events.d.ts +15 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/models.d.ts +1 -8
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/segments.d.ts +1 -13
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +1 -1
- package/dist/utils/chat-boundary.d.ts +8 -4
- package/dist/utils/chat-boundary.d.ts.map +1 -1
- package/package.json +5 -2
- package/dist/client/xml-artifact-feed.d.ts +0 -76
- package/dist/client/xml-artifact-feed.d.ts.map +0 -1
- package/dist/config/skeleton-provider.d.ts +0 -26
- package/dist/config/skeleton-provider.d.ts.map +0 -1
- package/dist/glyphs-QXfbL3n_.js.map +0 -1
- package/dist/is-awaiting-reply-CWp1ffHh.js.map +0 -1
- package/dist/renderers/segments/pipeline-waiting.d.ts +0 -3
- package/dist/renderers/segments/pipeline-waiting.d.ts.map +0 -1
package/dist/index.css
CHANGED
|
@@ -167,6 +167,10 @@
|
|
|
167
167
|
--aparte-focus-outline-width: 2px;
|
|
168
168
|
/* Scrollbars rendered by the library. */
|
|
169
169
|
--aparte-scrollbar-width: 6px;
|
|
170
|
+
/* The transcript's scrollbar track; the thumb is derived from the neutral, in the
|
|
171
|
+
derived layer. A host page with a scrollbar of its own (the docs site has one)
|
|
172
|
+
sets both so the chat's does not read as a second, foreign scrollbar. */
|
|
173
|
+
--aparte-scrollbar-track: transparent;
|
|
170
174
|
/* Comfortable hit-area on coarse (touch) pointers. */
|
|
171
175
|
--aparte-touch-target-size: 44px;
|
|
172
176
|
|
|
@@ -202,6 +206,11 @@
|
|
|
202
206
|
|
|
203
207
|
/* Action Bar */
|
|
204
208
|
--aparte-action-bar-btn-size: 28px;
|
|
209
|
+
/* The mark (display/mark.css): how much of its intent a marked row's ground takes,
|
|
210
|
+
and how wide the bar on its start edge is. One pair for the select's chosen option,
|
|
211
|
+
the checked field choice, the active conversation and an answered question. */
|
|
212
|
+
--aparte-mark-tint: 18%;
|
|
213
|
+
--aparte-mark-bar: 2px;
|
|
205
214
|
|
|
206
215
|
/* Branch Picker */
|
|
207
216
|
--aparte-branch-picker-btn-size: 20px;
|
|
@@ -387,12 +396,12 @@
|
|
|
387
396
|
a shadow tuned for a light ground is no shadow at all on a dark one, which is how
|
|
388
397
|
the select's dropdown lost its lift in dark mode until it was caught. */
|
|
389
398
|
--aparte-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
390
|
-
--aparte-popover-shadow: 0
|
|
399
|
+
--aparte-popover-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 10px 28px rgba(0, 0, 0, 0.16);
|
|
391
400
|
--aparte-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
392
401
|
--aparte-switch-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
393
402
|
--aparte-range-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
394
403
|
|
|
395
|
-
--aparte-select-shadow: 0
|
|
404
|
+
--aparte-select-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 10px 28px rgba(0, 0, 0, 0.16);
|
|
396
405
|
--aparte-select-min-width: 200px;
|
|
397
406
|
--aparte-select-dropdown-max-height: 300px;
|
|
398
407
|
--aparte-select-status-dot-size: 8px;
|
|
@@ -440,8 +449,6 @@
|
|
|
440
449
|
--aparte-art-file-icon-size: 40px;
|
|
441
450
|
--aparte-art-file-preview-padding: 20px;
|
|
442
451
|
--aparte-art-file-error-padding-inline: 18px;
|
|
443
|
-
--aparte-pw-min-height: 28px;
|
|
444
|
-
--aparte-pw-dot-size: 8px;
|
|
445
452
|
}
|
|
446
453
|
|
|
447
454
|
/* =========================================
|
|
@@ -477,9 +484,9 @@
|
|
|
477
484
|
|
|
478
485
|
/* Scroll-to-bottom button */
|
|
479
486
|
--aparte-scroll-btn-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
|
|
480
|
-
--aparte-select-shadow: 0
|
|
487
|
+
--aparte-select-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 10px 28px rgba(0, 0, 0, 0.55);
|
|
481
488
|
--aparte-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
|
482
|
-
--aparte-popover-shadow: 0
|
|
489
|
+
--aparte-popover-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 10px 28px rgba(0, 0, 0, 0.55);
|
|
483
490
|
--aparte-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
484
491
|
--aparte-switch-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
485
492
|
--aparte-range-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
@@ -602,6 +609,7 @@ aparte-chat {
|
|
|
602
609
|
--aparte-elic-step-padding: var(--aparte-space-2) var(--aparte-space-1);
|
|
603
610
|
--aparte-elic-step-gap: var(--aparte-space-7);
|
|
604
611
|
--aparte-viewport-padding: var(--aparte-space-8);
|
|
612
|
+
--aparte-scrollbar-thumb: var(--aparte-neutral);
|
|
605
613
|
--aparte-input-padding-x: var(--aparte-space-6);
|
|
606
614
|
--aparte-input-padding-y: var(--aparte-space-5);
|
|
607
615
|
--aparte-conv-list-gap: var(--aparte-space-1);
|
|
@@ -980,8 +988,8 @@ aparte-conversation-list {
|
|
|
980
988
|
curve, not a differently-named copy of this one. */
|
|
981
989
|
|
|
982
990
|
/* One pulse for every dot the library shows. There were three keyframes doing the
|
|
983
|
-
same thing at slightly different rhythms;
|
|
984
|
-
|
|
991
|
+
same thing at slightly different rhythms; a travelling wave, where a row wants one,
|
|
992
|
+
comes from an animation-delay on its later dots, not from a skewed curve. */
|
|
985
993
|
@keyframes aparte-pulse {
|
|
986
994
|
0%, 100% { opacity: 0.3; transform: scale(0.85); }
|
|
987
995
|
50% { opacity: 1; transform: scale(1.1); }
|
|
@@ -2076,7 +2084,8 @@ aparte-conversation-list {
|
|
|
2076
2084
|
background var(--aparte-duration-fast) var(--aparte-ease),
|
|
2077
2085
|
border-color var(--aparte-duration-fast) var(--aparte-ease);
|
|
2078
2086
|
}
|
|
2079
|
-
|
|
2087
|
+
/* Not on a chosen row: its mark (below) is what the pointer must not erase. */
|
|
2088
|
+
.aparte-field-choice:not(:has(:checked)):not(.aparte-field-choice--selected):hover { background: var(--aparte-surface-2); }
|
|
2080
2089
|
|
|
2081
2090
|
/* Boxed — each row carries its own edge, for a list that must read as a set of
|
|
2082
2091
|
separate targets rather than a menu. */
|
|
@@ -2084,9 +2093,31 @@ aparte-conversation-list {
|
|
|
2084
2093
|
|
|
2085
2094
|
/* Selected. `:has()` covers the native case with no class to toggle; the explicit
|
|
2086
2095
|
modifier covers a row whose state lives elsewhere — a button acting as an option,
|
|
2087
|
-
where there is no checked input to look at.
|
|
2096
|
+
where there is no checked input to look at. It wears the mark (display/mark.css):
|
|
2097
|
+
the tint and the start bar the select's chosen option wears, so a picked choice in
|
|
2098
|
+
an elicitation panel and a picked option in a dropdown say it the same way. The
|
|
2099
|
+
border stays its own — it was the only signal before, and a boxed row keeps its
|
|
2100
|
+
edge coloured. Written as the mark's declarations rather than its class because a
|
|
2101
|
+
`:has()` state cannot add a class to itself; the modifier is doubled so both halves
|
|
2102
|
+
of this rule sit at (0,2,0), above the hover. The bar is the mark's pseudo-element,
|
|
2103
|
+
on the logical start edge — see display/mark.css for why not a shadow. */
|
|
2088
2104
|
.aparte-field-choice:has(:checked),
|
|
2089
|
-
.aparte-field-choice--selected {
|
|
2105
|
+
.aparte-field-choice.aparte-field-choice--selected {
|
|
2106
|
+
position: relative;
|
|
2107
|
+
border-color: var(--aparte-primary);
|
|
2108
|
+
background: color-mix(in srgb, var(--aparte-primary) var(--aparte-mark-tint), transparent);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
.aparte-field-choice:has(:checked)::before,
|
|
2112
|
+
.aparte-field-choice.aparte-field-choice--selected::before {
|
|
2113
|
+
content: "";
|
|
2114
|
+
position: absolute;
|
|
2115
|
+
inset-block: 0;
|
|
2116
|
+
inset-inline-start: 0;
|
|
2117
|
+
width: var(--aparte-mark-bar);
|
|
2118
|
+
background: var(--aparte-primary-ink);
|
|
2119
|
+
pointer-events: none;
|
|
2120
|
+
}
|
|
2090
2121
|
|
|
2091
2122
|
.aparte-field-choice:has(:disabled),
|
|
2092
2123
|
.aparte-field-choice[aria-disabled='true'] {
|
|
@@ -2512,6 +2543,98 @@ aparte-icon {
|
|
|
2512
2543
|
@media (forced-colors: active) {
|
|
2513
2544
|
.aparte-thumbnail { border-color: currentColor; }
|
|
2514
2545
|
}
|
|
2546
|
+
|
|
2547
|
+
/* ── The attachment strip and its tiles ─────────────────────────────────────────
|
|
2548
|
+
Shared by the composer preview and the strip under a sent message: the bubble
|
|
2549
|
+
emits the very same classes. These rules lived in composer.css, a COMPONENT sheet,
|
|
2550
|
+
so the bubble — which imports no composer — rendered its file chips bare. A recipe
|
|
2551
|
+
lives in the display layer; the composer keeps only what is its own (the ✗ button). */
|
|
2552
|
+
.aparte-attachments {
|
|
2553
|
+
display: flex;
|
|
2554
|
+
flex-wrap: wrap;
|
|
2555
|
+
/* The strip sits directly above the message bubble and belongs to it, so it
|
|
2556
|
+
must share the bubble's edge. The user bubble hugs its text on the LEADING
|
|
2557
|
+
edge (AI-chat convention, see .aparte-message-content below), so anchoring
|
|
2558
|
+
the strip to the trailing edge would split one message across both sides. */
|
|
2559
|
+
justify-content: flex-start;
|
|
2560
|
+
gap: var(--aparte-space-3);
|
|
2561
|
+
margin-bottom: var(--aparte-space-4);
|
|
2562
|
+
/* Really small tiles inside the conversation. */
|
|
2563
|
+
--aparte-attachment-image-size: 40px;
|
|
2564
|
+
/* Many attachments → cap the height and scroll instead of growing forever. */
|
|
2565
|
+
max-height: var(--aparte-attachments-max-height);
|
|
2566
|
+
overflow-y: auto;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
/* The class `display: flex` above would otherwise override the `hidden`
|
|
2570
|
+
attribute — restore it so an empty attachment strip takes no space. */
|
|
2571
|
+
.aparte-attachments[hidden] {
|
|
2572
|
+
display: none;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
/* ── Attachment thumbnail tile ──────────────────────────────────────────────
|
|
2576
|
+
Shared by the composer attachment preview and the sent-message attachment
|
|
2577
|
+
strip. A square that shows the image itself (or a file glyph); the filename
|
|
2578
|
+
and — in the composer — a remove button surface on hover. */
|
|
2579
|
+
/* The tile is `.aparte-thumbnail`; the composer only sets its own measurements and
|
|
2580
|
+
the quieter ground an attachment strip uses. */
|
|
2581
|
+
.aparte-thumb {
|
|
2582
|
+
--aparte-thumbnail-size: var(--aparte-attachment-image-size);
|
|
2583
|
+
--aparte-thumbnail-radius: var(--aparte-thumb-radius);
|
|
2584
|
+
--aparte-thumbnail-bg: var(--aparte-attachment-chip-bg);
|
|
2585
|
+
--aparte-thumbnail-border: var(--aparte-attachment-chip-border);
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
/* Only an image tile the app declared it can preview is a button — see
|
|
2589
|
+
aparteGlobalConfig.setHostHandlers(). Undeclared, it is a picture, not a control. */
|
|
2590
|
+
.aparte-thumb--image[role='button'] {
|
|
2591
|
+
cursor: pointer;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
.aparte-thumb--image[role='button']:focus-visible {
|
|
2595
|
+
box-shadow: var(--aparte-focus-ring);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
.aparte-thumb__img {
|
|
2599
|
+
width: 100%;
|
|
2600
|
+
height: 100%;
|
|
2601
|
+
object-fit: cover;
|
|
2602
|
+
display: block;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
.aparte-thumb--file {
|
|
2606
|
+
display: flex;
|
|
2607
|
+
align-items: center;
|
|
2608
|
+
justify-content: center;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
.aparte-thumb__ext {
|
|
2612
|
+
font-size: var(--aparte-font-size-xs);
|
|
2613
|
+
font-weight: var(--aparte-font-weight-bold);
|
|
2614
|
+
letter-spacing: 0.03em;
|
|
2615
|
+
color: var(--aparte-text-muted);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
.aparte-thumb__name {
|
|
2619
|
+
position: absolute;
|
|
2620
|
+
left: 0;
|
|
2621
|
+
right: 0;
|
|
2622
|
+
bottom: 0;
|
|
2623
|
+
padding: var(--aparte-thumb-name-padding);
|
|
2624
|
+
font-size: var(--aparte-font-size-2xs);
|
|
2625
|
+
line-height: var(--aparte-line-height-tight);
|
|
2626
|
+
color: var(--aparte-thumb-name-color);
|
|
2627
|
+
background: var(--aparte-thumb-name-scrim);
|
|
2628
|
+
white-space: nowrap;
|
|
2629
|
+
overflow: hidden;
|
|
2630
|
+
text-overflow: ellipsis;
|
|
2631
|
+
opacity: 0;
|
|
2632
|
+
transition: opacity var(--aparte-duration-base) var(--aparte-ease);
|
|
2633
|
+
pointer-events: none;
|
|
2634
|
+
}
|
|
2635
|
+
.aparte-thumb:hover .aparte-thumb__name {
|
|
2636
|
+
opacity: 1;
|
|
2637
|
+
}
|
|
2515
2638
|
/* =========================================
|
|
2516
2639
|
aparte-spinner — a rotating indicator
|
|
2517
2640
|
=========================================
|
|
@@ -2653,29 +2776,6 @@ aparte-icon {
|
|
|
2653
2776
|
0% { background-position: 200% 0; }
|
|
2654
2777
|
100% { background-position: -200% 0; }
|
|
2655
2778
|
}
|
|
2656
|
-
|
|
2657
|
-
/* ── The zero-dependency fallbacks ────────────────────────────────────────────
|
|
2658
|
-
What `getSkeleton()` returns when no skeleton provider is registered. They used
|
|
2659
|
-
to carry their look in a `style=""` attribute — six inline declarations of
|
|
2660
|
-
Tailwind-slate hex (`#9ca3af`, `#1e293b`, `#64748b`), the exact palette this
|
|
2661
|
-
theme replaced. Three consequences, and the first is the one that matters: an
|
|
2662
|
-
inline style cannot be overridden by a consumer's stylesheet, so a dark-theme
|
|
2663
|
-
host got a light-grey label it had no way to reach. The second: hex in a `.ts`
|
|
2664
|
-
is invisible to `check:derived-vars`, which reads only `styles/`. The third:
|
|
2665
|
-
they had drifted from the recipe above, which already reads tokens.
|
|
2666
|
-
|
|
2667
|
-
Structure only, still — no shimmer, no animation. That is what makes them the
|
|
2668
|
-
fallback rather than a second skeleton implementation. */
|
|
2669
|
-
.aparte-skeleton-fallback {
|
|
2670
|
-
padding: var(--aparte-space-4);
|
|
2671
|
-
color: var(--aparte-text-muted);
|
|
2672
|
-
}
|
|
2673
|
-
.aparte-skeleton-fallback--snug { padding: var(--aparte-space-3); }
|
|
2674
|
-
.aparte-skeleton-fallback--tight { padding: var(--aparte-space-2); }
|
|
2675
|
-
.aparte-skeleton-fallback--code {
|
|
2676
|
-
background: var(--aparte-surface-2);
|
|
2677
|
-
border-radius: var(--aparte-radius-md);
|
|
2678
|
-
}
|
|
2679
2779
|
/* =========================================
|
|
2680
2780
|
aparte-divider — horizontal, vertical, optionally labelled
|
|
2681
2781
|
=========================================
|
|
@@ -2914,6 +3014,87 @@ aparte-icon {
|
|
|
2914
3014
|
line-height: var(--aparte-line-height-none);
|
|
2915
3015
|
}
|
|
2916
3016
|
|
|
3017
|
+
/*
|
|
3018
|
+
* aparté — The mark: a row a list has settled on.
|
|
3019
|
+
*
|
|
3020
|
+
* One of the sheets split out of aparte.css. Order matters: src/index.ts imports
|
|
3021
|
+
* them in the order listed there, and check:derived-vars reads them concatenated in
|
|
3022
|
+
* that same order, the way a browser does.
|
|
3023
|
+
*
|
|
3024
|
+
* WHAT IT IS. A chosen option, an accepted answer, the conversation you are in: the
|
|
3025
|
+
* one item among its peers that a decision landed on. Before this sheet the select
|
|
3026
|
+
* drew it one way (an accent tint plus an inset bar on the start edge), the field
|
|
3027
|
+
* choice another (a primary border), the conversation list a third (its own
|
|
3028
|
+
* background and weight), and the ask-user receipt none at all — four vocabularies
|
|
3029
|
+
* for one meaning, measured in the UI audit of 2026-08-28. The market agrees on two
|
|
3030
|
+
* signals: a tint on the ground (Radix, shadcn, Material's state layer) and a bar on
|
|
3031
|
+
* the row's start edge (GitHub Primer's ActionList). This recipe is both, on one
|
|
3032
|
+
* intent axis, and every row that means "this one" wears it.
|
|
3033
|
+
*
|
|
3034
|
+
* THE AXIS. `--aparte-mark-intent` is the fill, primary by default: chosen. The
|
|
3035
|
+
* modifiers set it to the theme's other masters — success for an answer that was
|
|
3036
|
+
* given or a call that was approved, danger for a destructive outcome, neutral for
|
|
3037
|
+
* the item you are merely in. `--quiet` is the outcome that did NOT happen: a declined
|
|
3038
|
+
* request, a version superseded — no tint, no bar, the muted voice. The market never
|
|
3039
|
+
* strikes through; neither does this.
|
|
3040
|
+
*
|
|
3041
|
+
* THE BAR IS DRAWN IN INK. The tint is the fill at 18%, which carries almost no
|
|
3042
|
+
* contrast by itself (1.2:1 in the light theme); the bar is the whole non-text signal,
|
|
3043
|
+
* so it reads `--aparte-mark-intent-ink` — the master's ink, the one the button and
|
|
3044
|
+
* the badge already derive for text on a ground. The raw success fill measured 2.27:1
|
|
3045
|
+
* on the light surface, under the 3:1 WCAG asks of a graphic; its ink is 7.1:1.
|
|
3046
|
+
*
|
|
3047
|
+
* THE BAR IS A PSEUDO-ELEMENT, NOT A SHADOW. An inset shadow knows only left and
|
|
3048
|
+
* right, so a right-to-left row got its bar on the wrong edge unless a document-level
|
|
3049
|
+
* `[dir="rtl"]` rule caught it — which a `dir` on the row itself, or `dir="auto"`,
|
|
3050
|
+
* escapes. `inset-inline-start` is the start edge whatever the direction, at any level.
|
|
3051
|
+
* Nothing shifts: the pseudo-element is out of the flow.
|
|
3052
|
+
*
|
|
3053
|
+
* THE MEASURES. `--aparte-mark-tint` and `--aparte-mark-bar` live in theme.css so a
|
|
3054
|
+
* theme moves every mark at once.
|
|
3055
|
+
*/
|
|
3056
|
+
.aparte-mark {
|
|
3057
|
+
--aparte-mark-intent: var(--aparte-primary);
|
|
3058
|
+
--aparte-mark-intent-ink: var(--aparte-primary-ink);
|
|
3059
|
+
position: relative;
|
|
3060
|
+
background: color-mix(in srgb, var(--aparte-mark-intent) var(--aparte-mark-tint), transparent);
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
.aparte-mark::before {
|
|
3064
|
+
content: "";
|
|
3065
|
+
position: absolute;
|
|
3066
|
+
inset-block: 0;
|
|
3067
|
+
inset-inline-start: 0;
|
|
3068
|
+
width: var(--aparte-mark-bar);
|
|
3069
|
+
background: var(--aparte-mark-intent-ink);
|
|
3070
|
+
pointer-events: none;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
.aparte-mark--success {
|
|
3074
|
+
--aparte-mark-intent: var(--aparte-success);
|
|
3075
|
+
--aparte-mark-intent-ink: var(--aparte-success-ink);
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
.aparte-mark--danger {
|
|
3079
|
+
--aparte-mark-intent: var(--aparte-error);
|
|
3080
|
+
--aparte-mark-intent-ink: var(--aparte-error-ink);
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
/* The neutral master has no derived ink; the fill is its own ink (5:1 light, 6.9:1 dark). */
|
|
3084
|
+
.aparte-mark--neutral {
|
|
3085
|
+
--aparte-mark-intent: var(--aparte-neutral);
|
|
3086
|
+
--aparte-mark-intent-ink: var(--aparte-neutral);
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
/* The outcome that did not happen. */
|
|
3090
|
+
.aparte-mark--quiet {
|
|
3091
|
+
background: none;
|
|
3092
|
+
color: var(--aparte-text-muted);
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
.aparte-mark--quiet::before {
|
|
3096
|
+
display: none;
|
|
3097
|
+
}
|
|
2917
3098
|
/*
|
|
2918
3099
|
* aparté — layered surfaces. Ready-made classes for the things that float ABOVE the
|
|
2919
3100
|
* transcript: tabs, an accordion, a dropdown menu, a popover, a tooltip.
|
|
@@ -3566,14 +3747,47 @@ aparte-select[disabled] .aparte-select-trigger {
|
|
|
3566
3747
|
cursor: not-allowed;
|
|
3567
3748
|
}
|
|
3568
3749
|
|
|
3750
|
+
/* The label is a grid with ONE cell: the visible text and, under it, a hidden stack of
|
|
3751
|
+
every option's label. The stack sets the cell's width — the widest option's — so the
|
|
3752
|
+
control keeps one width whatever is selected, like a native <select>; the text layer
|
|
3753
|
+
ellipsises when a host constrains the control below that (`min-width: 0` is what lets
|
|
3754
|
+
a flex item shrink at all). */
|
|
3569
3755
|
.aparte-select-label {
|
|
3570
3756
|
flex: 1;
|
|
3757
|
+
min-width: 0;
|
|
3758
|
+
display: grid;
|
|
3571
3759
|
overflow: hidden;
|
|
3572
|
-
text-overflow: ellipsis;
|
|
3573
|
-
white-space: nowrap;
|
|
3574
3760
|
color: var(--aparte-select-text, var(--aparte-text));
|
|
3575
3761
|
}
|
|
3576
3762
|
|
|
3763
|
+
.aparte-select-label > * {
|
|
3764
|
+
grid-area: 1 / 1;
|
|
3765
|
+
white-space: nowrap;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
.aparte-select-label-text {
|
|
3769
|
+
overflow: hidden;
|
|
3770
|
+
text-overflow: ellipsis;
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
/* `min-width: 0; overflow: hidden` on the stack is what keeps the ellipsis alive: a
|
|
3774
|
+
grid item's automatic minimum size is its min-content — the widest nowrap label —
|
|
3775
|
+
and that floored the label's track, so a host that constrained the control got a
|
|
3776
|
+
hard clip mid-glyph instead of "…" (measured in Chromium: clientWidth 244 for a
|
|
3777
|
+
140px control). Its max-content contribution, the one that sets the width, is
|
|
3778
|
+
untouched. */
|
|
3779
|
+
.aparte-select-label-sizer {
|
|
3780
|
+
display: grid;
|
|
3781
|
+
min-width: 0;
|
|
3782
|
+
overflow: hidden;
|
|
3783
|
+
visibility: hidden;
|
|
3784
|
+
pointer-events: none;
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
.aparte-select-label-sizer > span {
|
|
3788
|
+
grid-area: 1 / 1;
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3577
3791
|
.aparte-select-chevron {
|
|
3578
3792
|
--aparte-icon-size: var(--aparte-icon-size-sm);
|
|
3579
3793
|
flex-shrink: 0;
|
|
@@ -3647,15 +3861,30 @@ aparte-option[data-active] {
|
|
|
3647
3861
|
box-shadow: inset 0 0 0 2px var(--aparte-primary);
|
|
3648
3862
|
}
|
|
3649
3863
|
|
|
3650
|
-
/* Selected option: an accent TINT plus an accent bar
|
|
3651
|
-
text colour. A solid `--aparte-primary`
|
|
3652
|
-
both themes (the brass accent is
|
|
3653
|
-
and an unreadable selected row is the
|
|
3654
|
-
|
|
3864
|
+
/* Selected option: the mark (display/mark.css) — an accent TINT plus an accent bar
|
|
3865
|
+
on the start edge, keeping the theme's own text colour. A solid `--aparte-primary`
|
|
3866
|
+
fill with white text failed WCAG AA in both themes (the brass accent is
|
|
3867
|
+
mid-luminance, so white sits around 3.4:1), and an unreadable selected row is the
|
|
3868
|
+
one row that must be readable. The measures are the mark's tokens, so this option,
|
|
3869
|
+
a checked field choice and the active conversation move together; the option's own
|
|
3870
|
+
two tokens stay overridable for a design that wants the solid fill back. Not the
|
|
3871
|
+
class: an option's selected state is an attribute the select writes, and a second
|
|
3872
|
+
name for it on the same element would be one more thing to keep in step. */
|
|
3655
3873
|
aparte-option[selected] {
|
|
3656
|
-
|
|
3874
|
+
position: relative;
|
|
3875
|
+
background: var(--aparte-select-option-selected, color-mix(in srgb, var(--aparte-primary) var(--aparte-mark-tint), transparent));
|
|
3657
3876
|
color: var(--aparte-select-option-selected-text, var(--aparte-select-text, var(--aparte-text)));
|
|
3658
|
-
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
/* The bar: the mark's pseudo-element on the logical start edge (see display/mark.css). */
|
|
3880
|
+
aparte-option[selected]::before {
|
|
3881
|
+
content: "";
|
|
3882
|
+
position: absolute;
|
|
3883
|
+
inset-block: 0;
|
|
3884
|
+
inset-inline-start: 0;
|
|
3885
|
+
width: var(--aparte-mark-bar);
|
|
3886
|
+
background: var(--aparte-primary-ink);
|
|
3887
|
+
pointer-events: none;
|
|
3659
3888
|
}
|
|
3660
3889
|
|
|
3661
3890
|
aparte-option[disabled] {
|
|
@@ -3720,16 +3949,12 @@ aparte-optgroup[collapsed] .aparte-optgroup-chevron {
|
|
|
3720
3949
|
transform: rotate(-90deg);
|
|
3721
3950
|
}
|
|
3722
3951
|
|
|
3723
|
-
/*
|
|
3724
|
-
[data-aparte-theme
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
[data-aparte-theme="dark"] aparte-option:hover {
|
|
3731
|
-
background: var(--aparte-select-option-hover, var(--aparte-surface-2));
|
|
3732
|
-
}
|
|
3952
|
+
/* No dark block: every colour above reads a token the derived layer re-anchors on
|
|
3953
|
+
`[data-aparte-theme]`, so `--aparte-surface-1` IS the dark surface there. The block
|
|
3954
|
+
that used to sit here restated the trigger's and the panel's backgrounds from
|
|
3955
|
+
`--aparte-select-bg` — the TRIGGER's token — so a consumer making the trigger
|
|
3956
|
+
transparent (a pill on a coloured page) got a see-through panel in the dark, with
|
|
3957
|
+
the page's text showing through the options. */
|
|
3733
3958
|
|
|
3734
3959
|
.aparte-optgroup-loader {
|
|
3735
3960
|
padding: var(--aparte-space-4) var(--aparte-space-6);
|
|
@@ -3896,7 +4121,7 @@ aparte-chat-viewport {
|
|
|
3896
4121
|
overflow-x: hidden;
|
|
3897
4122
|
overscroll-behavior: contain;
|
|
3898
4123
|
scrollbar-width: thin;
|
|
3899
|
-
scrollbar-color: var(--aparte-
|
|
4124
|
+
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
3900
4125
|
}
|
|
3901
4126
|
|
|
3902
4127
|
.aparte-viewport-container::-webkit-scrollbar {
|
|
@@ -3904,7 +4129,14 @@ aparte-chat-viewport {
|
|
|
3904
4129
|
}
|
|
3905
4130
|
|
|
3906
4131
|
.aparte-viewport-container::-webkit-scrollbar-track {
|
|
3907
|
-
background:
|
|
4132
|
+
background: var(--aparte-scrollbar-track);
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
/* The thumb too: the token pair was half-wired on the legacy WebKit path — the
|
|
4136
|
+
standard `scrollbar-color` read both, this block read only the track. */
|
|
4137
|
+
.aparte-viewport-container::-webkit-scrollbar-thumb,
|
|
4138
|
+
aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
4139
|
+
background: var(--aparte-scrollbar-thumb);
|
|
3908
4140
|
}
|
|
3909
4141
|
|
|
3910
4142
|
/* ─── Scroll-to-bottom button ─────────────────────────────────────────────── */
|
|
@@ -3963,7 +4195,7 @@ aparte-chat-viewport[framework-managed] {
|
|
|
3963
4195
|
padding: var(--aparte-viewport-padding);
|
|
3964
4196
|
padding-bottom: calc(var(--aparte-viewport-padding) + var(--aparte-fw-spacer, 0px));
|
|
3965
4197
|
scrollbar-width: thin;
|
|
3966
|
-
scrollbar-color: var(--aparte-
|
|
4198
|
+
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
3967
4199
|
}
|
|
3968
4200
|
|
|
3969
4201
|
/* The framework wrapper renders bubbles as DIRECT flex children of the viewport
|
|
@@ -3981,7 +4213,7 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar {
|
|
|
3981
4213
|
}
|
|
3982
4214
|
|
|
3983
4215
|
aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-track {
|
|
3984
|
-
background:
|
|
4216
|
+
background: var(--aparte-scrollbar-track);
|
|
3985
4217
|
}
|
|
3986
4218
|
|
|
3987
4219
|
/* The scroll button lives INSIDE the scrolling host here (last child), so it
|
|
@@ -4127,6 +4359,8 @@ aparte-chat-bubble {
|
|
|
4127
4359
|
max-width: var(--aparte-message-max-width);
|
|
4128
4360
|
margin: 0 auto;
|
|
4129
4361
|
min-width: 0;
|
|
4362
|
+
/* The containing block of a floating footer — see the action bar below. */
|
|
4363
|
+
position: relative;
|
|
4130
4364
|
}
|
|
4131
4365
|
|
|
4132
4366
|
/* The avatar's shape, size and type live in `display.css` — a message avatar IS an
|
|
@@ -4222,6 +4456,58 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
4222
4456
|
display: none;
|
|
4223
4457
|
}
|
|
4224
4458
|
|
|
4459
|
+
/* An older message reserves no row for its actions.
|
|
4460
|
+
*
|
|
4461
|
+
* Measured (UI audit 2026-08-28, §8.3 #7): 103px between the text of one turn and the
|
|
4462
|
+
* text of the next, and 34 of them were this footer — 28px of buttons nobody could
|
|
4463
|
+
* see plus its margin, under EVERY message, so that the bar could appear on hover
|
|
4464
|
+
* without moving anything. The market floats it instead. It cannot float below the
|
|
4465
|
+
* message here: the bubble is a paint-containment boundary (`content-visibility`),
|
|
4466
|
+
* so anything outside its box is clipped. It floats INSIDE the box, over the header
|
|
4467
|
+
* row's empty end — the one place it overlaps no text — as the small bordered
|
|
4468
|
+
* toolbar a Slack message shows on hover.
|
|
4469
|
+
*
|
|
4470
|
+
* Three footers stay in the flow. The last assistant message: its bar is always on
|
|
4471
|
+
* show, under the reply, where every chat puts it. A message whose branch picker is
|
|
4472
|
+
* visible (`data-branches`, written by the bubble beside the picker's own state): the
|
|
4473
|
+
* picker must stay in view. And every message under a pointer that cannot hover, or
|
|
4474
|
+
* that is coarse — `(hover: hover) and (pointer: fine)`, because the two features are
|
|
4475
|
+
* independent and a TV remote reports both hover and coarse; responsive.css gives a
|
|
4476
|
+
* coarse pointer a 44px bar in the flow, and floated over the header row a 44px bar
|
|
4477
|
+
* would sit on the first line of the reply.
|
|
4478
|
+
*
|
|
4479
|
+
* The bar's place depends on the header row: 8px from the top, it fills the empty end
|
|
4480
|
+
* of the "Assistant · 12:34" line and clears the text by the header's height. A host
|
|
4481
|
+
* that hides `.aparte-header` moves that line away and should move the bar too
|
|
4482
|
+
* (`.aparte-footer { top: … }`), or keep it in the flow.
|
|
4483
|
+
*
|
|
4484
|
+
* Invisible but NOT inert, on purpose. `opacity: 0` keeps the bar hit-testable, so a
|
|
4485
|
+
* drag-select started on the empty end of the header row begins on a button nobody
|
|
4486
|
+
* sees — a review flagged it, and `pointer-events: none` until `:hover` was tried. It
|
|
4487
|
+
* made the bar unreachable to anything that clicks without hovering first: Playwright
|
|
4488
|
+
* (which hit-tests the point before it moves the mouse — the header "intercepted" the
|
|
4489
|
+
* edit button on four projects), and with it voice control and switch access, which
|
|
4490
|
+
* synthesise a click the same way. A control only a hover can enable is a control
|
|
4491
|
+
* some users cannot reach; a phantom button on an empty header end is not. */
|
|
4492
|
+
@media (hover: hover) and (pointer: fine) {
|
|
4493
|
+
aparte-chat-bubble:not(:last-of-type) .aparte-message:not([data-branches]) .aparte-footer,
|
|
4494
|
+
aparte-chat-bubble:last-of-type .aparte-message[data-role="user"]:not([data-branches]) .aparte-footer {
|
|
4495
|
+
position: absolute;
|
|
4496
|
+
top: var(--aparte-space-4);
|
|
4497
|
+
inset-inline-end: var(--aparte-space-6);
|
|
4498
|
+
margin: 0;
|
|
4499
|
+
z-index: 1;
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
aparte-chat-bubble:not(:last-of-type) .aparte-message:not([data-branches]) .aparte-action-bar,
|
|
4503
|
+
aparte-chat-bubble:last-of-type .aparte-message[data-role="user"]:not([data-branches]) .aparte-action-bar {
|
|
4504
|
+
background: var(--aparte-surface-1);
|
|
4505
|
+
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
4506
|
+
border-radius: var(--aparte-radius-md);
|
|
4507
|
+
box-shadow: var(--aparte-popover-shadow);
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
|
|
4225
4511
|
.aparte-action-btn {
|
|
4226
4512
|
color: var(--aparte-action-bar-btn-color);
|
|
4227
4513
|
--aparte-btn-radius: var(--aparte-radius-action-btn);
|
|
@@ -4336,6 +4622,15 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
4336
4622
|
.aparte-message[data-error] .aparte-avatar {
|
|
4337
4623
|
box-shadow: var(--aparte-avatar-error-ring);
|
|
4338
4624
|
}
|
|
4625
|
+
|
|
4626
|
+
/* An empty, settled turn renders no chrome. The bubble sets `data-empty` when it has
|
|
4627
|
+
no content, no segments, no attachments and is not streaming — a tool-only turn, or
|
|
4628
|
+
one stopped before its first token — and the whole row goes, name and timestamp
|
|
4629
|
+
included, instead of an orphan header floating over nothing. The element stays in
|
|
4630
|
+
the DOM (streaming and the action bar address it by id), it just does not paint. */
|
|
4631
|
+
.aparte-message[data-empty] {
|
|
4632
|
+
display: none;
|
|
4633
|
+
}
|
|
4339
4634
|
/*
|
|
4340
4635
|
* aparté — The composer and everything it holds: editor, send, attachments, model picker.
|
|
4341
4636
|
*
|
|
@@ -4404,92 +4699,6 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
4404
4699
|
Attachments
|
|
4405
4700
|
========================================= */
|
|
4406
4701
|
|
|
4407
|
-
.aparte-attachments {
|
|
4408
|
-
display: flex;
|
|
4409
|
-
flex-wrap: wrap;
|
|
4410
|
-
/* The strip sits directly above the message bubble and belongs to it, so it
|
|
4411
|
-
must share the bubble's edge. The user bubble hugs its text on the LEADING
|
|
4412
|
-
edge (AI-chat convention, see .aparte-message-content below), so anchoring
|
|
4413
|
-
the strip to the trailing edge would split one message across both sides. */
|
|
4414
|
-
justify-content: flex-start;
|
|
4415
|
-
gap: var(--aparte-space-3);
|
|
4416
|
-
margin-bottom: var(--aparte-space-4);
|
|
4417
|
-
/* Really small tiles inside the conversation. */
|
|
4418
|
-
--aparte-attachment-image-size: 40px;
|
|
4419
|
-
/* Many attachments → cap the height and scroll instead of growing forever. */
|
|
4420
|
-
max-height: var(--aparte-attachments-max-height);
|
|
4421
|
-
overflow-y: auto;
|
|
4422
|
-
}
|
|
4423
|
-
|
|
4424
|
-
/* The class `display: flex` above would otherwise override the `hidden`
|
|
4425
|
-
attribute — restore it so an empty attachment strip takes no space. */
|
|
4426
|
-
.aparte-attachments[hidden] {
|
|
4427
|
-
display: none;
|
|
4428
|
-
}
|
|
4429
|
-
|
|
4430
|
-
/* ── Attachment thumbnail tile ──────────────────────────────────────────────
|
|
4431
|
-
Shared by the composer attachment preview and the sent-message attachment
|
|
4432
|
-
strip. A square that shows the image itself (or a file glyph); the filename
|
|
4433
|
-
and — in the composer — a remove button surface on hover. */
|
|
4434
|
-
/* The tile is `.aparte-thumbnail`; the composer only sets its own measurements and
|
|
4435
|
-
the quieter ground an attachment strip uses. */
|
|
4436
|
-
.aparte-thumb {
|
|
4437
|
-
--aparte-thumbnail-size: var(--aparte-attachment-image-size);
|
|
4438
|
-
--aparte-thumbnail-radius: var(--aparte-thumb-radius);
|
|
4439
|
-
--aparte-thumbnail-bg: var(--aparte-attachment-chip-bg);
|
|
4440
|
-
--aparte-thumbnail-border: var(--aparte-attachment-chip-border);
|
|
4441
|
-
}
|
|
4442
|
-
|
|
4443
|
-
/* Only an image tile the app declared it can preview is a button — see
|
|
4444
|
-
aparteGlobalConfig.setHostHandlers(). Undeclared, it is a picture, not a control. */
|
|
4445
|
-
.aparte-thumb--image[role='button'] {
|
|
4446
|
-
cursor: pointer;
|
|
4447
|
-
}
|
|
4448
|
-
|
|
4449
|
-
.aparte-thumb--image[role='button']:focus-visible {
|
|
4450
|
-
box-shadow: var(--aparte-focus-ring);
|
|
4451
|
-
}
|
|
4452
|
-
|
|
4453
|
-
.aparte-thumb__img {
|
|
4454
|
-
width: 100%;
|
|
4455
|
-
height: 100%;
|
|
4456
|
-
object-fit: cover;
|
|
4457
|
-
display: block;
|
|
4458
|
-
}
|
|
4459
|
-
|
|
4460
|
-
.aparte-thumb--file {
|
|
4461
|
-
display: flex;
|
|
4462
|
-
align-items: center;
|
|
4463
|
-
justify-content: center;
|
|
4464
|
-
}
|
|
4465
|
-
|
|
4466
|
-
.aparte-thumb__ext {
|
|
4467
|
-
font-size: var(--aparte-font-size-xs);
|
|
4468
|
-
font-weight: var(--aparte-font-weight-bold);
|
|
4469
|
-
letter-spacing: 0.03em;
|
|
4470
|
-
color: var(--aparte-text-muted);
|
|
4471
|
-
}
|
|
4472
|
-
|
|
4473
|
-
.aparte-thumb__name {
|
|
4474
|
-
position: absolute;
|
|
4475
|
-
left: 0;
|
|
4476
|
-
right: 0;
|
|
4477
|
-
bottom: 0;
|
|
4478
|
-
padding: var(--aparte-thumb-name-padding);
|
|
4479
|
-
font-size: var(--aparte-font-size-2xs);
|
|
4480
|
-
line-height: var(--aparte-line-height-tight);
|
|
4481
|
-
color: var(--aparte-thumb-name-color);
|
|
4482
|
-
background: var(--aparte-thumb-name-scrim);
|
|
4483
|
-
white-space: nowrap;
|
|
4484
|
-
overflow: hidden;
|
|
4485
|
-
text-overflow: ellipsis;
|
|
4486
|
-
opacity: 0;
|
|
4487
|
-
transition: opacity var(--aparte-duration-base) var(--aparte-ease);
|
|
4488
|
-
pointer-events: none;
|
|
4489
|
-
}
|
|
4490
|
-
.aparte-thumb:hover .aparte-thumb__name {
|
|
4491
|
-
opacity: 1;
|
|
4492
|
-
}
|
|
4493
4702
|
|
|
4494
4703
|
.aparte-thumb__remove {
|
|
4495
4704
|
/* Icon size comes from `--sm` — see `--aparte-btn-icon-size` in button.css. */
|
|
@@ -4962,7 +5171,7 @@ aparte-composer-cancel .aparte-cc-button:hover {
|
|
|
4962
5171
|
flex: 0 0 auto;
|
|
4963
5172
|
}
|
|
4964
5173
|
/*
|
|
4965
|
-
* aparté — Segment renderers: thinking, code, tool call, error
|
|
5174
|
+
* aparté — Segment renderers: thinking, code, tool call, error.
|
|
4966
5175
|
*
|
|
4967
5176
|
* One of the sheets split out of aparte.css. Order matters: src/index.ts imports
|
|
4968
5177
|
* them in the order listed there, and check:derived-vars reads them concatenated in
|
|
@@ -5176,8 +5385,13 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5176
5385
|
0,2,0 beats the base rule's 0,1,0 and source order puts rejected/aborted last. Core
|
|
5177
5386
|
itself documents data-status as a free-form host attribute on <aparte-option>. */
|
|
5178
5387
|
.aparte-segment-tool-call[data-status="resolved"] .aparte-tool-state { color: var(--aparte-success-ink); }
|
|
5179
|
-
.
|
|
5180
|
-
|
|
5388
|
+
/* Rejected and aborted have NO rule: they keep the base's muted ink. They used to
|
|
5389
|
+
share one — red, the error ink — which said "something went wrong" about a call the
|
|
5390
|
+
user declined or a run they stopped, and made the two indistinguishable. The market
|
|
5391
|
+
(UI audit 2026-08-28, coherence pass) keeps red for what is destructive and shows an
|
|
5392
|
+
outcome that did not happen in the muted voice with a glyph: the renderer draws a
|
|
5393
|
+
cross for rejected and a stop square for aborted, so the word and the glyph tell them
|
|
5394
|
+
apart, not a colour that meant a third thing. */
|
|
5181
5395
|
/* The Approve / Reject button rules were here. They moved with the buttons, to
|
|
5182
5396
|
.aparte-approval-option in components/elicitation.css - and what stayed behind was a
|
|
5183
5397
|
leftover: a rule tinting .aparte-tool-label's border while a decision was pending,
|
|
@@ -5235,7 +5449,7 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5235
5449
|
}
|
|
5236
5450
|
|
|
5237
5451
|
/* ─────────────────────────────────────────────────────────────────────────────
|
|
5238
|
-
Segment renderers — tool call · artifact card
|
|
5452
|
+
Segment renderers — tool call · artifact card
|
|
5239
5453
|
|
|
5240
5454
|
These three shipped their CSS from `getStyles()` until now: a template literal
|
|
5241
5455
|
in the renderer's own .ts file, injected into <head> at runtime. That seam is
|
|
@@ -5265,27 +5479,6 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5265
5479
|
───────────────────────────────────────────────────────────────────────────── */
|
|
5266
5480
|
|
|
5267
5481
|
/* Tool call */
|
|
5268
|
-
.aparte-segment-pipeline-waiting {
|
|
5269
|
-
display: flex;
|
|
5270
|
-
align-items: center;
|
|
5271
|
-
gap: var(--aparte-space-3);
|
|
5272
|
-
padding: var(--aparte-space-3) var(--aparte-space-1);
|
|
5273
|
-
min-height: var(--aparte-pw-min-height);
|
|
5274
|
-
}
|
|
5275
|
-
/* The geometry is `.aparte-dot`. What stays is this row's own: its size, its resting
|
|
5276
|
-
colour, and a STAGGERED pulse — three dots on delays read as a wave travelling, which
|
|
5277
|
-
a symmetric breath does not. */
|
|
5278
|
-
.aparte-pw-dot {
|
|
5279
|
-
--aparte-status-dot-size: var(--aparte-pw-dot-size);
|
|
5280
|
-
--aparte-status-color: var(--aparte-text-muted);
|
|
5281
|
-
opacity: 0.3;
|
|
5282
|
-
}
|
|
5283
|
-
.aparte-pw-dot:nth-child(2) { animation-delay: var(--aparte-duration-slow); }
|
|
5284
|
-
.aparte-pw-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
5285
|
-
|
|
5286
|
-
/* =========================================
|
|
5287
|
-
Provider-HTML overflow guards
|
|
5288
|
-
========================================= */
|
|
5289
5482
|
/* Markdown providers inject arbitrary HTML into .aparte-segment-text, and the bubble
|
|
5290
5483
|
clips overflow (`aparte-chat-bubble { overflow: hidden }`) — without these
|
|
5291
5484
|
guards a wide image or table is silently cut at the bubble edge. */
|
|
@@ -5661,7 +5854,12 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5661
5854
|
--aparte-field-choice-radius: var(--aparte-elic-option-radius);
|
|
5662
5855
|
}
|
|
5663
5856
|
|
|
5664
|
-
|
|
5857
|
+
/* Blind to the chosen row: this rule is (0,3,0) in a sheet imported after field.css,
|
|
5858
|
+
so it used to out-specify the mark a checked choice wears there — hover the option
|
|
5859
|
+
you picked and its tint and primary border went back to the plain hover ground
|
|
5860
|
+
(measured in Chromium by the coherence pass's reviewer). The recipe's own hover is
|
|
5861
|
+
scoped the same way; a chosen row keeps saying it is chosen under the pointer. */
|
|
5862
|
+
.aparte-elic-option:not(.aparte-elic-option--command):not(:has(:checked)):not(.aparte-field-choice--selected):hover {
|
|
5665
5863
|
background: var(--aparte-surface-2);
|
|
5666
5864
|
border-color: var(--aparte-border);
|
|
5667
5865
|
}
|
|
@@ -5724,6 +5922,14 @@ textarea.aparte-elic-text {
|
|
|
5724
5922
|
border-color: var(--aparte-primary);
|
|
5725
5923
|
background: color-mix(in srgb, var(--aparte-primary) 6%, transparent);
|
|
5726
5924
|
}
|
|
5925
|
+
/* One ring at a time. The recommended option is the first to receive focus, and its
|
|
5926
|
+
tinted border under the recipe's focus ring read as a double outline (UI audit
|
|
5927
|
+
2026-08-28, §8.3 #9). While it holds focus the ring is the indicator; the tint
|
|
5928
|
+
comes back when focus leaves. */
|
|
5929
|
+
.aparte-elic-option--recommended:focus-within {
|
|
5930
|
+
background: transparent;
|
|
5931
|
+
border-color: var(--aparte-border-focus);
|
|
5932
|
+
}
|
|
5727
5933
|
|
|
5728
5934
|
/* The whole-request escape, in the corner and out of the way of anything that
|
|
5729
5935
|
answers a question. It used to be a row at the bottom beside the button that
|
|
@@ -5935,12 +6141,27 @@ textarea.aparte-elic-text {
|
|
|
5935
6141
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
5936
6142
|
}
|
|
5937
6143
|
|
|
6144
|
+
/* The conversation you are in wears the mark's bar (display/mark.css) on its start
|
|
6145
|
+
edge — Primer's ActionList pattern, and the same bar the select's chosen option and
|
|
6146
|
+
a checked field choice carry. Its ground keeps the list's own tokens: a navigation
|
|
6147
|
+
list is read for a long time, and the mark's tint on every visit was too loud. */
|
|
5938
6148
|
.aparte-conv-item--active {
|
|
6149
|
+
position: relative;
|
|
5939
6150
|
background: var(--aparte-conv-item-bg-active);
|
|
5940
6151
|
color: var(--aparte-conv-item-color-active);
|
|
5941
6152
|
font-weight: var(--aparte-conv-item-font-weight-active);
|
|
5942
6153
|
}
|
|
5943
6154
|
|
|
6155
|
+
.aparte-conv-item--active::before {
|
|
6156
|
+
content: "";
|
|
6157
|
+
position: absolute;
|
|
6158
|
+
inset-block: 0;
|
|
6159
|
+
inset-inline-start: 0;
|
|
6160
|
+
width: var(--aparte-mark-bar);
|
|
6161
|
+
background: var(--aparte-primary-ink);
|
|
6162
|
+
pointer-events: none;
|
|
6163
|
+
}
|
|
6164
|
+
|
|
5944
6165
|
.aparte-conv-item--active .aparte-conv-item__delete {
|
|
5945
6166
|
opacity: 0;
|
|
5946
6167
|
}
|
|
@@ -5997,6 +6218,77 @@ textarea.aparte-elic-text {
|
|
|
5997
6218
|
font-style: italic;
|
|
5998
6219
|
opacity: 0.85;
|
|
5999
6220
|
}
|
|
6221
|
+
/*
|
|
6222
|
+
* aparté — <aparte-suggestions>: the row of prompt starters.
|
|
6223
|
+
*
|
|
6224
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6225
|
+
* them concatenated in that same order, the way a browser does.
|
|
6226
|
+
*
|
|
6227
|
+
* The chips are `aparte-btn aparte-btn--surface aparte-btn--pill` — the recipe draws
|
|
6228
|
+
* them, this sheet only lays them out. No token of its own: the gap is the global
|
|
6229
|
+
* spacing scale, so a theme reaches it without a knob to re-set.
|
|
6230
|
+
*/
|
|
6231
|
+
aparte-suggestions {
|
|
6232
|
+
display: block;
|
|
6233
|
+
}
|
|
6234
|
+
aparte-suggestions[hidden],
|
|
6235
|
+
aparte-suggestions[data-empty] {
|
|
6236
|
+
display: none;
|
|
6237
|
+
}
|
|
6238
|
+
|
|
6239
|
+
.aparte-suggestions {
|
|
6240
|
+
display: flex;
|
|
6241
|
+
flex-wrap: wrap;
|
|
6242
|
+
gap: var(--aparte-space-2);
|
|
6243
|
+
padding-block: var(--aparte-space-2);
|
|
6244
|
+
}
|
|
6245
|
+
|
|
6246
|
+
/* A starter's label is a short phrase; it must not become a two-line chip. */
|
|
6247
|
+
.aparte-suggestion {
|
|
6248
|
+
white-space: nowrap;
|
|
6249
|
+
}
|
|
6250
|
+
/*
|
|
6251
|
+
* aparté — <aparte-context>: how full the model's context window is.
|
|
6252
|
+
*
|
|
6253
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6254
|
+
* them concatenated in that same order, the way a browser does.
|
|
6255
|
+
*
|
|
6256
|
+
* The bar is the `aparte-progress` recipe; this sheet lays it out beside its reading
|
|
6257
|
+
* and recolours the fill at the two levels — from the global alert tokens, no token
|
|
6258
|
+
* of its own.
|
|
6259
|
+
*/
|
|
6260
|
+
aparte-context {
|
|
6261
|
+
display: block;
|
|
6262
|
+
}
|
|
6263
|
+
aparte-context[hidden],
|
|
6264
|
+
aparte-context[data-empty] {
|
|
6265
|
+
display: none;
|
|
6266
|
+
}
|
|
6267
|
+
|
|
6268
|
+
.aparte-context {
|
|
6269
|
+
display: flex;
|
|
6270
|
+
align-items: center;
|
|
6271
|
+
gap: var(--aparte-space-2);
|
|
6272
|
+
color: var(--aparte-text-muted);
|
|
6273
|
+
font-size: var(--aparte-font-size-sm);
|
|
6274
|
+
}
|
|
6275
|
+
|
|
6276
|
+
.aparte-context .aparte-progress {
|
|
6277
|
+
flex: 1;
|
|
6278
|
+
min-width: 4rem;
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
.aparte-context__text {
|
|
6282
|
+
white-space: nowrap;
|
|
6283
|
+
font-variant-numeric: tabular-nums;
|
|
6284
|
+
}
|
|
6285
|
+
|
|
6286
|
+
aparte-context[data-level="warn"] .aparte-progress__bar {
|
|
6287
|
+
background: var(--aparte-warning);
|
|
6288
|
+
}
|
|
6289
|
+
aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
6290
|
+
background: var(--aparte-error);
|
|
6291
|
+
}
|
|
6000
6292
|
/*
|
|
6001
6293
|
* aparté — Reset-agnostic typography for model-authored markdown.
|
|
6002
6294
|
*
|
|
@@ -6118,6 +6410,42 @@ textarea.aparte-elic-text {
|
|
|
6118
6410
|
margin: var(--aparte-prose-hr-margin);
|
|
6119
6411
|
}
|
|
6120
6412
|
|
|
6413
|
+
/* Tables. The sanitizer allowlisted table/thead/tbody/tr/th/td from the start and no
|
|
6414
|
+
sheet ever drew them, so a GFM table — the second most common structure in an
|
|
6415
|
+
assistant reply, and a suggestion chip in our own example — rendered as words with
|
|
6416
|
+
no borders, no padding and columns that touched. `display: block` + `overflow-x` is
|
|
6417
|
+
the one honest way to keep a wide table inside a narrow bubble without a wrapper
|
|
6418
|
+
the markdown provider cannot emit. */
|
|
6419
|
+
.aparte-content table,
|
|
6420
|
+
.aparte-segment-content table,
|
|
6421
|
+
.aparte-thinking-content table {
|
|
6422
|
+
display: block;
|
|
6423
|
+
max-width: 100%;
|
|
6424
|
+
overflow-x: auto;
|
|
6425
|
+
border-collapse: collapse;
|
|
6426
|
+
margin: var(--aparte-space-4) 0;
|
|
6427
|
+
font-size: var(--aparte-font-size-md);
|
|
6428
|
+
}
|
|
6429
|
+
|
|
6430
|
+
.aparte-content th,
|
|
6431
|
+
.aparte-segment-content th,
|
|
6432
|
+
.aparte-thinking-content th,
|
|
6433
|
+
.aparte-content td,
|
|
6434
|
+
.aparte-segment-content td,
|
|
6435
|
+
.aparte-thinking-content td {
|
|
6436
|
+
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
6437
|
+
padding: var(--aparte-space-2) var(--aparte-space-4);
|
|
6438
|
+
text-align: start;
|
|
6439
|
+
vertical-align: top;
|
|
6440
|
+
}
|
|
6441
|
+
|
|
6442
|
+
.aparte-content th,
|
|
6443
|
+
.aparte-segment-content th,
|
|
6444
|
+
.aparte-thinking-content th {
|
|
6445
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
6446
|
+
background: var(--aparte-surface-2);
|
|
6447
|
+
}
|
|
6448
|
+
|
|
6121
6449
|
.aparte-thinking-content {
|
|
6122
6450
|
padding: var(--aparte-space-2) 0 var(--aparte-space-1) var(--aparte-space-5);
|
|
6123
6451
|
font-size: var(--aparte-font-size-md);
|
|
@@ -6204,7 +6532,7 @@ textarea.aparte-elic-text {
|
|
|
6204
6532
|
|
|
6205
6533
|
/* Respect prefers-reduced-motion. Scoped to Aparte's own custom elements (never
|
|
6206
6534
|
the consumer's page) — this covers every animated element the library renders
|
|
6207
|
-
inside them: typing dots, spinners,
|
|
6535
|
+
inside them: typing dots, spinners, artifact pulses, segment
|
|
6208
6536
|
animations. Activity stays conveyed via aria-live / aria-busy. */
|
|
6209
6537
|
@media (prefers-reduced-motion: reduce) {
|
|
6210
6538
|
aparte-chat-viewport *,
|
|
@@ -6265,6 +6593,20 @@ textarea.aparte-elic-text {
|
|
|
6265
6593
|
--aparte-input-action-btn-size: var(--aparte-touch-target-size);
|
|
6266
6594
|
}
|
|
6267
6595
|
|
|
6596
|
+
/* The bubble's action bar too: 28px buttons under a thumb missed as often as they
|
|
6597
|
+
hit (UI audit 2026-08-28, §8.3 #10). The bar's height follows the same token.
|
|
6598
|
+
And it is SHOWN: the bar is revealed by hover, and a finger cannot hover, so on a
|
|
6599
|
+
touch device every message's actions were sitting at opacity 0 with no way to
|
|
6600
|
+
reach them — a larger target for something invisible. The footer stays in the
|
|
6601
|
+
flow here (the floating rule in bubble.css is under `(hover: hover) and
|
|
6602
|
+
(pointer: fine)`, so a device that hovers with a coarse pointer — a TV remote —
|
|
6603
|
+
keeps it in the flow too), so what a touch device gets is the bar every mobile
|
|
6604
|
+
chat shows: under each message, always. */
|
|
6605
|
+
.aparte-action-bar {
|
|
6606
|
+
--aparte-action-bar-btn-size: var(--aparte-touch-target-size);
|
|
6607
|
+
opacity: 1;
|
|
6608
|
+
}
|
|
6609
|
+
|
|
6268
6610
|
/* Hover cannot reveal these on touch — keep them visible and tappable. */
|
|
6269
6611
|
.aparte-conv-item__delete,
|
|
6270
6612
|
.aparte-conv-item__archive {
|