@aparte/core 0.13.0 → 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/elicitation/aparte-elicitation.d.ts.map +1 -1
- 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 +37 -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 +6484 -6103
- 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 +539 -172
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +609 -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-DrO_oR32.js → is-awaiting-reply-BZ_iJ3s3.js} +188 -1017
- 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 +18 -0
- 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-DrO_oR32.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'] {
|
|
@@ -2389,6 +2420,7 @@ aparte-icon {
|
|
|
2389
2420
|
*/
|
|
2390
2421
|
|
|
2391
2422
|
.aparte-tag {
|
|
2423
|
+
box-sizing: border-box;
|
|
2392
2424
|
display: inline-flex;
|
|
2393
2425
|
align-items: center;
|
|
2394
2426
|
gap: var(--aparte-tag-gap);
|
|
@@ -2511,6 +2543,98 @@ aparte-icon {
|
|
|
2511
2543
|
@media (forced-colors: active) {
|
|
2512
2544
|
.aparte-thumbnail { border-color: currentColor; }
|
|
2513
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
|
+
}
|
|
2514
2638
|
/* =========================================
|
|
2515
2639
|
aparte-spinner — a rotating indicator
|
|
2516
2640
|
=========================================
|
|
@@ -2652,29 +2776,6 @@ aparte-icon {
|
|
|
2652
2776
|
0% { background-position: 200% 0; }
|
|
2653
2777
|
100% { background-position: -200% 0; }
|
|
2654
2778
|
}
|
|
2655
|
-
|
|
2656
|
-
/* ── The zero-dependency fallbacks ────────────────────────────────────────────
|
|
2657
|
-
What `getSkeleton()` returns when no skeleton provider is registered. They used
|
|
2658
|
-
to carry their look in a `style=""` attribute — six inline declarations of
|
|
2659
|
-
Tailwind-slate hex (`#9ca3af`, `#1e293b`, `#64748b`), the exact palette this
|
|
2660
|
-
theme replaced. Three consequences, and the first is the one that matters: an
|
|
2661
|
-
inline style cannot be overridden by a consumer's stylesheet, so a dark-theme
|
|
2662
|
-
host got a light-grey label it had no way to reach. The second: hex in a `.ts`
|
|
2663
|
-
is invisible to `check:derived-vars`, which reads only `styles/`. The third:
|
|
2664
|
-
they had drifted from the recipe above, which already reads tokens.
|
|
2665
|
-
|
|
2666
|
-
Structure only, still — no shimmer, no animation. That is what makes them the
|
|
2667
|
-
fallback rather than a second skeleton implementation. */
|
|
2668
|
-
.aparte-skeleton-fallback {
|
|
2669
|
-
padding: var(--aparte-space-4);
|
|
2670
|
-
color: var(--aparte-text-muted);
|
|
2671
|
-
}
|
|
2672
|
-
.aparte-skeleton-fallback--snug { padding: var(--aparte-space-3); }
|
|
2673
|
-
.aparte-skeleton-fallback--tight { padding: var(--aparte-space-2); }
|
|
2674
|
-
.aparte-skeleton-fallback--code {
|
|
2675
|
-
background: var(--aparte-surface-2);
|
|
2676
|
-
border-radius: var(--aparte-radius-md);
|
|
2677
|
-
}
|
|
2678
2779
|
/* =========================================
|
|
2679
2780
|
aparte-divider — horizontal, vertical, optionally labelled
|
|
2680
2781
|
=========================================
|
|
@@ -2913,6 +3014,87 @@ aparte-icon {
|
|
|
2913
3014
|
line-height: var(--aparte-line-height-none);
|
|
2914
3015
|
}
|
|
2915
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
|
+
}
|
|
2916
3098
|
/*
|
|
2917
3099
|
* aparté — layered surfaces. Ready-made classes for the things that float ABOVE the
|
|
2918
3100
|
* transcript: tabs, an accordion, a dropdown menu, a popover, a tooltip.
|
|
@@ -3167,6 +3349,7 @@ aparte-icon {
|
|
|
3167
3349
|
reset a button does: no native chrome, full width, cursor and text alignment
|
|
3168
3350
|
fixed for a header that reads left-to-right of its own toggle icon. */
|
|
3169
3351
|
.aparte-accordion__header {
|
|
3352
|
+
box-sizing: border-box;
|
|
3170
3353
|
display: flex;
|
|
3171
3354
|
align-items: center;
|
|
3172
3355
|
justify-content: space-between;
|
|
@@ -3296,6 +3479,16 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3296
3479
|
}
|
|
3297
3480
|
|
|
3298
3481
|
.aparte-menu__item {
|
|
3482
|
+
/* Its own width plus its own padding, because core is light DOM: a host that
|
|
3483
|
+
never wrote `* { box-sizing: border-box }` is not a broken host, and this rule
|
|
3484
|
+
said `width: 100%` next to a padding. Measured in a frame without a reset: the
|
|
3485
|
+
row came out its parent width PLUS both paddings — 328px inside 308 at one window
|
|
3486
|
+
size — and clipped its last button by the right one. The absolute numbers depend on
|
|
3487
|
+
the window; the 20px of overflow is the padding, whatever the width.
|
|
3488
|
+
Per element, never a `*` selector — the same reason every other box-sizing in
|
|
3489
|
+
these sheets is written on the thing it belongs to. */
|
|
3490
|
+
|
|
3491
|
+
box-sizing: border-box;
|
|
3299
3492
|
appearance: none;
|
|
3300
3493
|
-webkit-appearance: none;
|
|
3301
3494
|
display: flex;
|
|
@@ -3554,14 +3747,47 @@ aparte-select[disabled] .aparte-select-trigger {
|
|
|
3554
3747
|
cursor: not-allowed;
|
|
3555
3748
|
}
|
|
3556
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). */
|
|
3557
3755
|
.aparte-select-label {
|
|
3558
3756
|
flex: 1;
|
|
3757
|
+
min-width: 0;
|
|
3758
|
+
display: grid;
|
|
3559
3759
|
overflow: hidden;
|
|
3560
|
-
text-overflow: ellipsis;
|
|
3561
|
-
white-space: nowrap;
|
|
3562
3760
|
color: var(--aparte-select-text, var(--aparte-text));
|
|
3563
3761
|
}
|
|
3564
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
|
+
|
|
3565
3791
|
.aparte-select-chevron {
|
|
3566
3792
|
--aparte-icon-size: var(--aparte-icon-size-sm);
|
|
3567
3793
|
flex-shrink: 0;
|
|
@@ -3592,6 +3818,7 @@ aparte-select[open] .aparte-select-chevron {
|
|
|
3592
3818
|
|
|
3593
3819
|
/* Search */
|
|
3594
3820
|
.aparte-select-search {
|
|
3821
|
+
box-sizing: border-box;
|
|
3595
3822
|
display: block;
|
|
3596
3823
|
width: 100%;
|
|
3597
3824
|
padding: var(--aparte-space-4) var(--aparte-space-6);
|
|
@@ -3634,15 +3861,30 @@ aparte-option[data-active] {
|
|
|
3634
3861
|
box-shadow: inset 0 0 0 2px var(--aparte-primary);
|
|
3635
3862
|
}
|
|
3636
3863
|
|
|
3637
|
-
/* Selected option: an accent TINT plus an accent bar
|
|
3638
|
-
text colour. A solid `--aparte-primary`
|
|
3639
|
-
both themes (the brass accent is
|
|
3640
|
-
and an unreadable selected row is the
|
|
3641
|
-
|
|
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. */
|
|
3642
3873
|
aparte-option[selected] {
|
|
3643
|
-
|
|
3874
|
+
position: relative;
|
|
3875
|
+
background: var(--aparte-select-option-selected, color-mix(in srgb, var(--aparte-primary) var(--aparte-mark-tint), transparent));
|
|
3644
3876
|
color: var(--aparte-select-option-selected-text, var(--aparte-select-text, var(--aparte-text)));
|
|
3645
|
-
|
|
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;
|
|
3646
3888
|
}
|
|
3647
3889
|
|
|
3648
3890
|
aparte-option[disabled] {
|
|
@@ -3707,16 +3949,12 @@ aparte-optgroup[collapsed] .aparte-optgroup-chevron {
|
|
|
3707
3949
|
transform: rotate(-90deg);
|
|
3708
3950
|
}
|
|
3709
3951
|
|
|
3710
|
-
/*
|
|
3711
|
-
[data-aparte-theme
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
[data-aparte-theme="dark"] aparte-option:hover {
|
|
3718
|
-
background: var(--aparte-select-option-hover, var(--aparte-surface-2));
|
|
3719
|
-
}
|
|
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. */
|
|
3720
3958
|
|
|
3721
3959
|
.aparte-optgroup-loader {
|
|
3722
3960
|
padding: var(--aparte-space-4) var(--aparte-space-6);
|
|
@@ -3883,7 +4121,7 @@ aparte-chat-viewport {
|
|
|
3883
4121
|
overflow-x: hidden;
|
|
3884
4122
|
overscroll-behavior: contain;
|
|
3885
4123
|
scrollbar-width: thin;
|
|
3886
|
-
scrollbar-color: var(--aparte-
|
|
4124
|
+
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
3887
4125
|
}
|
|
3888
4126
|
|
|
3889
4127
|
.aparte-viewport-container::-webkit-scrollbar {
|
|
@@ -3891,7 +4129,14 @@ aparte-chat-viewport {
|
|
|
3891
4129
|
}
|
|
3892
4130
|
|
|
3893
4131
|
.aparte-viewport-container::-webkit-scrollbar-track {
|
|
3894
|
-
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);
|
|
3895
4140
|
}
|
|
3896
4141
|
|
|
3897
4142
|
/* ─── Scroll-to-bottom button ─────────────────────────────────────────────── */
|
|
@@ -3950,7 +4195,7 @@ aparte-chat-viewport[framework-managed] {
|
|
|
3950
4195
|
padding: var(--aparte-viewport-padding);
|
|
3951
4196
|
padding-bottom: calc(var(--aparte-viewport-padding) + var(--aparte-fw-spacer, 0px));
|
|
3952
4197
|
scrollbar-width: thin;
|
|
3953
|
-
scrollbar-color: var(--aparte-
|
|
4198
|
+
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
3954
4199
|
}
|
|
3955
4200
|
|
|
3956
4201
|
/* The framework wrapper renders bubbles as DIRECT flex children of the viewport
|
|
@@ -3968,7 +4213,7 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar {
|
|
|
3968
4213
|
}
|
|
3969
4214
|
|
|
3970
4215
|
aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-track {
|
|
3971
|
-
background:
|
|
4216
|
+
background: var(--aparte-scrollbar-track);
|
|
3972
4217
|
}
|
|
3973
4218
|
|
|
3974
4219
|
/* The scroll button lives INSIDE the scrolling host here (last child), so it
|
|
@@ -4106,6 +4351,7 @@ aparte-chat-bubble {
|
|
|
4106
4351
|
}
|
|
4107
4352
|
|
|
4108
4353
|
.aparte-message {
|
|
4354
|
+
box-sizing: border-box;
|
|
4109
4355
|
display: flex;
|
|
4110
4356
|
gap: var(--aparte-message-gap);
|
|
4111
4357
|
padding: var(--aparte-message-padding);
|
|
@@ -4113,6 +4359,8 @@ aparte-chat-bubble {
|
|
|
4113
4359
|
max-width: var(--aparte-message-max-width);
|
|
4114
4360
|
margin: 0 auto;
|
|
4115
4361
|
min-width: 0;
|
|
4362
|
+
/* The containing block of a floating footer — see the action bar below. */
|
|
4363
|
+
position: relative;
|
|
4116
4364
|
}
|
|
4117
4365
|
|
|
4118
4366
|
/* The avatar's shape, size and type live in `display.css` — a message avatar IS an
|
|
@@ -4208,6 +4456,58 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
4208
4456
|
display: none;
|
|
4209
4457
|
}
|
|
4210
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
|
+
|
|
4211
4511
|
.aparte-action-btn {
|
|
4212
4512
|
color: var(--aparte-action-bar-btn-color);
|
|
4213
4513
|
--aparte-btn-radius: var(--aparte-radius-action-btn);
|
|
@@ -4322,6 +4622,15 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
4322
4622
|
.aparte-message[data-error] .aparte-avatar {
|
|
4323
4623
|
box-shadow: var(--aparte-avatar-error-ring);
|
|
4324
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
|
+
}
|
|
4325
4634
|
/*
|
|
4326
4635
|
* aparté — The composer and everything it holds: editor, send, attachments, model picker.
|
|
4327
4636
|
*
|
|
@@ -4390,92 +4699,6 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
4390
4699
|
Attachments
|
|
4391
4700
|
========================================= */
|
|
4392
4701
|
|
|
4393
|
-
.aparte-attachments {
|
|
4394
|
-
display: flex;
|
|
4395
|
-
flex-wrap: wrap;
|
|
4396
|
-
/* The strip sits directly above the message bubble and belongs to it, so it
|
|
4397
|
-
must share the bubble's edge. The user bubble hugs its text on the LEADING
|
|
4398
|
-
edge (AI-chat convention, see .aparte-message-content below), so anchoring
|
|
4399
|
-
the strip to the trailing edge would split one message across both sides. */
|
|
4400
|
-
justify-content: flex-start;
|
|
4401
|
-
gap: var(--aparte-space-3);
|
|
4402
|
-
margin-bottom: var(--aparte-space-4);
|
|
4403
|
-
/* Really small tiles inside the conversation. */
|
|
4404
|
-
--aparte-attachment-image-size: 40px;
|
|
4405
|
-
/* Many attachments → cap the height and scroll instead of growing forever. */
|
|
4406
|
-
max-height: var(--aparte-attachments-max-height);
|
|
4407
|
-
overflow-y: auto;
|
|
4408
|
-
}
|
|
4409
|
-
|
|
4410
|
-
/* The class `display: flex` above would otherwise override the `hidden`
|
|
4411
|
-
attribute — restore it so an empty attachment strip takes no space. */
|
|
4412
|
-
.aparte-attachments[hidden] {
|
|
4413
|
-
display: none;
|
|
4414
|
-
}
|
|
4415
|
-
|
|
4416
|
-
/* ── Attachment thumbnail tile ──────────────────────────────────────────────
|
|
4417
|
-
Shared by the composer attachment preview and the sent-message attachment
|
|
4418
|
-
strip. A square that shows the image itself (or a file glyph); the filename
|
|
4419
|
-
and — in the composer — a remove button surface on hover. */
|
|
4420
|
-
/* The tile is `.aparte-thumbnail`; the composer only sets its own measurements and
|
|
4421
|
-
the quieter ground an attachment strip uses. */
|
|
4422
|
-
.aparte-thumb {
|
|
4423
|
-
--aparte-thumbnail-size: var(--aparte-attachment-image-size);
|
|
4424
|
-
--aparte-thumbnail-radius: var(--aparte-thumb-radius);
|
|
4425
|
-
--aparte-thumbnail-bg: var(--aparte-attachment-chip-bg);
|
|
4426
|
-
--aparte-thumbnail-border: var(--aparte-attachment-chip-border);
|
|
4427
|
-
}
|
|
4428
|
-
|
|
4429
|
-
/* Only an image tile the app declared it can preview is a button — see
|
|
4430
|
-
aparteGlobalConfig.setHostHandlers(). Undeclared, it is a picture, not a control. */
|
|
4431
|
-
.aparte-thumb--image[role='button'] {
|
|
4432
|
-
cursor: pointer;
|
|
4433
|
-
}
|
|
4434
|
-
|
|
4435
|
-
.aparte-thumb--image[role='button']:focus-visible {
|
|
4436
|
-
box-shadow: var(--aparte-focus-ring);
|
|
4437
|
-
}
|
|
4438
|
-
|
|
4439
|
-
.aparte-thumb__img {
|
|
4440
|
-
width: 100%;
|
|
4441
|
-
height: 100%;
|
|
4442
|
-
object-fit: cover;
|
|
4443
|
-
display: block;
|
|
4444
|
-
}
|
|
4445
|
-
|
|
4446
|
-
.aparte-thumb--file {
|
|
4447
|
-
display: flex;
|
|
4448
|
-
align-items: center;
|
|
4449
|
-
justify-content: center;
|
|
4450
|
-
}
|
|
4451
|
-
|
|
4452
|
-
.aparte-thumb__ext {
|
|
4453
|
-
font-size: var(--aparte-font-size-xs);
|
|
4454
|
-
font-weight: var(--aparte-font-weight-bold);
|
|
4455
|
-
letter-spacing: 0.03em;
|
|
4456
|
-
color: var(--aparte-text-muted);
|
|
4457
|
-
}
|
|
4458
|
-
|
|
4459
|
-
.aparte-thumb__name {
|
|
4460
|
-
position: absolute;
|
|
4461
|
-
left: 0;
|
|
4462
|
-
right: 0;
|
|
4463
|
-
bottom: 0;
|
|
4464
|
-
padding: var(--aparte-thumb-name-padding);
|
|
4465
|
-
font-size: var(--aparte-font-size-2xs);
|
|
4466
|
-
line-height: var(--aparte-line-height-tight);
|
|
4467
|
-
color: var(--aparte-thumb-name-color);
|
|
4468
|
-
background: var(--aparte-thumb-name-scrim);
|
|
4469
|
-
white-space: nowrap;
|
|
4470
|
-
overflow: hidden;
|
|
4471
|
-
text-overflow: ellipsis;
|
|
4472
|
-
opacity: 0;
|
|
4473
|
-
transition: opacity var(--aparte-duration-base) var(--aparte-ease);
|
|
4474
|
-
pointer-events: none;
|
|
4475
|
-
}
|
|
4476
|
-
.aparte-thumb:hover .aparte-thumb__name {
|
|
4477
|
-
opacity: 1;
|
|
4478
|
-
}
|
|
4479
4702
|
|
|
4480
4703
|
.aparte-thumb__remove {
|
|
4481
4704
|
/* Icon size comes from `--sm` — see `--aparte-btn-icon-size` in button.css. */
|
|
@@ -4542,6 +4765,7 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
4542
4765
|
}
|
|
4543
4766
|
|
|
4544
4767
|
.aparte-editor {
|
|
4768
|
+
box-sizing: border-box;
|
|
4545
4769
|
width: 100%;
|
|
4546
4770
|
max-height: var(--aparte-input-editor-max-height);
|
|
4547
4771
|
/* Matches _maxHeight default */
|
|
@@ -4947,7 +5171,7 @@ aparte-composer-cancel .aparte-cc-button:hover {
|
|
|
4947
5171
|
flex: 0 0 auto;
|
|
4948
5172
|
}
|
|
4949
5173
|
/*
|
|
4950
|
-
* aparté — Segment renderers: thinking, code, tool call, error
|
|
5174
|
+
* aparté — Segment renderers: thinking, code, tool call, error.
|
|
4951
5175
|
*
|
|
4952
5176
|
* One of the sheets split out of aparte.css. Order matters: src/index.ts imports
|
|
4953
5177
|
* them in the order listed there, and check:derived-vars reads them concatenated in
|
|
@@ -5161,8 +5385,13 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5161
5385
|
0,2,0 beats the base rule's 0,1,0 and source order puts rejected/aborted last. Core
|
|
5162
5386
|
itself documents data-status as a free-form host attribute on <aparte-option>. */
|
|
5163
5387
|
.aparte-segment-tool-call[data-status="resolved"] .aparte-tool-state { color: var(--aparte-success-ink); }
|
|
5164
|
-
.
|
|
5165
|
-
|
|
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. */
|
|
5166
5395
|
/* The Approve / Reject button rules were here. They moved with the buttons, to
|
|
5167
5396
|
.aparte-approval-option in components/elicitation.css - and what stayed behind was a
|
|
5168
5397
|
leftover: a rule tinting .aparte-tool-label's border while a decision was pending,
|
|
@@ -5220,7 +5449,7 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5220
5449
|
}
|
|
5221
5450
|
|
|
5222
5451
|
/* ─────────────────────────────────────────────────────────────────────────────
|
|
5223
|
-
Segment renderers — tool call · artifact card
|
|
5452
|
+
Segment renderers — tool call · artifact card
|
|
5224
5453
|
|
|
5225
5454
|
These three shipped their CSS from `getStyles()` until now: a template literal
|
|
5226
5455
|
in the renderer's own .ts file, injected into <head> at runtime. That seam is
|
|
@@ -5250,27 +5479,6 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5250
5479
|
───────────────────────────────────────────────────────────────────────────── */
|
|
5251
5480
|
|
|
5252
5481
|
/* Tool call */
|
|
5253
|
-
.aparte-segment-pipeline-waiting {
|
|
5254
|
-
display: flex;
|
|
5255
|
-
align-items: center;
|
|
5256
|
-
gap: var(--aparte-space-3);
|
|
5257
|
-
padding: var(--aparte-space-3) var(--aparte-space-1);
|
|
5258
|
-
min-height: var(--aparte-pw-min-height);
|
|
5259
|
-
}
|
|
5260
|
-
/* The geometry is `.aparte-dot`. What stays is this row's own: its size, its resting
|
|
5261
|
-
colour, and a STAGGERED pulse — three dots on delays read as a wave travelling, which
|
|
5262
|
-
a symmetric breath does not. */
|
|
5263
|
-
.aparte-pw-dot {
|
|
5264
|
-
--aparte-status-dot-size: var(--aparte-pw-dot-size);
|
|
5265
|
-
--aparte-status-color: var(--aparte-text-muted);
|
|
5266
|
-
opacity: 0.3;
|
|
5267
|
-
}
|
|
5268
|
-
.aparte-pw-dot:nth-child(2) { animation-delay: var(--aparte-duration-slow); }
|
|
5269
|
-
.aparte-pw-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
5270
|
-
|
|
5271
|
-
/* =========================================
|
|
5272
|
-
Provider-HTML overflow guards
|
|
5273
|
-
========================================= */
|
|
5274
5482
|
/* Markdown providers inject arbitrary HTML into .aparte-segment-text, and the bubble
|
|
5275
5483
|
clips overflow (`aparte-chat-bubble { overflow: hidden }`) — without these
|
|
5276
5484
|
guards a wide image or table is silently cut at the bubble edge. */
|
|
@@ -5646,7 +5854,12 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5646
5854
|
--aparte-field-choice-radius: var(--aparte-elic-option-radius);
|
|
5647
5855
|
}
|
|
5648
5856
|
|
|
5649
|
-
|
|
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 {
|
|
5650
5863
|
background: var(--aparte-surface-2);
|
|
5651
5864
|
border-color: var(--aparte-border);
|
|
5652
5865
|
}
|
|
@@ -5709,6 +5922,14 @@ textarea.aparte-elic-text {
|
|
|
5709
5922
|
border-color: var(--aparte-primary);
|
|
5710
5923
|
background: color-mix(in srgb, var(--aparte-primary) 6%, transparent);
|
|
5711
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
|
+
}
|
|
5712
5933
|
|
|
5713
5934
|
/* The whole-request escape, in the corner and out of the way of anything that
|
|
5714
5935
|
answers a question. It used to be a row at the bottom beside the button that
|
|
@@ -5920,12 +6141,27 @@ textarea.aparte-elic-text {
|
|
|
5920
6141
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
5921
6142
|
}
|
|
5922
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. */
|
|
5923
6148
|
.aparte-conv-item--active {
|
|
6149
|
+
position: relative;
|
|
5924
6150
|
background: var(--aparte-conv-item-bg-active);
|
|
5925
6151
|
color: var(--aparte-conv-item-color-active);
|
|
5926
6152
|
font-weight: var(--aparte-conv-item-font-weight-active);
|
|
5927
6153
|
}
|
|
5928
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
|
+
|
|
5929
6165
|
.aparte-conv-item--active .aparte-conv-item__delete {
|
|
5930
6166
|
opacity: 0;
|
|
5931
6167
|
}
|
|
@@ -5948,10 +6184,18 @@ textarea.aparte-elic-text {
|
|
|
5948
6184
|
width: var(--aparte-conv-action-btn-size);
|
|
5949
6185
|
height: var(--aparte-conv-action-btn-size);
|
|
5950
6186
|
color: var(--aparte-conv-delete-color);
|
|
6187
|
+
/* DECLARED, not out-specified. `.aparte-btn:hover:not(:disabled)` weighs 0,3,0 and
|
|
6188
|
+
`.aparte-conv-item__delete:hover` only 0,2,0, so the recipe won the background and
|
|
6189
|
+
this button's red never applied — while the `color` below DID, because the recipe's
|
|
6190
|
+
hover rule sets no colour. The result was the ink for a solid red fill painted on a
|
|
6191
|
+
neutral surface: 1.17:1 in the light theme, 1.20 in the dark, measured in a browser.
|
|
6192
|
+
The cross was invisible in both. Feeding the recipe its own token is the rule this
|
|
6193
|
+
file's neighbours already follow, and it makes the pair agree again — 3.70 and 4.84,
|
|
6194
|
+
both clear of the 3:1 a graphical object needs. */
|
|
6195
|
+
--aparte-btn-bg-hover: var(--aparte-conv-delete-bg-hover);
|
|
5951
6196
|
}
|
|
5952
6197
|
|
|
5953
6198
|
.aparte-conv-item__delete:hover {
|
|
5954
|
-
background: var(--aparte-conv-delete-bg-hover);
|
|
5955
6199
|
color: var(--aparte-conv-delete-color-hover);
|
|
5956
6200
|
}
|
|
5957
6201
|
|
|
@@ -5961,10 +6205,12 @@ textarea.aparte-elic-text {
|
|
|
5961
6205
|
width: var(--aparte-conv-action-btn-size);
|
|
5962
6206
|
height: var(--aparte-conv-action-btn-size);
|
|
5963
6207
|
color: var(--aparte-conv-archive-color, var(--aparte-text-muted));
|
|
6208
|
+
/* Same reason as the delete button above: the recipe out-weighs a plain `:hover` here,
|
|
6209
|
+
so this surface has to be declared as its token or it never applies. */
|
|
6210
|
+
--aparte-btn-bg-hover: var(--aparte-conv-archive-bg-hover, var(--aparte-surface-4, var(--aparte-surface-3)));
|
|
5964
6211
|
}
|
|
5965
6212
|
|
|
5966
6213
|
.aparte-conv-item__archive:hover {
|
|
5967
|
-
background: var(--aparte-conv-archive-bg-hover, var(--aparte-surface-4, var(--aparte-surface-3)));
|
|
5968
6214
|
color: var(--aparte-conv-archive-color-hover, var(--aparte-text));
|
|
5969
6215
|
}
|
|
5970
6216
|
|
|
@@ -5972,6 +6218,77 @@ textarea.aparte-elic-text {
|
|
|
5972
6218
|
font-style: italic;
|
|
5973
6219
|
opacity: 0.85;
|
|
5974
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
|
+
}
|
|
5975
6292
|
/*
|
|
5976
6293
|
* aparté — Reset-agnostic typography for model-authored markdown.
|
|
5977
6294
|
*
|
|
@@ -6093,6 +6410,42 @@ textarea.aparte-elic-text {
|
|
|
6093
6410
|
margin: var(--aparte-prose-hr-margin);
|
|
6094
6411
|
}
|
|
6095
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
|
+
|
|
6096
6449
|
.aparte-thinking-content {
|
|
6097
6450
|
padding: var(--aparte-space-2) 0 var(--aparte-space-1) var(--aparte-space-5);
|
|
6098
6451
|
font-size: var(--aparte-font-size-md);
|
|
@@ -6179,7 +6532,7 @@ textarea.aparte-elic-text {
|
|
|
6179
6532
|
|
|
6180
6533
|
/* Respect prefers-reduced-motion. Scoped to Aparte's own custom elements (never
|
|
6181
6534
|
the consumer's page) — this covers every animated element the library renders
|
|
6182
|
-
inside them: typing dots, spinners,
|
|
6535
|
+
inside them: typing dots, spinners, artifact pulses, segment
|
|
6183
6536
|
animations. Activity stays conveyed via aria-live / aria-busy. */
|
|
6184
6537
|
@media (prefers-reduced-motion: reduce) {
|
|
6185
6538
|
aparte-chat-viewport *,
|
|
@@ -6240,6 +6593,20 @@ textarea.aparte-elic-text {
|
|
|
6240
6593
|
--aparte-input-action-btn-size: var(--aparte-touch-target-size);
|
|
6241
6594
|
}
|
|
6242
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
|
+
|
|
6243
6610
|
/* Hover cannot reveal these on touch — keep them visible and tappable. */
|
|
6244
6611
|
.aparte-conv-item__delete,
|
|
6245
6612
|
.aparte-conv-item__archive {
|