anentrypoint-design 0.0.212 → 0.0.213

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/app-shell.css CHANGED
@@ -450,7 +450,7 @@ body.canvas-host { background: transparent !important; }
450
450
  .ds-icon-btn-primary { background: var(--accent); color: var(--accent-fg); }
451
451
  .ds-icon-btn-primary:hover { background: var(--fg); color: var(--bg); }
452
452
  /* warn = destructive ACTION tone; flame stays exclusively error STATUS. */
453
- .ds-icon-btn-danger { background: var(--warn); color: var(--paper); }
453
+ .ds-icon-btn-danger { background: var(--warn); color: var(--on-color); }
454
454
  .ds-icon-btn-danger:hover { filter: brightness(1.1); }
455
455
  .ds-icon-btn:active { transform: translateY(1px); }
456
456
  .ds-icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@@ -472,10 +472,10 @@ body.canvas-host { background: transparent !important; }
472
472
  .ds-badge.tone-purple { background: var(--purple-tint); color: var(--purple-deep); }
473
473
  .ds-badge.tone-mascot { background: var(--mascot-tint); color: var(--ink); }
474
474
  .ds-badge.tone-sun { background: var(--sun); color: var(--ink); }
475
- .ds-badge.tone-flame { background: var(--flame); color: var(--paper); }
475
+ .ds-badge.tone-flame { background: var(--flame); color: var(--on-color); }
476
476
  .ds-badge.tone-live { background: var(--green-tint); color: var(--green-deep); }
477
477
  .ds-badge.tone-wip { background: var(--bg-3); color: var(--fg-2); }
478
- .ds-badge.tone-error { background: var(--flame); color: var(--paper); }
478
+ .ds-badge.tone-error { background: var(--flame); color: var(--on-color); }
479
479
  .ds-badge.tone-success { background: var(--green-tint); color: var(--green-deep); }
480
480
  .ds-badge.tone-neutral { background: var(--bg-3); color: var(--fg-2); }
481
481
 
@@ -492,14 +492,14 @@ body.canvas-host { background: transparent !important; }
492
492
  .chip.tone-purple { background: var(--purple-tint); color: var(--purple-deep); }
493
493
  .chip.tone-mascot { background: var(--mascot-tint); color: var(--ink); }
494
494
  .chip.tone-sun { background: var(--sun); color: var(--ink); }
495
- .chip.tone-flame { background: var(--flame); color: var(--paper); }
495
+ .chip.tone-flame { background: var(--flame); color: var(--on-color); }
496
496
  .chip.tone-live { background: var(--green-tint); color: var(--green-deep); }
497
497
  .chip.tone-wip { background: var(--bg-3); color: var(--fg-2); }
498
- .chip.tone-error { background: var(--flame); color: var(--paper); }
498
+ .chip.tone-error { background: var(--flame); color: var(--on-color); }
499
499
  .chip.tone-success { background: var(--green-tint); color: var(--green-deep); }
500
500
  .chip.tone-disabled { background: var(--bg-3); color: var(--fg-3); }
501
501
  .chip.tone-ok { background: var(--green-tint); color: var(--green-deep); }
502
- .chip.tone-miss { background: var(--flame); color: var(--paper); }
502
+ .chip.tone-miss { background: var(--flame); color: var(--on-color); }
503
503
  .chip.tone-neutral { background: var(--bg-3); color: var(--fg-2); }
504
504
 
505
505
  .glyph {
@@ -538,6 +538,8 @@ body.canvas-host { background: transparent !important; }
538
538
  font-family: var(--ff-mono); letter-spacing: 0;
539
539
  }
540
540
  .panel-body { padding: var(--space-2) var(--space-3) var(--space-3); }
541
+ .panel-body > * { margin: 0; }
542
+ .panel-body > * + * { margin-top: var(--space-3); }
541
543
  .panel.panel-wide .panel-body { padding: 0; }
542
544
 
543
545
  /* ============================================================
@@ -560,6 +562,21 @@ body.canvas-host { background: transparent !important; }
560
562
  transition: background var(--dur-snap) var(--ease);
561
563
  }
562
564
  .row + .row { margin-top: 2px; }
565
+ /* `detail` drops to its own full-width line. The row is a grid, so span every
566
+ track (grid-column 1/-1); flex-basis:100% covers the flex-row fallback. */
567
+ .ds-row-detail {
568
+ white-space: pre-wrap;
569
+ font-family: var(--ff-mono);
570
+ font-size: var(--fs-sm);
571
+ line-height: var(--lh-snug, 1.3);
572
+ margin-top: 8px;
573
+ padding: 10px 12px;
574
+ background: var(--bg);
575
+ border-radius: var(--r-1);
576
+ overflow-x: auto;
577
+ flex-basis: 100%;
578
+ grid-column: 1 / -1;
579
+ }
563
580
  .row::before {
564
581
  content: '';
565
582
  position: absolute; left: 7px; top: 0; bottom: 0; width: 3px;
@@ -943,8 +960,6 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
943
960
  .chat-msg:hover { padding: 4px 0; margin: 0; background: transparent; }
944
961
  .chat-composer { padding: 8px; gap: 6px; }
945
962
  .chat-composer textarea { padding: 10px 12px; font-size: var(--fs-sm); }
946
- .chat-composer .send,
947
- .chat-composer button { width: 40px; height: 40px; font-size: 16px; }
948
963
 
949
964
  /* KPI Cards */
950
965
  .kpi {
@@ -1333,9 +1348,10 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
1333
1348
  background: var(--bg-2); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1);
1334
1349
  }
1335
1350
  .ds-density-btn {
1336
- font-size: var(--fs-tiny); color: var(--fg-2);
1351
+ display: inline-flex; align-items: center; justify-content: center;
1352
+ width: 28px; height: 28px; color: var(--fg-2);
1337
1353
  background: none; border: none; cursor: pointer;
1338
- padding: 2px var(--space-2); border-radius: calc(var(--r-1) - 2px);
1354
+ padding: 0; border-radius: calc(var(--r-1) - 2px);
1339
1355
  transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
1340
1356
  }
1341
1357
  .ds-density-btn:hover { color: var(--fg); }
@@ -1784,6 +1800,7 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
1784
1800
  transition: color var(--dur-base) var(--ease), background var(--dur-base) var(--ease);
1785
1801
  }
1786
1802
  .ds-segmented .ds-seg-btn:hover { color: var(--fg); }
1803
+ .ds-segmented .ds-seg-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
1787
1804
  .ds-segmented .ds-seg-btn.is-on {
1788
1805
  background: var(--bg); color: var(--fg);
1789
1806
  box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule);
@@ -1931,9 +1948,19 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
1931
1948
  color: color-mix(in oklab, currentColor 75%, transparent); font-style: italic;
1932
1949
  }
1933
1950
  .chat-bubble.chat-md pre {
1934
- background: color-mix(in oklab, var(--fg) 8%, transparent);
1935
- padding: 10px 12px; border-radius: 6px; overflow-x: auto;
1936
- font-family: var(--ff-mono); font-size: 0.9em; margin: 8px 0;
1951
+ background: var(--bg);
1952
+ border: 1px solid color-mix(in oklab, var(--fg) 12%, transparent);
1953
+ padding: 12px 16px; border-radius: var(--r-1); overflow-x: auto;
1954
+ font-family: var(--ff-mono); font-size: var(--fs-xs); line-height: 1.55;
1955
+ color: var(--fg); margin: 8px 0;
1956
+ }
1957
+ /* When injectCodeCopy has wrapped the fence (.chat-code-block), reserve room
1958
+ for the absolute .chat-code-lang tab. This selector outranks the bare
1959
+ .chat-code-block pre reservation in chat.css, which the 2-class md pre
1960
+ selector above would otherwise override. */
1961
+ .chat-code-block .chat-bubble.chat-md pre,
1962
+ .chat-bubble.chat-md .chat-code-block pre {
1963
+ padding-top: calc(var(--space-3) + 4px);
1937
1964
  }
1938
1965
  /* Markdown tables + horizontal rules: agents routinely emit them, but they were
1939
1966
  unstyled (bare unspaced cells, vanished rules). */
@@ -2169,6 +2196,14 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
2169
2196
  border-color: var(--accent);
2170
2197
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 18%, transparent);
2171
2198
  }
2199
+ .chat-composer.is-error,
2200
+ .chat-composer:has(textarea[aria-invalid="true"]) {
2201
+ border-color: var(--flame);
2202
+ }
2203
+ .chat-composer.is-error:focus-within,
2204
+ .chat-composer:has(textarea[aria-invalid="true"]):focus-within {
2205
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--flame) 22%, transparent);
2206
+ }
2172
2207
  .chat-composer textarea {
2173
2208
  flex: 1; min-width: 0; padding: 8px 4px;
2174
2209
  background: none; color: var(--fg);
@@ -2184,7 +2219,7 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
2184
2219
  .chat-composer textarea::placeholder { color: var(--fg-3); }
2185
2220
  .chat-composer textarea:focus { background: none; border: none; box-shadow: none; outline: none; }
2186
2221
  .chat-composer .send {
2187
- width: 36px; height: 36px; border-radius: var(--r-1);
2222
+ border-radius: var(--r-1);
2188
2223
  background: var(--accent); color: var(--accent-fg);
2189
2224
  border: 0; cursor: pointer;
2190
2225
  display: inline-flex; align-items: center; justify-content: center;
@@ -2192,10 +2227,10 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
2192
2227
  transition: background var(--dur-snap, .15s) var(--ease, ease);
2193
2228
  }
2194
2229
  .chat-composer .send:disabled { opacity: .5; cursor: not-allowed; }
2195
- /* Ghost icon buttons in the toolbar (attach / emoji / more) NOT the round
2196
- accent send button. */
2230
+ /* Ghost icon buttons in the toolbar (attach / emoji / more). Size + radius are
2231
+ owned by .chat-composer-toolbar .composer-btn in chat.css (32px / --r-1,
2232
+ 44px coarse); this base rule keeps only the shared appearance. */
2197
2233
  .composer-btn {
2198
- width: 40px; height: 40px; border-radius: 50%;
2199
2234
  background: transparent; color: var(--fg-3);
2200
2235
  border: 0; cursor: pointer;
2201
2236
  display: inline-flex; align-items: center; justify-content: center;
@@ -2291,9 +2326,6 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
2291
2326
  @media (max-height: 500px) and (orientation: landscape) {
2292
2327
  .chat-composer { padding: 6px 0; }
2293
2328
  .chat-composer textarea { min-height: 44px; max-height: 120px; }
2294
- /* Keep the send button at the 44x44 minimum tap target even in the
2295
- space-constrained landscape layout. */
2296
- .chat-composer .send, .chat-composer button { width: 44px; height: 44px; font-size: 14px; }
2297
2329
  }
2298
2330
 
2299
2331
  /* ============================================================
@@ -2436,7 +2468,6 @@ input[type="checkbox"], input[type="radio"] {
2436
2468
  position: absolute;
2437
2469
  left: 0; right: 0; height: 2px;
2438
2470
  background: var(--accent);
2439
- animation: pulse-line 0.6s ease-in-out infinite;
2440
2471
  }
2441
2472
  .list-item.drag-before::before { top: -2px; }
2442
2473
  .list-item.drag-after::after { bottom: -2px; }
@@ -2445,6 +2476,10 @@ input[type="checkbox"], input[type="radio"] {
2445
2476
  0%, 100% { opacity: 0.3; }
2446
2477
  50% { opacity: 1; }
2447
2478
  }
2479
+ @media (prefers-reduced-motion: no-preference) {
2480
+ .list-item.drag-before::before,
2481
+ .list-item.drag-after::after { animation: pulse-line 0.6s ease-in-out infinite; }
2482
+ }
2448
2483
 
2449
2484
  /* ------------------------------------------------------------
2450
2485
  Context Menu Styles
@@ -2459,7 +2494,6 @@ input[type="checkbox"], input[type="radio"] {
2459
2494
  min-width: 160px;
2460
2495
  z-index: calc(var(--z-overlay, 1000) + 1);
2461
2496
  padding: 4px 0;
2462
- animation: context-menu-in 0.15s ease-out;
2463
2497
  }
2464
2498
 
2465
2499
  @keyframes context-menu-in {
@@ -2472,6 +2506,9 @@ input[type="checkbox"], input[type="radio"] {
2472
2506
  transform: scale(1) translateY(0);
2473
2507
  }
2474
2508
  }
2509
+ @media (prefers-reduced-motion: no-preference) {
2510
+ .ds-context-menu { animation: context-menu-in 0.15s ease-out; }
2511
+ }
2475
2512
 
2476
2513
  .ds-context-menu-item {
2477
2514
  display: flex; align-items: center; gap: 10px;
@@ -2728,6 +2765,10 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
2728
2765
  padding: var(--space-8) var(--space-4);
2729
2766
  text-align: center; color: var(--fg-3);
2730
2767
  }
2768
+ .empty-state--inline {
2769
+ flex-direction: row; align-items: center; gap: var(--space-2);
2770
+ padding: var(--space-2) 0; text-align: left;
2771
+ }
2731
2772
  .empty-state-icon {
2732
2773
  font-size: 48px; opacity: 0.4;
2733
2774
  }
@@ -2772,7 +2813,6 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
2772
2813
  padding: var(--space-3) var(--space-4);
2773
2814
  max-width: 320px;
2774
2815
  box-shadow: 0 10px 40px color-mix(in oklab, var(--fg) 20%, transparent);
2775
- animation: toast-slide-in 0.3s ease-out;
2776
2816
  z-index: calc(var(--z-overlay, 1000) + 10);
2777
2817
  }
2778
2818
 
@@ -2786,6 +2826,9 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
2786
2826
  transform: translateX(0);
2787
2827
  }
2788
2828
  }
2829
+ @media (prefers-reduced-motion: no-preference) {
2830
+ .toast { animation: toast-slide-in 0.3s ease-out; }
2831
+ }
2789
2832
 
2790
2833
  .toast.error { border-color: var(--warn); }
2791
2834
  .toast.success { border-color: var(--green-2); }
@@ -2812,11 +2855,13 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
2812
2855
  height: 4px;
2813
2856
  border-radius: 50%;
2814
2857
  background: var(--accent);
2815
- animation: ds-bounce 1.4s infinite ease-in-out;
2816
2858
  }
2817
- .ds-spinner span:nth-child(1) { animation-delay: 0s; }
2818
- .ds-spinner span:nth-child(2) { animation-delay: 0.18s; }
2819
- .ds-spinner span:nth-child(3) { animation-delay: 0.36s; }
2859
+ @media (prefers-reduced-motion: no-preference) {
2860
+ .ds-spinner span { animation: ds-bounce 1.4s infinite ease-in-out; }
2861
+ .ds-spinner span:nth-child(1) { animation-delay: 0s; }
2862
+ .ds-spinner span:nth-child(2) { animation-delay: 0.18s; }
2863
+ .ds-spinner span:nth-child(3) { animation-delay: 0.36s; }
2864
+ }
2820
2865
 
2821
2866
  .ds-spinner-lg { height: 24px; gap: 6px; }
2822
2867
  .ds-spinner-lg span { width: 6px; height: 6px; }
@@ -3094,6 +3139,13 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
3094
3139
  @media (max-width: 1480px) { .ws-resizer.ws-resizer-pane { display: none; } }
3095
3140
  @media (max-width: 1100px) { .ws-resizer.ws-resizer-sessions { display: none; } }
3096
3141
  @media (max-width: 900px) { .ws-resizer { display: none; } }
3142
+ /* A resizer must also vanish once its TRACK is desktop-collapsed: a 0px (pane/
3143
+ sessions) or 60px icon-only (rail) track has no width to drag, and a drag would
3144
+ write an INLINE --ws-*-w that overrides the collapse var (inline wins), fighting
3145
+ the collapse state. Mirror the breakpoint guards for the collapse classes. */
3146
+ .ws-rail-collapsed .ws-resizer-rail,
3147
+ .ws-sessions-collapsed .ws-resizer-sessions,
3148
+ .ws-pane-collapsed .ws-resizer-pane { display: none; }
3097
3149
  /* Coarse-pointer hit target for hybrid touch laptops above the drawer breakpoint
3098
3150
  (the 8px sliver is un-grabbable by touch). */
3099
3151
  @media (pointer: coarse) { .ws-resizer { width: 16px; margin-right: -8px; } }
@@ -3194,16 +3246,10 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
3194
3246
  overflow: hidden; transition: width var(--dur-base) var(--ease);
3195
3247
  }
3196
3248
  .ws-pane-collapsed .ws-pane { width: 0; border-left: none; }
3197
- .ws-pane-collapsed .ws-pane > :not(.ws-pane-toggle) { display: none; }
3198
- .ws-pane-toggle {
3199
- position: absolute; top: var(--space-2); left: calc(-1 * var(--space-5));
3200
- width: 28px; height: 28px;
3201
- display: inline-flex; align-items: center; justify-content: center;
3202
- background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); color: var(--fg-2);
3203
- border-radius: var(--r-1); cursor: pointer; z-index: 2;
3249
+ .ws-pane-collapsed .ws-pane > * { display: none; }
3250
+ @media (pointer: coarse) {
3251
+ .ws-rail-toggle { width: 44px; height: 44px; }
3204
3252
  }
3205
- .ws-pane-toggle:hover { background: var(--bg-3); color: var(--fg); }
3206
- .ws-pane-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
3207
3253
 
3208
3254
  /* Drawer toggles and the scrim are hidden by default and revealed by the staged
3209
3255
  media blocks BELOW - these base rules must precede those blocks in source
@@ -3216,6 +3262,10 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
3216
3262
  .ws-desktop-toggle:hover { background: var(--bg-2); color: var(--fg); }
3217
3263
  .ws-desktop-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
3218
3264
  @media (max-width: 900px) { .ws-desktop-toggle { display: none; } }
3265
+ /* The pane collapse toggle is meaningless once the pane TRACK is dropped (the
3266
+ pane becomes a mobile overlay drawer at <=1480px, reached via its own
3267
+ drawer-toggle), so hide this crumb control past that breakpoint. */
3268
+ @media (max-width: 1480px) { .ws-pane-toggle { display: none; } }
3219
3269
  .ws-scrim { display: none; }
3220
3270
 
3221
3271
  /* Responsive: the columns yield to the CONTENT in stages - the main column is
@@ -3252,9 +3302,7 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
3252
3302
  }
3253
3303
  .ws-shell.ws-pane-open .ws-pane { transform: translateX(0); width: min(340px, 85vw); }
3254
3304
  .ws-shell.ws-pane-open.ws-pane-collapsed .ws-pane { border-left: var(--bw-hair) solid var(--bg-3); }
3255
- .ws-shell.ws-pane-open.ws-pane-collapsed .ws-pane > :not(.ws-pane-toggle) { display: revert; }
3256
- /* The desktop collapse chevron is meaningless in drawer mode. */
3257
- .ws-pane-toggle { display: none; }
3305
+ .ws-shell.ws-pane-open.ws-pane-collapsed .ws-pane > * { display: revert; }
3258
3306
  .ws-pane-drawer-toggle { display: inline-flex; }
3259
3307
  /* Scrim is always present from this stage down so it FADES with the drawer
3260
3308
  instead of hard-appearing; pointer-events gate keeps it inert while closed. */
package/chat.css CHANGED
@@ -97,17 +97,19 @@
97
97
  font: inherit;
98
98
  }
99
99
  .agentchat-cwd-btn:hover { border-color: var(--accent); }
100
- .agentchat-cwd-btn:focus-visible { outline: 2px solid var(--accent, var(--fg)); outline-offset: 2px; }
100
+ .agentchat-cwd-btn:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
101
101
  .agentchat-cwd-input {
102
102
  flex: 1;
103
103
  min-width: 12ch;
104
104
  background: var(--bg-2);
105
- border: 1px solid var(--rule);
105
+ border: var(--bw-hair) solid var(--rule);
106
106
  color: var(--fg);
107
- border-radius: 6px;
107
+ border-radius: var(--r-1);
108
108
  padding: 4px 8px;
109
109
  font: inherit;
110
110
  }
111
+ .agentchat-cwd-input:focus-visible { outline: none; box-shadow: var(--focus-ring-inset); }
112
+ .agentchat-cwd-input[aria-invalid='true'] { border-color: var(--flame); box-shadow: inset 0 0 0 var(--bw-hair) var(--flame); }
111
113
 
112
114
  /* head + thread */
113
115
  .agentchat-head {
@@ -315,17 +317,28 @@
315
317
  /* Each variant carries a non-colour channel (fill vs halo vs hollow ring) so the
316
318
  states stay distinguishable for colour-blind users and when stale/connecting
317
319
  would otherwise share a hue. Mirrors the rail-tone shape differentiation. */
318
- .status-dot-disc.status-dot-live { background: var(--accent); animation: status-disc-pulse 1.8s ease-in-out infinite; }
320
+ /* One canonical stale tone (disc + word + rail share it) a muted grey-warn,
321
+ deliberately desaturated so it is NOT the saturated --amber used by
322
+ connecting/connecting-stream (which would re-collide hues). */
323
+ :root { --stale: color-mix(in oklab, var(--warn) 70%, var(--fg-3)); }
324
+ /* Live: a persistent faint concentric ring (resting shape channel, independent
325
+ of motion) with the pulse layered on top — so even at the pulse trough the
326
+ disc reads as solid-fill + ring, never a bare solid that aliases stale. */
327
+ .status-dot-disc.status-dot-live { background: var(--accent); box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 30%, transparent); animation: status-disc-pulse 1.8s ease-in-out infinite; }
319
328
  .status-dot-disc.status-dot-error { background: var(--flame); box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--flame) 38%, transparent); }
320
329
  .status-dot-disc.status-dot-connecting { background: transparent; box-shadow: inset 0 0 0 2px var(--amber); }
321
- .status-dot-disc.status-dot-stale { background: color-mix(in oklab, var(--warn) 70%, var(--fg-3)); }
330
+ /* Stale: a muted ring with a hollow centre — silhouette distinct from live's
331
+ filled disc and connecting's amber hollow ring (hue + inset core). */
332
+ .status-dot-disc.status-dot-stale { background: transparent; box-shadow: inset 0 0 0 1px var(--stale), inset 0 0 0 3px var(--bg); }
322
333
  @keyframes status-disc-pulse {
323
- 0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--accent) 55%, transparent); }
324
- 50% { box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent) 0%, transparent); }
334
+ /* First shadow is the persistent resting ring; second is the animated halo —
335
+ the ring is always present so the trough never collapses to a bare solid. */
336
+ 0%, 100% { box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 30%, transparent), 0 0 0 2px color-mix(in oklab, var(--accent) 55%, transparent); }
337
+ 50% { box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 30%, transparent), 0 0 0 6px color-mix(in oklab, var(--accent) 0%, transparent); }
325
338
  }
326
339
  @media (prefers-reduced-motion: reduce) {
327
- /* Keep the live disc shape-differentiated without motion: a static concentric ring. */
328
- .status-dot-disc.status-dot-live { animation: none; box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent); }
340
+ /* The base rule already carries the persistent ring; just stop the pulse. */
341
+ .status-dot-disc.status-dot-live { animation: none; }
329
342
  }
330
343
 
331
344
  /* ----------------------------------------------------------------------------
@@ -492,7 +505,7 @@
492
505
  }
493
506
  button.chat-composer-context { cursor: pointer; }
494
507
  button.chat-composer-context:hover { color: var(--fg-2); }
495
- button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
508
+ button.chat-composer-context:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
496
509
 
497
510
  /* Composer send/cancel button row: a non-wrapping cluster pinned to the
498
511
  bottom-right of the flex-end composer (was unstyled, so the send button
@@ -512,7 +525,7 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
512
525
  .agentchat-followups { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
513
526
 
514
527
  /* --- C1: stale/idle dashboard status (static, NOT pulsing). --- */
515
- .ds-dash-status.is-stale { color: var(--amber); }
528
+ .ds-dash-status.is-stale { color: var(--stale); }
516
529
  .ds-dash-status.is-running { color: var(--accent); }
517
530
 
518
531
  /* --- C2: dashboard sort/filter toolbar + stream-state line. --- */
@@ -556,7 +569,7 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
556
569
  /* Stale/idle card: a positive amber inset bar (mirrors is-active) so a stuck
557
570
  agent is flagged in a dense grid, not merely faded near-invisibly. The word
558
571
  'idle' co-carries state, so this stays colour-blind safe. */
559
- .ds-dash-card.is-stale { box-shadow: inset 2px 0 0 var(--amber); }
572
+ .ds-dash-card.is-stale { box-shadow: inset 2px 0 0 var(--stale); }
560
573
 
561
574
  /* --- H3: dashboard live disc pulses; stale/error do not (handled by H1). --- */
562
575
 
@@ -666,7 +679,7 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
666
679
  font: inherit; color: inherit; text-decoration: underline dotted;
667
680
  }
668
681
  .chat-composer-context-bit:hover { color: var(--fg-2); }
669
- .chat-composer-context-bit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
682
+ .chat-composer-context-bit:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
670
683
  @media (pointer: coarse) {
671
684
  .chat-composer-context-bit { min-height: 44px; }
672
685
  }
@@ -745,6 +758,11 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
745
758
  .chat-msg-flat.you .chat-role { color: var(--accent); }
746
759
  .chat-msg-flat .chat-bubble { background: none; border: none; border-radius: 0; padding: 0; max-width: 100%; box-shadow: none; transform: none; }
747
760
  .chat-msg-flat.you .chat-bubble { background: none; color: inherit; }
761
+ /* Flat user turns reset their bubble to transparent, but the messenger rule
762
+ (.chat-msg.you .chat-bubble code) tints inline code on --accent-fg, which is
763
+ invisible/wrong on the bare page surface. Reset to the neutral fg-10% tint the
764
+ sibling assistant turns get (wins over app-shell.css by later source order). */
765
+ .chat-msg-flat.you .chat-bubble code { background: color-mix(in oklab, var(--fg) 10%, transparent); }
748
766
  /* Flat turns strip the bubble padding, so the role label sat 4px above the first
749
767
  paragraph and the .them tint hugged text flush. Restore reading rhythm + give
750
768
  the tinted assistant row inner breathing room so it reads as a card. */
@@ -768,6 +786,16 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
768
786
  ladder stays monotonic): assistant 5% > its 3% rest; user 4% > transparent. */
769
787
  .chat-msg-flat.them:hover { background: color-mix(in oklab, var(--fg) 5%, transparent); }
770
788
  .chat-msg-flat.you:hover { background: color-mix(in oklab, var(--fg) 4%, transparent); }
789
+ /* A turn whose only assistant content is a bordered tool card or code block
790
+ already carries its own --bg surface + rule border; the turn-level .them tint
791
+ would stack a second, wider-than-measure surface behind it. Suppress it so the
792
+ card is the single surface (claude.ai/code keeps tool cards on bare canvas).
793
+ Selectors mirror the breakout pair above so suppression + widening always
794
+ cover the same turns. */
795
+ .chat-msg-flat.them:has(.chat-tool),
796
+ .chat-msg-flat.them:has(.chat-bubble.chat-code),
797
+ .chat-msg-flat.them:has(.chat-tool):hover,
798
+ .chat-msg-flat.them:has(.chat-bubble.chat-code):hover { background: none; }
771
799
  .chat-msg-flat:hover .chat-bubble { transform: none; box-shadow: none; background: none; }
772
800
  .chat-msg-flat .chat-avatar { display: none; }
773
801
 
@@ -806,7 +834,9 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
806
834
  .chat-msg .chat-tool.tool-done .chat-tool-status { color: var(--success); background: color-mix(in oklab, var(--success) 12%, transparent); }
807
835
  .chat-tool-body { border-top: var(--bw-hair) solid var(--rule); padding: var(--space-2) var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
808
836
  .chat-tool-section { display: flex; flex-direction: column; gap: var(--space-1); }
809
- .chat-tool-section-label { font-size: var(--fs-tiny); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--fg-3); }
837
+ .chat-tool-section-label { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); font-size: var(--fs-tiny); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--fg-3); }
838
+ .chat-tool-copy { position: static; opacity: 0; }
839
+ .chat-tool-section:hover .chat-tool-copy, .chat-tool-section:focus-within .chat-tool-copy { opacity: 1; }
810
840
  .chat-tool-pre { margin: 0; padding: var(--space-2); background: var(--bg-2); border-radius: var(--r-1); font-family: var(--ff-mono); font-size: var(--fs-tiny); line-height: 1.45; overflow-x: auto; max-height: 320px; overflow-y: auto; }
811
841
  .chat-tool-pre.is-error { color: var(--flame); }
812
842
  .chat-tool-pre.chat-tool-empty { color: var(--fg-3); }
@@ -842,6 +872,11 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
842
872
  accent), placed last so its inset bar wins source order over is-active/is-stale.
843
873
  The full-perimeter border-color (line ~396) stays for extra severity emphasis. */
844
874
  .ds-dash-card.is-error { box-shadow: inset 2px 0 0 var(--flame); }
875
+ /* A newly-arrived AND errored card: keep the LEFT inset bar flame (severity
876
+ precedence, never accent) while restoring the is-new arrival outline as a
877
+ full-perimeter accent glow that does not collide with the flame bar. Placed
878
+ after both is-new and is-error so it wins source order. */
879
+ .ds-dash-card.is-new.is-error { box-shadow: inset 2px 0 0 var(--flame), 0 0 0 1px color-mix(in oklab, var(--accent) 40%, transparent); }
845
880
  @media (prefers-reduced-motion: no-preference) {
846
881
  .ds-dash-card.is-new { animation: ds-card-in var(--dur-slow) var(--ease); }
847
882
  }
@@ -851,9 +886,17 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
851
886
  .ds-dash-group { display: flex; flex-direction: column; gap: var(--space-2); }
852
887
  .ds-dash-group-label { font-size: var(--fs-tiny); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--fg-3); padding: 0 var(--space-1); }
853
888
  .ds-dash-breakdown { display: flex; align-items: center; gap: var(--space-1); font-size: var(--fs-sm); color: var(--fg-2); }
854
- .ds-dash-breakdown .seg.is-running { color: var(--accent); font-weight: 600; }
855
- .ds-dash-breakdown .seg.is-error { color: var(--flame); font-weight: 600; }
856
- .ds-dash-breakdown .seg.is-idle { color: var(--amber); font-weight: 600; }
889
+ /* Shape-channel discs mirror the .status-dot-disc vocabulary (solid /
890
+ solid+halo / hollow ring) so a colour-blind user can scan the breakdown by
891
+ shape, not hue alone. CSS-drawn discs, not glyphs (glyph policy clean). */
892
+ .ds-dash-breakdown .seg { display: inline-flex; align-items: center; gap: var(--space-1); font-weight: 600; }
893
+ .ds-dash-breakdown .seg::before { content: ''; width: 7px; height: 7px; border-radius: 50%; flex: none; }
894
+ .ds-dash-breakdown .seg.is-running { color: var(--accent); }
895
+ .ds-dash-breakdown .seg.is-running::before { background: var(--accent); }
896
+ .ds-dash-breakdown .seg.is-error { color: var(--flame); }
897
+ .ds-dash-breakdown .seg.is-error::before { background: var(--flame); box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--flame) 38%, transparent); }
898
+ .ds-dash-breakdown .seg.is-idle { color: var(--amber); }
899
+ .ds-dash-breakdown .seg.is-idle::before { background: transparent; box-shadow: inset 0 0 0 2px var(--amber); }
857
900
  .ds-dash-stream-disc { display: inline-flex; align-items: center; gap: var(--space-1); }
858
901
  .ds-dash-selectall { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--fs-tiny); color: var(--fg-2); cursor: pointer; background: none; border: none; padding: var(--space-1); }
859
902
  .ds-dash-selectall:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@@ -925,3 +968,18 @@ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); o
925
968
  @media (min-width: 1600px) {
926
969
  .agentchat-thread { --measure: 84ch; }
927
970
  }
971
+
972
+ /* Stop/cancel composer control: re-tone the accent send button as a quiet
973
+ neutral control so 'stop generating' reads as distinct from accent send.
974
+ Flame/warn stay reserved (flame=error STATUS, warn=destructive ACTION); a
975
+ neutral quiet tone is the claude-code-aligned read for an in-flight stop. */
976
+ .chat-composer .send.cancel {
977
+ background: var(--bg-3);
978
+ color: var(--fg);
979
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule-strong, var(--rule));
980
+ }
981
+ .chat-composer .send.cancel:hover {
982
+ background: color-mix(in oklab, var(--fg) 8%, var(--bg-3));
983
+ filter: none;
984
+ }
985
+ .chat-composer .send.cancel:active { filter: brightness(0.96); }
@@ -18,6 +18,8 @@
18
18
  --ink: #131318;
19
19
  --ink-2: #25252C;
20
20
  --ink-3: #54545A; /* WCAG AAA on --paper (~7.1:1); was #6A6A70 (AA only) */
21
+ --ink-3-dark: #464650; /* dark theme bg-3 (tier-3 surface) */
22
+ --paper-3-dark: #B4B4BC; /* dark theme fg-3 (tier-3 text) */
21
23
 
22
24
  /* Lore palette */
23
25
  --green: #247420;
@@ -250,10 +252,10 @@ select:focus-visible {
250
252
  color-scheme: dark;
251
253
  --bg: var(--ink);
252
254
  --bg-2: var(--ink-2);
253
- --bg-3: #464650;
255
+ --bg-3: var(--ink-3-dark);
254
256
  --fg: var(--paper);
255
257
  --fg-2: var(--paper-2);
256
- --fg-3: #B4B4BC;
258
+ --fg-3: var(--paper-3-dark);
257
259
  --panel-bg: var(--bg);
258
260
  --panel-bg-2: var(--bg-2);
259
261
  --panel-0: var(--bg);
@@ -280,18 +282,20 @@ select:focus-visible {
280
282
  --code-num: var(--sun);
281
283
  }
282
284
 
283
- /* NOTE: the [data-theme="auto"] block below intentionally duplicates the
284
- [data-theme="ink"/"dark"] block above. They are NOT literally identical —
285
- the explicit-dark block also overrides --danger for contrast so they are
286
- kept separate rather than consolidated to avoid a behavior change. */
285
+ /* NOTE: the [data-theme="auto"] block below mirrors the
286
+ [data-theme="ink"/"dark"] block above token-for-token. 'auto + OS-dark' and
287
+ 'explicit dark' are the same visual context and resolve every token
288
+ including --danger identically. The two are kept as separate selectors
289
+ only because one is media-gated; a future de-drift can consolidate them into
290
+ a shared selector list once a build step can hoist the media query. */
287
291
  @media (prefers-color-scheme: dark) {
288
292
  [data-theme="auto"] {
289
293
  --bg: var(--ink);
290
294
  --bg-2: var(--ink-2);
291
- --bg-3: #464650;
295
+ --bg-3: var(--ink-3-dark);
292
296
  --fg: var(--paper);
293
297
  --fg-2: var(--paper-2);
294
- --fg-3: #B4B4BC;
298
+ --fg-3: var(--paper-3-dark);
295
299
  --panel-bg: var(--bg);
296
300
  --panel-bg-2: var(--bg-2);
297
301
  --panel-0: var(--bg);
@@ -304,6 +308,7 @@ select:focus-visible {
304
308
  --accent: var(--accent-bright, var(--green-2));
305
309
  --accent-fg: var(--ink);
306
310
  --accent-bright: var(--green-2);
311
+ --danger: oklch(0.68 0.19 25);
307
312
  --flame: #FF5A1F;
308
313
  --amber: #D9A93A;
309
314
  --warn: #FF5A52;
@@ -373,7 +378,7 @@ select:focus-visible {
373
378
  --fg: var(--ink); --fg-2: var(--ink-2); --fg-3: var(--ink-3);
374
379
  --accent: var(--green); --accent-fg: var(--paper);
375
380
  /* A paper island under a dark ancestor must not inherit the dark signal pair. */
376
- --flame: #C53E00; --amber: #8A6512;
381
+ --flame: #C53E00; --amber: #8A6512; --warn: #E0241A; --sky: #3A6EFF;
377
382
  }
378
383
 
379
384
  /* thebird — warm-paper brand preset. A first-class swappable theme: it lives