@workerdeck/ui 3.4.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/scoped.css CHANGED
@@ -311,18 +311,27 @@
311
311
  .wd-root .right-4 {
312
312
  right: calc(var(--spacing) * 4);
313
313
  }
314
+ .wd-root .-bottom-1 {
315
+ bottom: calc(var(--spacing) * -1);
316
+ }
314
317
  .wd-root .bottom-0 {
315
318
  bottom: 0px;
316
319
  }
317
320
  .wd-root .bottom-2 {
318
321
  bottom: calc(var(--spacing) * 2);
319
322
  }
323
+ .wd-root .bottom-3 {
324
+ bottom: calc(var(--spacing) * 3);
325
+ }
320
326
  .wd-root .bottom-4 {
321
327
  bottom: calc(var(--spacing) * 4);
322
328
  }
323
329
  .wd-root .bottom-full {
324
330
  bottom: 100%;
325
331
  }
332
+ .wd-root .-left-\[7px\] {
333
+ left: calc(7px * -1);
334
+ }
326
335
  .wd-root .left-0 {
327
336
  left: 0px;
328
337
  }
@@ -431,6 +440,9 @@
431
440
  .wd-root .-mr-1 {
432
441
  margin-right: calc(var(--spacing) * -1);
433
442
  }
443
+ .wd-root .-mr-1\.5 {
444
+ margin-right: calc(var(--spacing) * -1.5);
445
+ }
434
446
  .wd-root .mr-1 {
435
447
  margin-right: var(--spacing);
436
448
  }
@@ -455,6 +467,12 @@
455
467
  .wd-root .ml-1 {
456
468
  margin-left: var(--spacing);
457
469
  }
470
+ .wd-root .ml-3 {
471
+ margin-left: calc(var(--spacing) * 3);
472
+ }
473
+ .wd-root .ml-3\.5 {
474
+ margin-left: calc(var(--spacing) * 3.5);
475
+ }
458
476
  .wd-root .ml-auto {
459
477
  margin-left: auto;
460
478
  }
@@ -501,6 +519,10 @@
501
519
  width: calc(var(--spacing) * 1.5);
502
520
  height: calc(var(--spacing) * 1.5);
503
521
  }
522
+ .wd-root .size-1\/2 {
523
+ width: calc(1 / 2 * 100%);
524
+ height: calc(1 / 2 * 100%);
525
+ }
504
526
  .wd-root .size-2 {
505
527
  width: calc(var(--spacing) * 2);
506
528
  height: calc(var(--spacing) * 2);
@@ -678,6 +700,9 @@
678
700
  .wd-root .w-36 {
679
701
  width: calc(var(--spacing) * 36);
680
702
  }
703
+ .wd-root .w-44 {
704
+ width: calc(var(--spacing) * 44);
705
+ }
681
706
  .wd-root .w-48 {
682
707
  width: calc(var(--spacing) * 48);
683
708
  }
@@ -738,6 +763,9 @@
738
763
  .wd-root .max-w-full {
739
764
  max-width: 100%;
740
765
  }
766
+ .wd-root .max-w-max {
767
+ max-width: max-content;
768
+ }
741
769
  .wd-root .max-w-md {
742
770
  max-width: var(--container-md);
743
771
  }
@@ -798,6 +826,9 @@
798
826
  .wd-root .grow {
799
827
  flex-grow: 1;
800
828
  }
829
+ .wd-root .basis-0 {
830
+ flex-basis: 0px;
831
+ }
801
832
  .wd-root .border-collapse {
802
833
  border-collapse: collapse;
803
834
  }
@@ -872,6 +903,9 @@
872
903
  .wd-root .appearance-none {
873
904
  appearance: none;
874
905
  }
906
+ .wd-root .grid-cols-4 {
907
+ grid-template-columns: repeat(4, minmax(0, 1fr));
908
+ }
875
909
  .wd-root .flex-col {
876
910
  flex-direction: column;
877
911
  }
@@ -1003,6 +1037,12 @@
1003
1037
  .wd-root .rounded {
1004
1038
  border-radius: 0.25rem;
1005
1039
  }
1040
+ .wd-root .rounded-\[2px\] {
1041
+ border-radius: 2px;
1042
+ }
1043
+ .wd-root .rounded-\[3px\] {
1044
+ border-radius: 3px;
1045
+ }
1006
1046
  .wd-root .rounded-\[4px\] {
1007
1047
  border-radius: 4px;
1008
1048
  }
@@ -1064,6 +1104,12 @@
1064
1104
  .wd-root .border-\[var\(--wd-shell-accent\)\] {
1065
1105
  border-color: var(--wd-shell-accent);
1066
1106
  }
1107
+ .wd-root .border-accent\/50 {
1108
+ border-color: var(--accent);
1109
+ @supports (color: color-mix(in lab, red, red)) {
1110
+ border-color: color-mix(in oklab, var(--accent) 50%, transparent);
1111
+ }
1112
+ }
1067
1113
  .wd-root .border-border {
1068
1114
  border-color: var(--border);
1069
1115
  }
@@ -1139,6 +1185,12 @@
1139
1185
  .wd-root .bg-accent-bg {
1140
1186
  background-color: var(--accent-bg);
1141
1187
  }
1188
+ .wd-root .bg-accent\/10 {
1189
+ background-color: var(--accent);
1190
+ @supports (color: color-mix(in lab, red, red)) {
1191
+ background-color: color-mix(in oklab, var(--accent) 10%, transparent);
1192
+ }
1193
+ }
1142
1194
  .wd-root .bg-background {
1143
1195
  background-color: var(--background);
1144
1196
  }
@@ -1259,6 +1311,9 @@
1259
1311
  .wd-root .bg-red-100 {
1260
1312
  background-color: var(--color-red-100);
1261
1313
  }
1314
+ .wd-root .bg-row-attention {
1315
+ background-color: var(--row-attention);
1316
+ }
1262
1317
  .wd-root .bg-row-hover\/50 {
1263
1318
  background-color: var(--row-hover);
1264
1319
  @supports (color: color-mix(in lab, red, red)) {
@@ -1352,6 +1407,9 @@
1352
1407
  .wd-root .p-px {
1353
1408
  padding: 1px;
1354
1409
  }
1410
+ .wd-root .px-0\.5 {
1411
+ padding-inline: calc(var(--spacing) * 0.5);
1412
+ }
1355
1413
  .wd-root .px-1 {
1356
1414
  padding-inline: var(--spacing);
1357
1415
  }
@@ -1475,6 +1533,9 @@
1475
1533
  .wd-root .pb-\[var\(--wd-composer-padding\)\] {
1476
1534
  padding-bottom: var(--wd-composer-padding);
1477
1535
  }
1536
+ .wd-root .pl-1 {
1537
+ padding-left: var(--spacing);
1538
+ }
1478
1539
  .wd-root .pl-1\.5 {
1479
1540
  padding-left: calc(var(--spacing) * 1.5);
1480
1541
  }
@@ -1571,9 +1632,16 @@
1571
1632
  .wd-root .text-\[0\.75rem\] {
1572
1633
  font-size: 0.75rem;
1573
1634
  }
1635
+ .wd-root .text-\[0\.625rem\] {
1636
+ font-size: 0.625rem;
1637
+ }
1574
1638
  .wd-root .text-\[9px\] {
1575
1639
  font-size: 9px;
1576
1640
  }
1641
+ .wd-root .leading-3\.5 {
1642
+ --tw-leading: calc(var(--spacing) * 3.5);
1643
+ line-height: calc(var(--spacing) * 3.5);
1644
+ }
1577
1645
  .wd-root .leading-4 {
1578
1646
  --tw-leading: calc(var(--spacing) * 4);
1579
1647
  line-height: calc(var(--spacing) * 4);
@@ -1790,6 +1858,10 @@
1790
1858
  --tw-shadow: var(--shadow-xs);
1791
1859
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1792
1860
  }
1861
+ .wd-root .shadow-\[inset_0_-2px_0_0_var\(--color-accent\)\] {
1862
+ --tw-shadow: inset 0 -2px 0 0 var(--tw-shadow-color, var(--color-accent));
1863
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1864
+ }
1793
1865
  .wd-root .shadow-\[inset_0_2px_0_0_var\(--color-accent\)\] {
1794
1866
  --tw-shadow: inset 0 2px 0 0 var(--tw-shadow-color, var(--color-accent));
1795
1867
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -1810,16 +1882,39 @@
1810
1882
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1811
1883
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1812
1884
  }
1885
+ .wd-root .ring-1 {
1886
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1887
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1888
+ }
1813
1889
  .wd-root .ring-2 {
1814
1890
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1815
1891
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1816
1892
  }
1893
+ .wd-root .ring-accent {
1894
+ --tw-ring-color: var(--accent);
1895
+ }
1896
+ .wd-root .ring-danger\/60 {
1897
+ --tw-ring-color: var(--danger);
1898
+ @supports (color: color-mix(in lab, red, red)) {
1899
+ --tw-ring-color: color-mix(in oklab, var(--danger) 60%, transparent);
1900
+ }
1901
+ }
1902
+ .wd-root .ring-fg-3 {
1903
+ --tw-ring-color: var(--fg-3);
1904
+ }
1817
1905
  .wd-root .ring-ring\/30 {
1818
1906
  --tw-ring-color: var(--ring);
1819
1907
  @supports (color: color-mix(in lab, red, red)) {
1820
1908
  --tw-ring-color: color-mix(in oklab, var(--ring) 30%, transparent);
1821
1909
  }
1822
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
+ }
1823
1918
  .wd-root .outline {
1824
1919
  outline-style: var(--tw-outline-style);
1825
1920
  outline-width: 1px;
@@ -1828,6 +1923,10 @@
1828
1923
  --tw-blur: blur(8px);
1829
1924
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
1830
1925
  }
1926
+ .wd-root .grayscale {
1927
+ --tw-grayscale: grayscale(100%);
1928
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
1929
+ }
1831
1930
  .wd-root .backdrop-blur-\[1px\] {
1832
1931
  --tw-backdrop-blur: blur(1px);
1833
1932
  -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
@@ -1906,6 +2005,12 @@
1906
2005
  .wd-root .\[counter-reset\:line\] {
1907
2006
  counter-reset: line;
1908
2007
  }
2008
+ .wd-root .\[image-rendering\:pixelated\] {
2009
+ image-rendering: pixelated;
2010
+ }
2011
+ .wd-root .ring-inset {
2012
+ --tw-ring-inset: inset;
2013
+ }
1909
2014
  @media (hover: hover) {
1910
2015
  .wd-root .group-hover\:block:is(:where(.group):hover *) {
1911
2016
  display: block;
@@ -2074,6 +2179,13 @@
2074
2179
  .wd-root .hover\:opacity-100:hover {
2075
2180
  opacity: 100%;
2076
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
+ }
2077
2189
  }
2078
2190
  .wd-root .focus-visible\:border-ring:focus-visible {
2079
2191
  border-color: var(--ring);
@@ -3141,6 +3253,7 @@
3141
3253
  --row-active: rgb(0 0 0 / 0.08);
3142
3254
  --row-selected: #cfe4f7;
3143
3255
  --row-selected-weak: #e4e6f1;
3256
+ --row-attention: #fbdcb8;
3144
3257
  --badge: #c4c4c4;
3145
3258
  --badge-fg: #333333;
3146
3259
  --border: #ebebeb;
@@ -3194,6 +3307,7 @@
3194
3307
  --row-active: rgb(255 255 255 / 0.1);
3195
3308
  --row-selected: #04395e;
3196
3309
  --row-selected-weak: #37373d;
3310
+ --row-attention: #5c3906;
3197
3311
  --badge: #616161;
3198
3312
  --badge-fg: #cccccc;
3199
3313
  --border: #1f1f1f;
@@ -3424,6 +3538,23 @@
3424
3538
  opacity: 0;
3425
3539
  animation: wd-hold-appear 150ms ease-out 600ms forwards;
3426
3540
  }
3541
+ @keyframes wd-avatar-strip {
3542
+ to {
3543
+ background-position-x: var(--wd-strip-end);
3544
+ }
3545
+ }
3546
+ .wd-root .wd-avatar-strip {
3547
+ image-rendering: pixelated;
3548
+ background-repeat: no-repeat;
3549
+ animation-name: wd-avatar-strip;
3550
+ animation-timing-function: steps(var(--wd-strip-steps));
3551
+ animation-iteration-count: infinite;
3552
+ }
3553
+ @media (prefers-reduced-motion: reduce) {
3554
+ .wd-root .wd-avatar-strip {
3555
+ animation: none;
3556
+ }
3557
+ }
3427
3558
  @keyframes prompt-area-chip-ripple {
3428
3559
  0% {
3429
3560
  transform: scale(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workerdeck/ui",
3
- "version": "3.4.0",
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/client": "3.4.0",
43
- "@workerdeck/react": "3.4.0",
44
- "@workerdeck/protocol": "3.4.0"
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",
@@ -0,0 +1,117 @@
1
+ import type { CSSProperties } from 'react'
2
+ import type { AgentRef, SessionRow, SessionState } from '@workerdeck/protocol'
3
+ import { EngineIcon, vendorMarkClass } from './EngineIcon.tsx'
4
+ import { SessionStatusIcon } from './SessionStatusIcon.tsx'
5
+ import { cn } from '../../lib/utils.ts'
6
+
7
+ export type AgentAvatarImage = { still: string; busy?: { src: string; durations: number[] } }
8
+
9
+ // Keyed by `AgentRef.avatar`, the gateway path: the host fetches with its own credentials and hands over data URLs.
10
+ export type AgentAvatars = Record<string, AgentAvatarImage>
11
+
12
+ export function avatarOf(avatars: AgentAvatars | undefined, agent: AgentRef | undefined): AgentAvatarImage | undefined {
13
+ return agent?.avatar === undefined ? undefined : avatars?.[agent.avatar]
14
+ }
15
+
16
+ export interface AgentAvatarProps {
17
+ row: SessionRow
18
+ image?: AgentAvatarImage
19
+ size?: number
20
+ // The state the corner badge draws; a folded lead passes its team's most urgent one.
21
+ state?: SessionState
22
+ badge?: boolean
23
+ className?: string
24
+ }
25
+
26
+ export function AgentAvatar({ row, image, size = 32, state, badge = true, className }: AgentAvatarProps) {
27
+ const { info } = row
28
+ const shown = state ?? row.state
29
+ const busy = shown === 'working' && image?.busy && image.busy.durations.length > 1 ? image.busy : undefined
30
+ const asleep = info.engineAsleep === true
31
+ const engine = info.engine ?? 'claude'
32
+ const badgeSize = size >= 28 ? 14 : 11
33
+ return (
34
+ <span
35
+ data-slot="agent-avatar"
36
+ className={cn('relative inline-flex shrink-0', className)}
37
+ style={{ width: size, height: size }}
38
+ title={info.agent?.name}
39
+ >
40
+ {busy ? (
41
+ <span
42
+ aria-hidden
43
+ className="wd-avatar-strip size-full rounded-[4px]"
44
+ style={
45
+ {
46
+ backgroundImage: `url(${busy.src})`,
47
+ backgroundSize: `${busy.durations.length * size}px ${size}px`,
48
+ '--wd-strip-end': `-${busy.durations.length * size}px`,
49
+ '--wd-strip-steps': busy.durations.length,
50
+ animationDuration: `${busy.durations.reduce((sum, ms) => sum + ms, 0)}ms`,
51
+ } as CSSProperties
52
+ }
53
+ />
54
+ ) : image ? (
55
+ <img
56
+ src={image.still}
57
+ alt=""
58
+ draggable={false}
59
+ className={cn('size-full rounded-[4px] [image-rendering:pixelated]', asleep && 'opacity-60 grayscale')}
60
+ />
61
+ ) : (
62
+ <span className="flex size-full items-center justify-center rounded-[4px] border border-dashed border-border">
63
+ <EngineIcon engine={engine} model={info.model} className={cn('size-1/2', vendorMarkClass(engine, info.model))} />
64
+ </span>
65
+ )}
66
+ {badge && (shown !== 'idle' || asleep) ? (
67
+ <span
68
+ className="absolute -right-1 -bottom-1 flex items-center justify-center rounded-full bg-bg"
69
+ style={{ width: badgeSize, height: badgeSize }}
70
+ >
71
+ <SessionStatusIcon row={{ ...row, state: shown }} className="size-full p-px" />
72
+ </span>
73
+ ) : null}
74
+ </span>
75
+ )
76
+ }
77
+
78
+ export function LeadChip({ className }: { className?: string }) {
79
+ return (
80
+ <span
81
+ className={cn(
82
+ 'shrink-0 rounded-[3px] border border-accent/50 px-1 text-[0.625rem] leading-3.5 font-medium tracking-wide text-accent',
83
+ className,
84
+ )}
85
+ >
86
+ LEAD
87
+ </span>
88
+ )
89
+ }
90
+
91
+ export interface AgentHeadingProps {
92
+ row: SessionRow
93
+ image?: AgentAvatarImage
94
+ // Overrides `AgentRef.conversation`, which a gateway sends from the second conversation on.
95
+ conversation?: number
96
+ className?: string
97
+ }
98
+
99
+ // A session panel's title for an agent: avatar, name, its place in a team. Plain sessions keep the host's own title.
100
+ export function AgentHeading({ row, image, conversation, className }: AgentHeadingProps) {
101
+ const agent = row.info.agent
102
+ if (!agent) {
103
+ return null
104
+ }
105
+ const n = conversation ?? agent.conversation ?? 1
106
+ const detail = [agent.lead !== undefined && agent.team ? `${agent.team} team` : undefined, `conversation ${n}`]
107
+ .filter(Boolean)
108
+ .join(' · ')
109
+ return (
110
+ <span data-slot="agent-heading" className={cn('flex min-w-0 items-center gap-2', className)}>
111
+ <AgentAvatar row={row} image={image} size={24} />
112
+ <span className="truncate text-body-sm font-medium text-fg-1">{agent.name}</span>
113
+ {agent.leads ? <LeadChip /> : null}
114
+ {detail ? <span className="shrink-0 text-label text-fg-4">{detail}</span> : null}
115
+ </span>
116
+ )
117
+ }