anentrypoint-design 0.0.199 → 0.0.200

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
@@ -879,6 +879,10 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
879
879
  /* On mobile, fold meta under the title and keep actions inline. */
880
880
  .ds-file-row .ds-file-meta { font-size: var(--fs-micro); }
881
881
  .ds-file-row .ds-file-actions { opacity: 1; }
882
+ /* H4: floor finger-sized controls so file actions/sort/filter/breadcrumb are
883
+ reachable by touch (cohesion sweep). */
884
+ .ds-file-act, .ds-file-sort-btn, .ds-crumb-seg { min-height: 44px; }
885
+ .ds-file-filter-input { min-height: 44px; }
882
886
 
883
887
  /* Chat */
884
888
  .chat-stack { max-width: 100%; min-width: 0; }
@@ -2767,6 +2771,13 @@ input[type="password"]:not(:placeholder-shown) + .input-clear {
2767
2771
  .ws-shell.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr var(--ws-pane-w); grid-template-areas: "rail content pane"; }
2768
2772
  .ws-shell.ws-no-pane.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr; grid-template-areas: "rail content"; }
2769
2773
  .ws-shell.ws-no-pane:not(.ws-no-sessions) { grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) 1fr; grid-template-areas: "rail sessions content"; }
2774
+ /* G1 stable frame: when a tab has no pane but asks to keep the column geometry
2775
+ (ws-no-pane + ws-pane-collapsed), KEEP the 4-track grid with the pane track
2776
+ at width 0 instead of collapsing to a 3-track layout - so the shell does not
2777
+ re-flow its column count on tab switch. The pane track stays present (0px),
2778
+ so chat/history/files/live/settings share one geometry. */
2779
+ .ws-shell.ws-no-pane.ws-pane-collapsed:not(.ws-no-sessions) { grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) 1fr var(--ws-pane-w); grid-template-areas: "rail sessions content pane"; }
2780
+ .ws-shell.ws-no-pane.ws-pane-collapsed.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr var(--ws-pane-w); grid-template-areas: "rail content pane"; }
2770
2781
  .ws-shell.ws-rail-collapsed { --ws-rail-w: var(--ws-rail-w-collapsed); }
2771
2782
  .ws-shell.ws-pane-collapsed { --ws-pane-w: 0px; }
2772
2783
 
package/chat.css CHANGED
@@ -147,10 +147,12 @@
147
147
  .chat-msg-actions { display: flex; gap: 2px; margin-top: 3px; opacity: 0; transition: opacity var(--dur-snap, .12s) var(--ease, ease); }
148
148
  .chat-msg:hover .chat-msg-actions, .chat-msg:focus-within .chat-msg-actions { opacity: 1; }
149
149
  .chat-msg-action {
150
- display: inline-flex; align-items: center; justify-content: center;
151
- width: 26px; height: 26px; padding: 0; cursor: pointer;
150
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px;
151
+ min-width: 26px; height: 26px; padding: 0 6px; cursor: pointer;
152
152
  border: none; border-radius: var(--r-1, 4px); background: none; color: var(--fg-3);
153
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
153
154
  }
155
+ .chat-msg-action-label { font-size: var(--fs-tiny); }
154
156
  .chat-msg-action:hover { background: var(--bg-2); color: var(--fg); }
155
157
  .chat-msg-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
156
158
 
@@ -232,12 +234,27 @@
232
234
  Global status disc — a CSS-drawn status indicator (no text glyph) usable
233
235
  outside .agentchat-status (session rows, dashboard cards). Tone via modifier.
234
236
  ---------------------------------------------------------------------------- */
237
+ /* Single canonical status disc — one source for live/error/connecting/stale
238
+ everywhere (crumb dot, conversation row, dashboard card, composer status).
239
+ The app's index.html disc/pulse override is deleted in favour of this. Only
240
+ the -live disc pulses; -stale is deliberately static (a stuck session must NOT
241
+ read as busy). Pulse colour is derived from --accent via color-mix so it
242
+ tracks the theme instead of a hardcoded rgba. */
235
243
  .status-dot-disc {
236
244
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
237
245
  background: var(--fg-3); flex: 0 0 auto;
238
246
  }
239
- .status-dot-disc.status-dot-live { background: var(--green); }
247
+ .status-dot-disc.status-dot-live { background: var(--accent); animation: status-disc-pulse 1.8s ease-in-out infinite; }
240
248
  .status-dot-disc.status-dot-error { background: var(--flame); }
249
+ .status-dot-disc.status-dot-connecting { background: var(--amber, #d9a93a); }
250
+ .status-dot-disc.status-dot-stale { background: var(--amber, #d9a93a); }
251
+ @keyframes status-disc-pulse {
252
+ 0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--accent) 55%, transparent); }
253
+ 50% { box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent) 0%, transparent); }
254
+ }
255
+ @media (prefers-reduced-motion: reduce) {
256
+ .status-dot-disc.status-dot-live { animation: none; }
257
+ }
241
258
 
242
259
  /* ----------------------------------------------------------------------------
243
260
  ConversationList — left-rail "Chats" column.
@@ -277,8 +294,10 @@
277
294
  .ds-session-row.rail-green::before, .ds-session-row.rail-purple::before, .ds-session-row.rail-flame::before {
278
295
  content: ''; position: absolute; left: 0; top: 8px; bottom: 8px; width: 3px; border-radius: 3px;
279
296
  }
280
- .ds-session-row.rail-green::before { background: var(--green); }
281
- .ds-session-row.rail-purple::before { background: var(--purple, var(--accent)); }
297
+ /* Rail tones MUST match .row.rail-* in app-shell.css so the same semantic state
298
+ reads as one colour across the conversation list and content rows. */
299
+ .ds-session-row.rail-green::before { background: var(--accent); }
300
+ .ds-session-row.rail-purple::before { background: var(--purple-2, #7F18A4); }
282
301
  .ds-session-row.rail-flame::before { background: var(--flame); }
283
302
  .ds-session-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
284
303
  .ds-session-title { font-size: var(--fs-sm); color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -326,3 +345,131 @@
326
345
  .ds-context .row .meta { font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-2); }
327
346
  .ds-context .row .sub { font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
328
347
  .ds-context-actions { display: flex; gap: var(--space-2); }
348
+
349
+ /* ============================================================================
350
+ Cohesion sweep (gui-cohesion.js): one-product polish across surfaces.
351
+ ============================================================================ */
352
+
353
+ /* --- A1/A2/E3: per-block code copy. The <pre> is wrapped in .chat-code-block
354
+ (position:relative) so the copy button sits top-right and reveals on hover/
355
+ focus-within, matching the .chat-msg-actions pattern. The structured CodeNode
356
+ head + preview head place the button inline (always visible). --- */
357
+ .chat-code-block { position: relative; }
358
+ .chat-code-copy {
359
+ position: absolute; top: 6px; right: 6px;
360
+ display: inline-flex; align-items: center; justify-content: center;
361
+ min-width: 48px; min-height: 24px; padding: 2px 8px;
362
+ border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 4px);
363
+ background: var(--bg-2); color: var(--fg-2); cursor: pointer;
364
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
365
+ opacity: 0; transition: opacity var(--dur-snap, .12s) var(--ease, ease);
366
+ }
367
+ .chat-code-block:hover .chat-code-copy, .chat-code-block:focus-within .chat-code-copy { opacity: 1; }
368
+ .chat-code-copy.is-copied { color: var(--accent); border-color: var(--accent); }
369
+ .chat-code-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
370
+ /* Head-placed copy (structured CodeNode + FilePreviewCode) is always visible. */
371
+ .chat-code-head .spread { flex: 1; }
372
+ .chat-code-copy-head { position: static; opacity: 1; }
373
+
374
+ /* --- B1: streaming pre shell — monospaced bubble while a fenced block streams,
375
+ so it does not reflow prose->block on settle. --- */
376
+ .chat-stream-pre pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: var(--ff-mono); font-size: var(--fs-sm); }
377
+
378
+ /* --- A5: composer context line (agent / model / cwd at point of typing). --- */
379
+ .chat-composer-context {
380
+ display: block; width: 100%; text-align: left; padding: 4px var(--space-3) 0;
381
+ background: none; border: none; cursor: default; color: var(--fg-3);
382
+ font-family: var(--ff-mono); font-size: var(--fs-tiny);
383
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
384
+ }
385
+ button.chat-composer-context { cursor: pointer; }
386
+ button.chat-composer-context:hover { color: var(--fg-2); }
387
+ button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
388
+
389
+ /* --- B2: follow-up chips below the last settled assistant turn. --- */
390
+ .agentchat-followups { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
391
+
392
+ /* --- C1: stale/idle dashboard status (static, NOT pulsing). --- */
393
+ .ds-dash-status.is-stale { color: var(--amber, #d9a93a); }
394
+ .ds-dash-status.is-running { color: var(--accent); }
395
+
396
+ /* --- C2: dashboard sort/filter toolbar + stream-state line. --- */
397
+ .ds-dash-toolbar { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
398
+ .ds-dash-stream { font-size: var(--fs-tiny); color: var(--fg-3); }
399
+ .ds-dash-stream.is-lost { color: var(--flame); }
400
+ .ds-dash-stream.is-connecting { color: var(--amber, #d9a93a); }
401
+ .ds-dash-header .spread { flex: 1; }
402
+ .ds-dash-errors-toggle {
403
+ padding: 4px 10px; min-height: 32px; border: var(--bw-hair) solid var(--bg-3);
404
+ border-radius: var(--r-1); background: var(--bg-2); color: var(--fg-2);
405
+ cursor: pointer; font-family: var(--ff-body); font-size: var(--fs-tiny);
406
+ }
407
+ .ds-dash-errors-toggle.active { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); }
408
+ .ds-dash-errors-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
409
+
410
+ /* --- C3: per-card select checkbox. --- */
411
+ .ds-dash-select {
412
+ display: inline-flex; align-items: center; justify-content: center;
413
+ min-width: 24px; min-height: 24px; padding: 0; cursor: pointer;
414
+ border: none; background: none; color: var(--fg-3);
415
+ font-family: var(--ff-mono); font-size: var(--fs-sm);
416
+ }
417
+ .ds-dash-select[aria-checked="true"] { color: var(--accent); }
418
+ .ds-dash-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
419
+
420
+ /* --- C5: active dashboard card (current conversation). --- */
421
+ .ds-dash-card.is-active { box-shadow: inset 2px 0 0 var(--accent); }
422
+ .ds-dash-card.is-selected { background: var(--accent-tint); }
423
+ .ds-dash-card.is-stale { opacity: .92; }
424
+
425
+ /* --- H3: dashboard live disc pulses; stale/error do not (handled by H1). --- */
426
+
427
+ /* --- C6: per-tab conversation-list caption. --- */
428
+ .ds-session-caption { padding: var(--space-2) var(--space-3) 0; font-size: var(--fs-tiny); color: var(--fg-3); }
429
+
430
+ /* --- F1: context-pane empty state. --- */
431
+ .ds-context-empty { padding: var(--space-4) var(--space-3); color: var(--fg-3); font-size: var(--fs-sm); line-height: 1.5; }
432
+
433
+ /* --- E1: inline file preview pane (split view, non-modal). --- */
434
+ .ds-preview-pane { display: flex; flex-direction: column; min-height: 0; height: 100%; overflow: hidden; }
435
+ .ds-preview-pane:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
436
+ .ds-preview-pane .ds-preview-body { flex: 1; min-height: 0; overflow: auto; }
437
+ .ds-preview-pane-empty { align-items: center; justify-content: center; color: var(--fg-3); font-size: var(--fs-sm); }
438
+ .ds-preview-code-wrap { display: flex; flex-direction: column; min-height: 0; }
439
+ .ds-preview-code-head { flex: 0 0 auto; }
440
+ /* --- E2: prev/next stepper in preview head. --- */
441
+ .ds-preview-step { display: inline-flex; gap: 2px; }
442
+ .ds-preview-step .ds-file-act { min-width: 40px; padding: 0 6px; font-family: var(--ff-body); font-size: var(--fs-tiny); }
443
+
444
+ /* --- D1: file grid "show more" row. --- */
445
+ .ds-file-more { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); justify-content: center; }
446
+ .ds-file-more-count { font-size: var(--fs-tiny); color: var(--fg-3); }
447
+ .ds-file-more-btn {
448
+ padding: 4px 12px; min-height: 32px; border: var(--bw-hair) solid var(--bg-3);
449
+ border-radius: var(--r-1); background: var(--bg-2); color: var(--fg-2); cursor: pointer;
450
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
451
+ }
452
+ .ds-file-more-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
453
+ /* --- D3: locked/no-access file row. --- */
454
+ .ds-file-row.is-locked { opacity: .6; }
455
+
456
+ /* --- D4: roots picker segmented control. --- */
457
+ .ds-roots-picker { display: inline-flex; gap: 2px; padding: 2px; background: var(--bg-2); border-radius: var(--r-1); }
458
+ .ds-roots-tab {
459
+ padding: 4px 10px; min-height: 32px; border: none; border-radius: var(--r-1);
460
+ background: none; color: var(--fg-3); cursor: pointer;
461
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
462
+ }
463
+ .ds-roots-tab.active { background: var(--bg); color: var(--fg); }
464
+ .ds-roots-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
465
+
466
+ /* --- H4: touch targets. On a coarse pointer / narrow screen the hover-revealed
467
+ action rows are made permanently visible and small controls floor to 44px so
468
+ they are reachable by finger. --- */
469
+ @media (pointer: coarse), (max-width: 640px) {
470
+ .chat-msg-actions { opacity: 1; }
471
+ .chat-code-copy { opacity: 1; }
472
+ .chat-msg-action { min-width: 44px; height: 44px; }
473
+ .ds-dash-select { min-width: 44px; min-height: 44px; }
474
+ .ds-file-more-btn, .ds-roots-tab, .ds-dash-errors-toggle { min-height: 44px; }
475
+ }
package/dist/247420.css CHANGED
@@ -1280,6 +1280,10 @@
1280
1280
  /* On mobile, fold meta under the title and keep actions inline. */
1281
1281
  .ds-247420 .ds-file-row .ds-file-meta { font-size: var(--fs-micro); }
1282
1282
  .ds-247420 .ds-file-row .ds-file-actions { opacity: 1; }
1283
+ /* H4: floor finger-sized controls so file actions/sort/filter/breadcrumb are
1284
+ reachable by touch (cohesion sweep). */
1285
+ .ds-247420 .ds-file-act, .ds-247420 .ds-file-sort-btn, .ds-247420 .ds-crumb-seg { min-height: 44px; }
1286
+ .ds-247420 .ds-file-filter-input { min-height: 44px; }
1283
1287
 
1284
1288
  /* Chat */
1285
1289
  .ds-247420 .chat-stack { max-width: 100%; min-width: 0; }
@@ -3159,6 +3163,13 @@
3159
3163
  .ds-247420 .ws-shell.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr var(--ws-pane-w); grid-template-areas: "rail content pane"; }
3160
3164
  .ds-247420 .ws-shell.ws-no-pane.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr; grid-template-areas: "rail content"; }
3161
3165
  .ds-247420 .ws-shell.ws-no-pane:not(.ws-no-sessions) { grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) 1fr; grid-template-areas: "rail sessions content"; }
3166
+ /* G1 stable frame: when a tab has no pane but asks to keep the column geometry
3167
+ (ws-no-pane + ws-pane-collapsed), KEEP the 4-track grid with the pane track
3168
+ at width 0 instead of collapsing to a 3-track layout - so the shell does not
3169
+ re-flow its column count on tab switch. The pane track stays present (0px),
3170
+ so chat/history/files/live/settings share one geometry. */
3171
+ .ds-247420 .ws-shell.ws-no-pane.ws-pane-collapsed:not(.ws-no-sessions) { grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) 1fr var(--ws-pane-w); grid-template-areas: "rail sessions content pane"; }
3172
+ .ds-247420 .ws-shell.ws-no-pane.ws-pane-collapsed.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr var(--ws-pane-w); grid-template-areas: "rail content pane"; }
3162
3173
  .ds-247420 .ws-shell.ws-rail-collapsed { --ws-rail-w: var(--ws-rail-w-collapsed); }
3163
3174
  .ds-247420 .ws-shell.ws-pane-collapsed { --ws-pane-w: 0px; }
3164
3175
 
@@ -4967,10 +4978,12 @@
4967
4978
  .ds-247420 .chat-msg-actions { display: flex; gap: 2px; margin-top: 3px; opacity: 0; transition: opacity var(--dur-snap, .12s) var(--ease, ease); }
4968
4979
  .ds-247420 .chat-msg:hover .chat-msg-actions, .ds-247420 .chat-msg:focus-within .chat-msg-actions { opacity: 1; }
4969
4980
  .ds-247420 .chat-msg-action {
4970
- display: inline-flex; align-items: center; justify-content: center;
4971
- width: 26px; height: 26px; padding: 0; cursor: pointer;
4981
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px;
4982
+ min-width: 26px; height: 26px; padding: 0 6px; cursor: pointer;
4972
4983
  border: none; border-radius: var(--r-1, 4px); background: none; color: var(--fg-3);
4984
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
4973
4985
  }
4986
+ .ds-247420 .chat-msg-action-label { font-size: var(--fs-tiny); }
4974
4987
  .ds-247420 .chat-msg-action:hover { background: var(--bg-2); color: var(--fg); }
4975
4988
  .ds-247420 .chat-msg-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
4976
4989
 
@@ -5052,12 +5065,27 @@
5052
5065
  Global status disc — a CSS-drawn status indicator (no text glyph) usable
5053
5066
  outside .agentchat-status (session rows, dashboard cards). Tone via modifier.
5054
5067
  ---------------------------------------------------------------------------- */
5068
+ /* Single canonical status disc — one source for live/error/connecting/stale
5069
+ everywhere (crumb dot, conversation row, dashboard card, composer status).
5070
+ The app's index.html disc/pulse override is deleted in favour of this. Only
5071
+ the -live disc pulses; -stale is deliberately static (a stuck session must NOT
5072
+ read as busy). Pulse colour is derived from --accent via color-mix so it
5073
+ tracks the theme instead of a hardcoded rgba. */
5055
5074
  .ds-247420 .status-dot-disc {
5056
5075
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
5057
5076
  background: var(--fg-3); flex: 0 0 auto;
5058
5077
  }
5059
- .ds-247420 .status-dot-disc.status-dot-live { background: var(--green); }
5078
+ .ds-247420 .status-dot-disc.status-dot-live { background: var(--accent); animation: status-disc-pulse 1.8s ease-in-out infinite; }
5060
5079
  .ds-247420 .status-dot-disc.status-dot-error { background: var(--flame); }
5080
+ .ds-247420 .status-dot-disc.status-dot-connecting { background: var(--amber, #d9a93a); }
5081
+ .ds-247420 .status-dot-disc.status-dot-stale { background: var(--amber, #d9a93a); }
5082
+ @keyframes status-disc-pulse {
5083
+ 0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--accent) 55%, transparent); }
5084
+ 50% { box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent) 0%, transparent); }
5085
+ }
5086
+ @media (prefers-reduced-motion: reduce) {
5087
+ .ds-247420 .status-dot-disc.status-dot-live { animation: none; }
5088
+ }
5061
5089
 
5062
5090
  /* ----------------------------------------------------------------------------
5063
5091
  ConversationList — left-rail "Chats" column.
@@ -5097,8 +5125,10 @@
5097
5125
  .ds-247420 .ds-session-row.rail-green::before, .ds-247420 .ds-session-row.rail-purple::before, .ds-247420 .ds-session-row.rail-flame::before {
5098
5126
  content: ''; position: absolute; left: 0; top: 8px; bottom: 8px; width: 3px; border-radius: 3px;
5099
5127
  }
5100
- .ds-247420 .ds-session-row.rail-green::before { background: var(--green); }
5101
- .ds-247420 .ds-session-row.rail-purple::before { background: var(--purple, var(--accent)); }
5128
+ /* Rail tones MUST match .row.rail-* in app-shell.css so the same semantic state
5129
+ reads as one colour across the conversation list and content rows. */
5130
+ .ds-247420 .ds-session-row.rail-green::before { background: var(--accent); }
5131
+ .ds-247420 .ds-session-row.rail-purple::before { background: var(--purple-2, #7F18A4); }
5102
5132
  .ds-247420 .ds-session-row.rail-flame::before { background: var(--flame); }
5103
5133
  .ds-247420 .ds-session-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
5104
5134
  .ds-247420 .ds-session-title { font-size: var(--fs-sm); color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -5147,6 +5177,134 @@
5147
5177
  .ds-247420 .ds-context .row .sub { font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
5148
5178
  .ds-247420 .ds-context-actions { display: flex; gap: var(--space-2); }
5149
5179
 
5180
+ /* ============================================================================
5181
+ Cohesion sweep (gui-cohesion.js): one-product polish across surfaces.
5182
+ ============================================================================ */
5183
+
5184
+ /* --- A1/A2/E3: per-block code copy. The <pre> is wrapped in .chat-code-block
5185
+ (position:relative) so the copy button sits top-right and reveals on hover/
5186
+ focus-within, matching the .chat-msg-actions pattern. The structured CodeNode
5187
+ head + preview head place the button inline (always visible). --- */
5188
+ .ds-247420 .chat-code-block { position: relative; }
5189
+ .ds-247420 .chat-code-copy {
5190
+ position: absolute; top: 6px; right: 6px;
5191
+ display: inline-flex; align-items: center; justify-content: center;
5192
+ min-width: 48px; min-height: 24px; padding: 2px 8px;
5193
+ border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1, 4px);
5194
+ background: var(--bg-2); color: var(--fg-2); cursor: pointer;
5195
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
5196
+ opacity: 0; transition: opacity var(--dur-snap, .12s) var(--ease, ease);
5197
+ }
5198
+ .ds-247420 .chat-code-block:hover .chat-code-copy, .ds-247420 .chat-code-block:focus-within .chat-code-copy { opacity: 1; }
5199
+ .ds-247420 .chat-code-copy.is-copied { color: var(--accent); border-color: var(--accent); }
5200
+ .ds-247420 .chat-code-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
5201
+ /* Head-placed copy (structured CodeNode + FilePreviewCode) is always visible. */
5202
+ .ds-247420 .chat-code-head .spread { flex: 1; }
5203
+ .ds-247420 .chat-code-copy-head { position: static; opacity: 1; }
5204
+
5205
+ /* --- B1: streaming pre shell — monospaced bubble while a fenced block streams,
5206
+ so it does not reflow prose->block on settle. --- */
5207
+ .ds-247420 .chat-stream-pre pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: var(--ff-mono); font-size: var(--fs-sm); }
5208
+
5209
+ /* --- A5: composer context line (agent / model / cwd at point of typing). --- */
5210
+ .ds-247420 .chat-composer-context {
5211
+ display: block; width: 100%; text-align: left; padding: 4px var(--space-3) 0;
5212
+ background: none; border: none; cursor: default; color: var(--fg-3);
5213
+ font-family: var(--ff-mono); font-size: var(--fs-tiny);
5214
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
5215
+ }
5216
+ .ds-247420 button.chat-composer-context { cursor: pointer; }
5217
+ .ds-247420 button.chat-composer-context:hover { color: var(--fg-2); }
5218
+ .ds-247420 button.chat-composer-context:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
5219
+
5220
+ /* --- B2: follow-up chips below the last settled assistant turn. --- */
5221
+ .ds-247420 .agentchat-followups { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
5222
+
5223
+ /* --- C1: stale/idle dashboard status (static, NOT pulsing). --- */
5224
+ .ds-247420 .ds-dash-status.is-stale { color: var(--amber, #d9a93a); }
5225
+ .ds-247420 .ds-dash-status.is-running { color: var(--accent); }
5226
+
5227
+ /* --- C2: dashboard sort/filter toolbar + stream-state line. --- */
5228
+ .ds-247420 .ds-dash-toolbar { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
5229
+ .ds-247420 .ds-dash-stream { font-size: var(--fs-tiny); color: var(--fg-3); }
5230
+ .ds-247420 .ds-dash-stream.is-lost { color: var(--flame); }
5231
+ .ds-247420 .ds-dash-stream.is-connecting { color: var(--amber, #d9a93a); }
5232
+ .ds-247420 .ds-dash-header .spread { flex: 1; }
5233
+ .ds-247420 .ds-dash-errors-toggle {
5234
+ padding: 4px 10px; min-height: 32px; border: var(--bw-hair) solid var(--bg-3);
5235
+ border-radius: var(--r-1); background: var(--bg-2); color: var(--fg-2);
5236
+ cursor: pointer; font-family: var(--ff-body); font-size: var(--fs-tiny);
5237
+ }
5238
+ .ds-247420 .ds-dash-errors-toggle.active { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); }
5239
+ .ds-247420 .ds-dash-errors-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
5240
+
5241
+ /* --- C3: per-card select checkbox. --- */
5242
+ .ds-247420 .ds-dash-select {
5243
+ display: inline-flex; align-items: center; justify-content: center;
5244
+ min-width: 24px; min-height: 24px; padding: 0; cursor: pointer;
5245
+ border: none; background: none; color: var(--fg-3);
5246
+ font-family: var(--ff-mono); font-size: var(--fs-sm);
5247
+ }
5248
+ .ds-247420 .ds-dash-select[aria-checked="true"] { color: var(--accent); }
5249
+ .ds-247420 .ds-dash-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
5250
+
5251
+ /* --- C5: active dashboard card (current conversation). --- */
5252
+ .ds-247420 .ds-dash-card.is-active { box-shadow: inset 2px 0 0 var(--accent); }
5253
+ .ds-247420 .ds-dash-card.is-selected { background: var(--accent-tint); }
5254
+ .ds-247420 .ds-dash-card.is-stale { opacity: .92; }
5255
+
5256
+ /* --- H3: dashboard live disc pulses; stale/error do not (handled by H1). --- */
5257
+
5258
+ /* --- C6: per-tab conversation-list caption. --- */
5259
+ .ds-247420 .ds-session-caption { padding: var(--space-2) var(--space-3) 0; font-size: var(--fs-tiny); color: var(--fg-3); }
5260
+
5261
+ /* --- F1: context-pane empty state. --- */
5262
+ .ds-247420 .ds-context-empty { padding: var(--space-4) var(--space-3); color: var(--fg-3); font-size: var(--fs-sm); line-height: 1.5; }
5263
+
5264
+ /* --- E1: inline file preview pane (split view, non-modal). --- */
5265
+ .ds-247420 .ds-preview-pane { display: flex; flex-direction: column; min-height: 0; height: 100%; overflow: hidden; }
5266
+ .ds-247420 .ds-preview-pane:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
5267
+ .ds-247420 .ds-preview-pane .ds-preview-body { flex: 1; min-height: 0; overflow: auto; }
5268
+ .ds-247420 .ds-preview-pane-empty { align-items: center; justify-content: center; color: var(--fg-3); font-size: var(--fs-sm); }
5269
+ .ds-247420 .ds-preview-code-wrap { display: flex; flex-direction: column; min-height: 0; }
5270
+ .ds-247420 .ds-preview-code-head { flex: 0 0 auto; }
5271
+ /* --- E2: prev/next stepper in preview head. --- */
5272
+ .ds-247420 .ds-preview-step { display: inline-flex; gap: 2px; }
5273
+ .ds-247420 .ds-preview-step .ds-file-act { min-width: 40px; padding: 0 6px; font-family: var(--ff-body); font-size: var(--fs-tiny); }
5274
+
5275
+ /* --- D1: file grid "show more" row. --- */
5276
+ .ds-247420 .ds-file-more { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); justify-content: center; }
5277
+ .ds-247420 .ds-file-more-count { font-size: var(--fs-tiny); color: var(--fg-3); }
5278
+ .ds-247420 .ds-file-more-btn {
5279
+ padding: 4px 12px; min-height: 32px; border: var(--bw-hair) solid var(--bg-3);
5280
+ border-radius: var(--r-1); background: var(--bg-2); color: var(--fg-2); cursor: pointer;
5281
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
5282
+ }
5283
+ .ds-247420 .ds-file-more-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
5284
+ /* --- D3: locked/no-access file row. --- */
5285
+ .ds-247420 .ds-file-row.is-locked { opacity: .6; }
5286
+
5287
+ /* --- D4: roots picker segmented control. --- */
5288
+ .ds-247420 .ds-roots-picker { display: inline-flex; gap: 2px; padding: 2px; background: var(--bg-2); border-radius: var(--r-1); }
5289
+ .ds-247420 .ds-roots-tab {
5290
+ padding: 4px 10px; min-height: 32px; border: none; border-radius: var(--r-1);
5291
+ background: none; color: var(--fg-3); cursor: pointer;
5292
+ font-family: var(--ff-body); font-size: var(--fs-tiny);
5293
+ }
5294
+ .ds-247420 .ds-roots-tab.active { background: var(--bg); color: var(--fg); }
5295
+ .ds-247420 .ds-roots-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
5296
+
5297
+ /* --- H4: touch targets. On a coarse pointer / narrow screen the hover-revealed
5298
+ action rows are made permanently visible and small controls floor to 44px so
5299
+ they are reachable by finger. --- */
5300
+ @media (pointer: coarse), (max-width: 640px) {
5301
+ .ds-247420 .chat-msg-actions { opacity: 1; }
5302
+ .ds-247420 .chat-code-copy { opacity: 1; }
5303
+ .ds-247420 .chat-msg-action { min-width: 44px; height: 44px; }
5304
+ .ds-247420 .ds-dash-select { min-width: 44px; min-height: 44px; }
5305
+ .ds-247420 .ds-file-more-btn, .ds-247420 .ds-roots-tab, .ds-247420 .ds-dash-errors-toggle { min-height: 44px; }
5306
+ }
5307
+
5150
5308
  /* editor-primitives.css */
5151
5309
  /* editor-primitives.css — chrome for in-engine editors, inspectors, IDEs,
5152
5310
  debug HUDs. All rules under .ds-247420 scope (build prefixes). Tokens