@workerdeck/ui 3.5.0 → 3.6.1
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/build/index.mjs +127 -23
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +43 -0
- package/package.json +4 -4
- package/src/components/agent/SessionBrowser.tsx +3 -1
- package/src/components/agent/SessionGroups.tsx +126 -3
- package/src/components/agent/SessionItem.tsx +37 -16
- package/src/styles/theme.css +5 -0
package/build/scoped.css
CHANGED
|
@@ -440,6 +440,9 @@
|
|
|
440
440
|
.wd-root .-mr-1 {
|
|
441
441
|
margin-right: calc(var(--spacing) * -1);
|
|
442
442
|
}
|
|
443
|
+
.wd-root .-mr-1\.5 {
|
|
444
|
+
margin-right: calc(var(--spacing) * -1.5);
|
|
445
|
+
}
|
|
443
446
|
.wd-root .mr-1 {
|
|
444
447
|
margin-right: var(--spacing);
|
|
445
448
|
}
|
|
@@ -697,6 +700,9 @@
|
|
|
697
700
|
.wd-root .w-36 {
|
|
698
701
|
width: calc(var(--spacing) * 36);
|
|
699
702
|
}
|
|
703
|
+
.wd-root .w-44 {
|
|
704
|
+
width: calc(var(--spacing) * 44);
|
|
705
|
+
}
|
|
700
706
|
.wd-root .w-48 {
|
|
701
707
|
width: calc(var(--spacing) * 48);
|
|
702
708
|
}
|
|
@@ -757,6 +763,9 @@
|
|
|
757
763
|
.wd-root .max-w-full {
|
|
758
764
|
max-width: 100%;
|
|
759
765
|
}
|
|
766
|
+
.wd-root .max-w-max {
|
|
767
|
+
max-width: max-content;
|
|
768
|
+
}
|
|
760
769
|
.wd-root .max-w-md {
|
|
761
770
|
max-width: var(--container-md);
|
|
762
771
|
}
|
|
@@ -817,6 +826,9 @@
|
|
|
817
826
|
.wd-root .grow {
|
|
818
827
|
flex-grow: 1;
|
|
819
828
|
}
|
|
829
|
+
.wd-root .basis-0 {
|
|
830
|
+
flex-basis: 0px;
|
|
831
|
+
}
|
|
820
832
|
.wd-root .border-collapse {
|
|
821
833
|
border-collapse: collapse;
|
|
822
834
|
}
|
|
@@ -891,6 +903,9 @@
|
|
|
891
903
|
.wd-root .appearance-none {
|
|
892
904
|
appearance: none;
|
|
893
905
|
}
|
|
906
|
+
.wd-root .grid-cols-4 {
|
|
907
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
908
|
+
}
|
|
894
909
|
.wd-root .flex-col {
|
|
895
910
|
flex-direction: column;
|
|
896
911
|
}
|
|
@@ -1022,6 +1037,9 @@
|
|
|
1022
1037
|
.wd-root .rounded {
|
|
1023
1038
|
border-radius: 0.25rem;
|
|
1024
1039
|
}
|
|
1040
|
+
.wd-root .rounded-\[2px\] {
|
|
1041
|
+
border-radius: 2px;
|
|
1042
|
+
}
|
|
1025
1043
|
.wd-root .rounded-\[3px\] {
|
|
1026
1044
|
border-radius: 3px;
|
|
1027
1045
|
}
|
|
@@ -1293,6 +1311,9 @@
|
|
|
1293
1311
|
.wd-root .bg-red-100 {
|
|
1294
1312
|
background-color: var(--color-red-100);
|
|
1295
1313
|
}
|
|
1314
|
+
.wd-root .bg-row-attention {
|
|
1315
|
+
background-color: var(--row-attention);
|
|
1316
|
+
}
|
|
1296
1317
|
.wd-root .bg-row-hover\/50 {
|
|
1297
1318
|
background-color: var(--row-hover);
|
|
1298
1319
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -1878,12 +1899,22 @@
|
|
|
1878
1899
|
--tw-ring-color: color-mix(in oklab, var(--danger) 60%, transparent);
|
|
1879
1900
|
}
|
|
1880
1901
|
}
|
|
1902
|
+
.wd-root .ring-fg-3 {
|
|
1903
|
+
--tw-ring-color: var(--fg-3);
|
|
1904
|
+
}
|
|
1881
1905
|
.wd-root .ring-ring\/30 {
|
|
1882
1906
|
--tw-ring-color: var(--ring);
|
|
1883
1907
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1884
1908
|
--tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
|
|
1885
1909
|
}
|
|
1886
1910
|
}
|
|
1911
|
+
.wd-root .ring-offset-1 {
|
|
1912
|
+
--tw-ring-offset-width: 1px;
|
|
1913
|
+
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
1914
|
+
}
|
|
1915
|
+
.wd-root .ring-offset-surface {
|
|
1916
|
+
--tw-ring-offset-color: var(--surface);
|
|
1917
|
+
}
|
|
1887
1918
|
.wd-root .outline {
|
|
1888
1919
|
outline-style: var(--tw-outline-style);
|
|
1889
1920
|
outline-width: 1px;
|
|
@@ -1977,6 +2008,9 @@
|
|
|
1977
2008
|
.wd-root .\[image-rendering\:pixelated\] {
|
|
1978
2009
|
image-rendering: pixelated;
|
|
1979
2010
|
}
|
|
2011
|
+
.wd-root .ring-inset {
|
|
2012
|
+
--tw-ring-inset: inset;
|
|
2013
|
+
}
|
|
1980
2014
|
@media (hover: hover) {
|
|
1981
2015
|
.wd-root .group-hover\:block:is(:where(.group):hover *) {
|
|
1982
2016
|
display: block;
|
|
@@ -2145,6 +2179,13 @@
|
|
|
2145
2179
|
.wd-root .hover\:opacity-100:hover {
|
|
2146
2180
|
opacity: 100%;
|
|
2147
2181
|
}
|
|
2182
|
+
.wd-root .hover\:ring-1:hover {
|
|
2183
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
2184
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2185
|
+
}
|
|
2186
|
+
.wd-root .hover\:ring-border:hover {
|
|
2187
|
+
--tw-ring-color: var(--border);
|
|
2188
|
+
}
|
|
2148
2189
|
}
|
|
2149
2190
|
.wd-root .focus-visible\:border-ring:focus-visible {
|
|
2150
2191
|
border-color: var(--ring);
|
|
@@ -3212,6 +3253,7 @@
|
|
|
3212
3253
|
--row-active: rgb(0 0 0 / 0.08);
|
|
3213
3254
|
--row-selected: #cfe4f7;
|
|
3214
3255
|
--row-selected-weak: #e4e6f1;
|
|
3256
|
+
--row-attention: #fbdcb8;
|
|
3215
3257
|
--badge: #c4c4c4;
|
|
3216
3258
|
--badge-fg: #333333;
|
|
3217
3259
|
--border: #ebebeb;
|
|
@@ -3265,6 +3307,7 @@
|
|
|
3265
3307
|
--row-active: rgb(255 255 255 / 0.1);
|
|
3266
3308
|
--row-selected: #04395e;
|
|
3267
3309
|
--row-selected-weak: #37373d;
|
|
3310
|
+
--row-attention: #5c3906;
|
|
3268
3311
|
--badge: #616161;
|
|
3269
3312
|
--badge-fg: #cccccc;
|
|
3270
3313
|
--border: #1f1f1f;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workerdeck/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Styled agent-control component library for WorkerDeck: session panel, transcript with streaming, tool-call cards, permission prompts, session list, composer. Tailwind v4 + Base UI + cva. Pairs with the headless @workerdeck/react layer.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"streamdown": "^2.6.0",
|
|
40
40
|
"tailwind-merge": "^3.7.0",
|
|
41
41
|
"use-stick-to-bottom": "^1.1.6",
|
|
42
|
-
"@workerdeck/
|
|
43
|
-
"@workerdeck/
|
|
44
|
-
"@workerdeck/
|
|
42
|
+
"@workerdeck/client": "3.6.1",
|
|
43
|
+
"@workerdeck/react": "3.6.1",
|
|
44
|
+
"@workerdeck/protocol": "3.6.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"monaco-editor": "^0.56.0",
|
|
@@ -143,6 +143,8 @@ export function SessionBrowser({
|
|
|
143
143
|
return (
|
|
144
144
|
<CustomGroupHeader
|
|
145
145
|
group={group}
|
|
146
|
+
look={config.customGroups?.find((custom) => custom.id === group.custom)}
|
|
147
|
+
onStyle={(look) => group.custom && drag.style(group.custom, look)}
|
|
146
148
|
editing={group.custom !== undefined && editingGroup === group.custom}
|
|
147
149
|
onEditingChange={(editing) => setEditingGroup(editing ? group.custom : undefined)}
|
|
148
150
|
onRename={(name) => group.custom && drag.rename(group.custom, name)}
|
|
@@ -246,7 +248,7 @@ export function SessionBrowser({
|
|
|
246
248
|
<Empty icon={<Layers />} title="Nothing here" description="No session to show." />
|
|
247
249
|
)
|
|
248
250
|
) : (
|
|
249
|
-
<div className="flex flex-col gap-
|
|
251
|
+
<div className="flex flex-col gap-2 px-1">
|
|
250
252
|
{groups.map((group) => (
|
|
251
253
|
<div
|
|
252
254
|
key={group.key}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import type { DragEvent, HTMLAttributes, ReactNode } from 'react'
|
|
3
|
-
import { GripVertical, Pencil, Plus, Trash2 } from 'lucide-react'
|
|
3
|
+
import { GripVertical, Image as ImageIcon, Pencil, Plus, Trash2 } from 'lucide-react'
|
|
4
4
|
import {
|
|
5
5
|
addCustomGroup,
|
|
6
6
|
moveCustomGroup,
|
|
@@ -9,11 +9,15 @@ import {
|
|
|
9
9
|
removeCustomGroup,
|
|
10
10
|
renameCustomGroup,
|
|
11
11
|
sessionKey,
|
|
12
|
+
styleCustomGroup,
|
|
13
|
+
customGroupColor,
|
|
14
|
+
PROJECT_ACCENTS,
|
|
12
15
|
} from '@workerdeck/protocol'
|
|
13
16
|
import type { CustomGroup, SessionGroup, SessionRow } from '@workerdeck/protocol'
|
|
14
17
|
import { dropZone, joinDrop, memberDrop, type DropZone, type TeamMove } from '../../lib/team-drop.ts'
|
|
15
18
|
import { Button } from '../ui/Button.tsx'
|
|
16
19
|
import { Input } from '../ui/Input.tsx'
|
|
20
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../ui/Popover.tsx'
|
|
17
21
|
import { cn } from '../../lib/utils.ts'
|
|
18
22
|
|
|
19
23
|
type DragItem = { kind: 'session'; key: string } | { kind: 'group'; id: string }
|
|
@@ -213,23 +217,29 @@ export function useGroupDrag(
|
|
|
213
217
|
return id
|
|
214
218
|
},
|
|
215
219
|
rename: (id: string, name: string) => onChange(renameCustomGroup(groups, id, name)),
|
|
220
|
+
style: (id: string, look: { color?: string | null; icon?: string | null }) => onChange(styleCustomGroup(groups, id, look)),
|
|
216
221
|
remove: (id: string) => onChange(removeCustomGroup(groups, id)),
|
|
217
222
|
}
|
|
218
223
|
}
|
|
219
224
|
|
|
220
225
|
export function CustomGroupHeader({
|
|
221
226
|
group,
|
|
227
|
+
look,
|
|
222
228
|
editing,
|
|
223
229
|
onEditingChange,
|
|
224
230
|
onRename,
|
|
225
231
|
onRemove,
|
|
232
|
+
onStyle,
|
|
226
233
|
dragProps,
|
|
227
234
|
}: {
|
|
228
235
|
group: SessionGroup
|
|
236
|
+
// The stored group, for its colour and image; absent for the ungrouped bucket.
|
|
237
|
+
look?: CustomGroup
|
|
229
238
|
editing: boolean
|
|
230
239
|
onEditingChange: (editing: boolean) => void
|
|
231
240
|
onRename: (name: string) => void
|
|
232
241
|
onRemove: () => void
|
|
242
|
+
onStyle?: (look: { color?: string | null; icon?: string | null }) => void
|
|
233
243
|
dragProps: HTMLAttributes<HTMLDivElement>
|
|
234
244
|
}) {
|
|
235
245
|
if (editing) {
|
|
@@ -242,9 +252,17 @@ export function CustomGroupHeader({
|
|
|
242
252
|
{...dragProps}
|
|
243
253
|
label={group.label ?? ''}
|
|
244
254
|
count={group.rows.length}
|
|
255
|
+
caps={!custom}
|
|
245
256
|
className={cn('group/heading', custom && 'cursor-grab')}
|
|
246
257
|
onDoubleClick={custom ? () => onEditingChange(true) : undefined}
|
|
247
|
-
leading={
|
|
258
|
+
leading={
|
|
259
|
+
custom ? (
|
|
260
|
+
<>
|
|
261
|
+
<GripVertical className="-mr-1.5 -ml-1.5 size-3 opacity-0 group-hover/heading:opacity-60" />
|
|
262
|
+
{look ? <GroupBadge group={look} onStyle={onStyle} /> : null}
|
|
263
|
+
</>
|
|
264
|
+
) : undefined
|
|
265
|
+
}
|
|
248
266
|
actions={
|
|
249
267
|
custom ? (
|
|
250
268
|
<>
|
|
@@ -298,6 +316,7 @@ export function GroupHeading({
|
|
|
298
316
|
count,
|
|
299
317
|
leading,
|
|
300
318
|
actions,
|
|
319
|
+
caps = true,
|
|
301
320
|
className,
|
|
302
321
|
...rest
|
|
303
322
|
}: {
|
|
@@ -305,11 +324,13 @@ export function GroupHeading({
|
|
|
305
324
|
count: number
|
|
306
325
|
leading?: ReactNode
|
|
307
326
|
actions?: ReactNode
|
|
327
|
+
// Off for a name the operator typed, which keeps its own case.
|
|
328
|
+
caps?: boolean
|
|
308
329
|
} & HTMLAttributes<HTMLDivElement>) {
|
|
309
330
|
return (
|
|
310
331
|
<div {...rest} className={cn('group/heading flex min-h-5 items-center gap-2 px-2 text-label font-medium text-fg-4', className)}>
|
|
311
332
|
{leading}
|
|
312
|
-
<span className=
|
|
333
|
+
<span className={cn('min-w-0 truncate', caps ? 'uppercase tracking-wide' : 'text-fg-3')}>{label}</span>
|
|
313
334
|
<span className="text-fg-4/70">{count}</span>
|
|
314
335
|
{actions ? (
|
|
315
336
|
<span className="ml-auto flex items-center gap-0.5 opacity-0 transition-opacity group-hover/heading:opacity-100 focus-within:opacity-100">
|
|
@@ -356,3 +377,105 @@ export function NewGroupButton({ onClick }: { onClick: () => void }) {
|
|
|
356
377
|
</Button>
|
|
357
378
|
)
|
|
358
379
|
}
|
|
380
|
+
|
|
381
|
+
const ICON_PX = 64
|
|
382
|
+
|
|
383
|
+
// A group's badge: its image, else its colour; pressing it picks either.
|
|
384
|
+
export function GroupBadge({
|
|
385
|
+
group,
|
|
386
|
+
onStyle,
|
|
387
|
+
}: {
|
|
388
|
+
group: CustomGroup
|
|
389
|
+
onStyle?: (look: { color?: string | null; icon?: string | null }) => void
|
|
390
|
+
}) {
|
|
391
|
+
const [error, setError] = useState<string>()
|
|
392
|
+
const file = useRef<HTMLInputElement>(null)
|
|
393
|
+
const mark = group.icon ? (
|
|
394
|
+
<img src={group.icon} alt="" className="size-3.5 rounded-[3px] object-cover" draggable={false} />
|
|
395
|
+
) : (
|
|
396
|
+
<span className="size-2 rounded-[2px]" style={{ background: customGroupColor(group) }} />
|
|
397
|
+
)
|
|
398
|
+
if (!onStyle) {
|
|
399
|
+
return <span className="flex size-3.5 shrink-0 items-center justify-center">{mark}</span>
|
|
400
|
+
}
|
|
401
|
+
return (
|
|
402
|
+
<Popover>
|
|
403
|
+
<PopoverTrigger
|
|
404
|
+
render={
|
|
405
|
+
<button
|
|
406
|
+
type="button"
|
|
407
|
+
aria-label={`Colour or image for ${group.name}`}
|
|
408
|
+
title="Colour or image"
|
|
409
|
+
onClick={(e) => e.stopPropagation()}
|
|
410
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
411
|
+
className="flex size-3.5 shrink-0 items-center justify-center rounded-[3px] hover:ring-1 hover:ring-border"
|
|
412
|
+
>
|
|
413
|
+
{mark}
|
|
414
|
+
</button>
|
|
415
|
+
}
|
|
416
|
+
/>
|
|
417
|
+
<PopoverContent align="start" className="flex w-44 flex-col gap-2">
|
|
418
|
+
<div className="grid grid-cols-4 gap-1.5">
|
|
419
|
+
{PROJECT_ACCENTS.map((color) => (
|
|
420
|
+
<button
|
|
421
|
+
key={color}
|
|
422
|
+
type="button"
|
|
423
|
+
aria-label={`Colour ${color}`}
|
|
424
|
+
onClick={() => onStyle({ color, icon: null })}
|
|
425
|
+
className={cn(
|
|
426
|
+
'h-6 rounded-[4px] ring-offset-1 ring-offset-surface',
|
|
427
|
+
!group.icon && customGroupColor(group) === color && 'ring-2 ring-fg-3',
|
|
428
|
+
)}
|
|
429
|
+
style={{ background: color }}
|
|
430
|
+
/>
|
|
431
|
+
))}
|
|
432
|
+
</div>
|
|
433
|
+
<input
|
|
434
|
+
ref={file}
|
|
435
|
+
type="file"
|
|
436
|
+
accept="image/*"
|
|
437
|
+
className="hidden"
|
|
438
|
+
onChange={(e) => {
|
|
439
|
+
const picked = e.target.files?.[0]
|
|
440
|
+
e.target.value = ''
|
|
441
|
+
if (!picked) {
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
setError(undefined)
|
|
445
|
+
void iconFromFile(picked)
|
|
446
|
+
.then((icon) => onStyle({ icon }))
|
|
447
|
+
.catch(() => setError('That image could not be read'))
|
|
448
|
+
}}
|
|
449
|
+
/>
|
|
450
|
+
<Button variant="outline" size="xs" onClick={() => file.current?.click()}>
|
|
451
|
+
<ImageIcon />
|
|
452
|
+
Choose image…
|
|
453
|
+
</Button>
|
|
454
|
+
{group.icon ? (
|
|
455
|
+
<Button variant="ghost" size="xs" onClick={() => onStyle({ icon: null })}>
|
|
456
|
+
Remove image
|
|
457
|
+
</Button>
|
|
458
|
+
) : null}
|
|
459
|
+
{error ? <span className="text-label text-danger">{error}</span> : null}
|
|
460
|
+
</PopoverContent>
|
|
461
|
+
</Popover>
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Scaled to cover a 64 px square and re-encoded, so a photo from disk stores as a few kB in the view config.
|
|
466
|
+
async function iconFromFile(file: File): Promise<string> {
|
|
467
|
+
const bitmap = await createImageBitmap(file)
|
|
468
|
+
const canvas = document.createElement('canvas')
|
|
469
|
+
canvas.width = ICON_PX
|
|
470
|
+
canvas.height = ICON_PX
|
|
471
|
+
const context = canvas.getContext('2d')
|
|
472
|
+
if (!context) {
|
|
473
|
+
throw new Error('no canvas')
|
|
474
|
+
}
|
|
475
|
+
const scale = Math.max(ICON_PX / bitmap.width, ICON_PX / bitmap.height)
|
|
476
|
+
const width = bitmap.width * scale
|
|
477
|
+
const height = bitmap.height * scale
|
|
478
|
+
context.drawImage(bitmap, (ICON_PX - width) / 2, (ICON_PX - height) / 2, width, height)
|
|
479
|
+
bitmap.close()
|
|
480
|
+
return canvas.toDataURL('image/png')
|
|
481
|
+
}
|
|
@@ -2,7 +2,7 @@ import { Fragment, useEffect, useRef, useState } from 'react'
|
|
|
2
2
|
import { Users } from 'lucide-react'
|
|
3
3
|
import type { ReactNode } from 'react'
|
|
4
4
|
import { projectLabel, projectName, projectSubpath, sessionLabel } from '@workerdeck/protocol'
|
|
5
|
-
import type { SessionRow, SessionState, SessionTask, StepDisplay, SubagentDisplay } from '@workerdeck/protocol'
|
|
5
|
+
import type { SessionInfo, SessionRow, SessionState, SessionTask, StepDisplay, SubagentDisplay } from '@workerdeck/protocol'
|
|
6
6
|
import { AgentAvatar, LeadChip, avatarOf, type AgentAvatars } from './AgentAvatar.tsx'
|
|
7
7
|
import { ContextRing } from './ContextRing.tsx'
|
|
8
8
|
import { EngineIcon, vendorMarkClass, vendorTextClass } from './EngineIcon.tsx'
|
|
@@ -108,6 +108,9 @@ export function SessionItem({
|
|
|
108
108
|
].filter((part): part is string => Boolean(part))
|
|
109
109
|
|
|
110
110
|
const model = friendlyModel(info.model)
|
|
111
|
+
const status = statusLine(info.statusLabel)
|
|
112
|
+
// Its own approval or question, or (on a lead) a team member's: the one card that asks for an action.
|
|
113
|
+
const needsYou = row.state === 'attention' || row.teamState === 'attention' || badgeState === 'attention'
|
|
111
114
|
const parts: ReactNode[] = []
|
|
112
115
|
if (model) {
|
|
113
116
|
parts.push(
|
|
@@ -116,6 +119,13 @@ export function SessionItem({
|
|
|
116
119
|
</span>,
|
|
117
120
|
)
|
|
118
121
|
}
|
|
122
|
+
if (status) {
|
|
123
|
+
parts.push(
|
|
124
|
+
<span key="status" title={status} className="text-fg-3">
|
|
125
|
+
{status}
|
|
126
|
+
</span>,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
119
129
|
if (project !== undefined) {
|
|
120
130
|
parts.push(
|
|
121
131
|
<span key="project" title={projectTitle}>
|
|
@@ -221,7 +231,13 @@ export function SessionItem({
|
|
|
221
231
|
className={cn(
|
|
222
232
|
'group flex w-full cursor-pointer flex-col p-1 text-left outline-none',
|
|
223
233
|
'rounded-[4px] transition-colors focus-visible:ring-2 focus-visible:ring-ring',
|
|
224
|
-
|
|
234
|
+
needsYou
|
|
235
|
+
? cn('bg-row-attention', active && 'ring-1 ring-accent ring-inset')
|
|
236
|
+
: holdsOpenStep
|
|
237
|
+
? 'bg-row-selected-weak'
|
|
238
|
+
: active
|
|
239
|
+
? 'bg-row-selected'
|
|
240
|
+
: 'hover:bg-row-hover',
|
|
225
241
|
className,
|
|
226
242
|
)}
|
|
227
243
|
>
|
|
@@ -229,7 +245,13 @@ export function SessionItem({
|
|
|
229
245
|
<div className={cn('flex h-6 items-center gap-1.5 overflow-hidden py-0.5 pr-0.5 pl-1.5', info.engineAsleep && 'opacity-60')}>
|
|
230
246
|
<AgentAvatar row={row} image={avatarOf(avatars, agent)} size={22} />
|
|
231
247
|
{nameLabel}
|
|
232
|
-
{
|
|
248
|
+
{status ? (
|
|
249
|
+
<span title={status} className="min-w-0 flex-1 truncate text-body-sm text-fg-3">
|
|
250
|
+
{status}
|
|
251
|
+
</span>
|
|
252
|
+
) : (
|
|
253
|
+
<span className="flex-1" />
|
|
254
|
+
)}
|
|
233
255
|
{unread}
|
|
234
256
|
<span className="shrink-0 text-body-sm text-fg-4">{time}</span>
|
|
235
257
|
{actions}
|
|
@@ -241,6 +263,7 @@ export function SessionItem({
|
|
|
241
263
|
<div className="flex h-5 items-center gap-1.5 overflow-hidden">
|
|
242
264
|
{nameLabel}
|
|
243
265
|
{agent?.leads ? <LeadChip /> : null}
|
|
266
|
+
{agent ? <span className="w-max max-w-max min-w-0 grow basis-0 truncate text-label text-fg-4/70">{time}</span> : null}
|
|
244
267
|
<span className="flex-1" />
|
|
245
268
|
{unread}
|
|
246
269
|
{teamUnseen > 0 ? (
|
|
@@ -258,19 +281,13 @@ export function SessionItem({
|
|
|
258
281
|
<div className="flex h-5 items-center gap-1.5 overflow-hidden text-body-sm tracking-[-0.005em]">
|
|
259
282
|
{agent ? (
|
|
260
283
|
<>
|
|
261
|
-
{
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
</>
|
|
269
|
-
) : info.title ? (
|
|
270
|
-
'· '
|
|
271
|
-
) : null}
|
|
272
|
-
{time}
|
|
273
|
-
</span>
|
|
284
|
+
{model ? <span className={cn('shrink-0', vendorTextClass(engine, info.model))}>{model}</span> : null}
|
|
285
|
+
{status ? (
|
|
286
|
+
<span title={status} className="min-w-0 truncate text-fg-3">
|
|
287
|
+
{model ? '· ' : ''}
|
|
288
|
+
{status}
|
|
289
|
+
</span>
|
|
290
|
+
) : null}
|
|
274
291
|
</>
|
|
275
292
|
) : (
|
|
276
293
|
<>
|
|
@@ -398,3 +415,7 @@ function NameEditor({
|
|
|
398
415
|
/>
|
|
399
416
|
)
|
|
400
417
|
}
|
|
418
|
+
|
|
419
|
+
export function statusLine(label: SessionInfo['statusLabel']): string | undefined {
|
|
420
|
+
return label ? (label.emoji ? `${label.emoji} ${label.text}` : label.text) : undefined
|
|
421
|
+
}
|
package/src/styles/theme.css
CHANGED
|
@@ -65,6 +65,9 @@
|
|
|
65
65
|
--row-active: rgb(0 0 0 / 0.08);
|
|
66
66
|
--row-selected: #cfe4f7;
|
|
67
67
|
--row-selected-weak: #e4e6f1;
|
|
68
|
+
/* A card that is waiting on the person: an approval or a question, its own or a team member's. As loud as
|
|
69
|
+
the selection, in the warning hue, because it is the one card that asks for an action. */
|
|
70
|
+
--row-attention: #fbdcb8;
|
|
68
71
|
|
|
69
72
|
/* A count badge that is NOT an alert. The unread badge wears `--accent`
|
|
70
73
|
because unread is a call to look; a plain count (how many sub-agents, how
|
|
@@ -187,6 +190,7 @@
|
|
|
187
190
|
--row-active: rgb(255 255 255 / 0.1);
|
|
188
191
|
--row-selected: #04395e;
|
|
189
192
|
--row-selected-weak: #37373d;
|
|
193
|
+
--row-attention: #5c3906;
|
|
190
194
|
|
|
191
195
|
/* See the light block. */
|
|
192
196
|
--badge: #616161;
|
|
@@ -352,6 +356,7 @@
|
|
|
352
356
|
--color-row-active: var(--row-active);
|
|
353
357
|
--color-row-selected: var(--row-selected);
|
|
354
358
|
--color-row-selected-weak: var(--row-selected-weak);
|
|
359
|
+
--color-row-attention: var(--row-attention);
|
|
355
360
|
--color-badge: var(--badge);
|
|
356
361
|
--color-badge-fg: var(--badge-fg);
|
|
357
362
|
/* Named code-bg (not code) so it can't shadow the --text-code font-size token:
|