@workerdeck/ui 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
  }
@@ -455,6 +464,12 @@
455
464
  .wd-root .ml-1 {
456
465
  margin-left: var(--spacing);
457
466
  }
467
+ .wd-root .ml-3 {
468
+ margin-left: calc(var(--spacing) * 3);
469
+ }
470
+ .wd-root .ml-3\.5 {
471
+ margin-left: calc(var(--spacing) * 3.5);
472
+ }
458
473
  .wd-root .ml-auto {
459
474
  margin-left: auto;
460
475
  }
@@ -501,6 +516,10 @@
501
516
  width: calc(var(--spacing) * 1.5);
502
517
  height: calc(var(--spacing) * 1.5);
503
518
  }
519
+ .wd-root .size-1\/2 {
520
+ width: calc(1 / 2 * 100%);
521
+ height: calc(1 / 2 * 100%);
522
+ }
504
523
  .wd-root .size-2 {
505
524
  width: calc(var(--spacing) * 2);
506
525
  height: calc(var(--spacing) * 2);
@@ -1003,6 +1022,9 @@
1003
1022
  .wd-root .rounded {
1004
1023
  border-radius: 0.25rem;
1005
1024
  }
1025
+ .wd-root .rounded-\[3px\] {
1026
+ border-radius: 3px;
1027
+ }
1006
1028
  .wd-root .rounded-\[4px\] {
1007
1029
  border-radius: 4px;
1008
1030
  }
@@ -1064,6 +1086,12 @@
1064
1086
  .wd-root .border-\[var\(--wd-shell-accent\)\] {
1065
1087
  border-color: var(--wd-shell-accent);
1066
1088
  }
1089
+ .wd-root .border-accent\/50 {
1090
+ border-color: var(--accent);
1091
+ @supports (color: color-mix(in lab, red, red)) {
1092
+ border-color: color-mix(in oklab, var(--accent) 50%, transparent);
1093
+ }
1094
+ }
1067
1095
  .wd-root .border-border {
1068
1096
  border-color: var(--border);
1069
1097
  }
@@ -1139,6 +1167,12 @@
1139
1167
  .wd-root .bg-accent-bg {
1140
1168
  background-color: var(--accent-bg);
1141
1169
  }
1170
+ .wd-root .bg-accent\/10 {
1171
+ background-color: var(--accent);
1172
+ @supports (color: color-mix(in lab, red, red)) {
1173
+ background-color: color-mix(in oklab, var(--accent) 10%, transparent);
1174
+ }
1175
+ }
1142
1176
  .wd-root .bg-background {
1143
1177
  background-color: var(--background);
1144
1178
  }
@@ -1352,6 +1386,9 @@
1352
1386
  .wd-root .p-px {
1353
1387
  padding: 1px;
1354
1388
  }
1389
+ .wd-root .px-0\.5 {
1390
+ padding-inline: calc(var(--spacing) * 0.5);
1391
+ }
1355
1392
  .wd-root .px-1 {
1356
1393
  padding-inline: var(--spacing);
1357
1394
  }
@@ -1475,6 +1512,9 @@
1475
1512
  .wd-root .pb-\[var\(--wd-composer-padding\)\] {
1476
1513
  padding-bottom: var(--wd-composer-padding);
1477
1514
  }
1515
+ .wd-root .pl-1 {
1516
+ padding-left: var(--spacing);
1517
+ }
1478
1518
  .wd-root .pl-1\.5 {
1479
1519
  padding-left: calc(var(--spacing) * 1.5);
1480
1520
  }
@@ -1571,9 +1611,16 @@
1571
1611
  .wd-root .text-\[0\.75rem\] {
1572
1612
  font-size: 0.75rem;
1573
1613
  }
1614
+ .wd-root .text-\[0\.625rem\] {
1615
+ font-size: 0.625rem;
1616
+ }
1574
1617
  .wd-root .text-\[9px\] {
1575
1618
  font-size: 9px;
1576
1619
  }
1620
+ .wd-root .leading-3\.5 {
1621
+ --tw-leading: calc(var(--spacing) * 3.5);
1622
+ line-height: calc(var(--spacing) * 3.5);
1623
+ }
1577
1624
  .wd-root .leading-4 {
1578
1625
  --tw-leading: calc(var(--spacing) * 4);
1579
1626
  line-height: calc(var(--spacing) * 4);
@@ -1790,6 +1837,10 @@
1790
1837
  --tw-shadow: var(--shadow-xs);
1791
1838
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1792
1839
  }
1840
+ .wd-root .shadow-\[inset_0_-2px_0_0_var\(--color-accent\)\] {
1841
+ --tw-shadow: inset 0 -2px 0 0 var(--tw-shadow-color, var(--color-accent));
1842
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1843
+ }
1793
1844
  .wd-root .shadow-\[inset_0_2px_0_0_var\(--color-accent\)\] {
1794
1845
  --tw-shadow: inset 0 2px 0 0 var(--tw-shadow-color, var(--color-accent));
1795
1846
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -1810,10 +1861,23 @@
1810
1861
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1811
1862
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1812
1863
  }
1864
+ .wd-root .ring-1 {
1865
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1866
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1867
+ }
1813
1868
  .wd-root .ring-2 {
1814
1869
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1815
1870
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1816
1871
  }
1872
+ .wd-root .ring-accent {
1873
+ --tw-ring-color: var(--accent);
1874
+ }
1875
+ .wd-root .ring-danger\/60 {
1876
+ --tw-ring-color: var(--danger);
1877
+ @supports (color: color-mix(in lab, red, red)) {
1878
+ --tw-ring-color: color-mix(in oklab, var(--danger) 60%, transparent);
1879
+ }
1880
+ }
1817
1881
  .wd-root .ring-ring\/30 {
1818
1882
  --tw-ring-color: var(--ring);
1819
1883
  @supports (color: color-mix(in lab, red, red)) {
@@ -1828,6 +1892,10 @@
1828
1892
  --tw-blur: blur(8px);
1829
1893
  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
1894
  }
1895
+ .wd-root .grayscale {
1896
+ --tw-grayscale: grayscale(100%);
1897
+ 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,);
1898
+ }
1831
1899
  .wd-root .backdrop-blur-\[1px\] {
1832
1900
  --tw-backdrop-blur: blur(1px);
1833
1901
  -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 +1974,9 @@
1906
1974
  .wd-root .\[counter-reset\:line\] {
1907
1975
  counter-reset: line;
1908
1976
  }
1977
+ .wd-root .\[image-rendering\:pixelated\] {
1978
+ image-rendering: pixelated;
1979
+ }
1909
1980
  @media (hover: hover) {
1910
1981
  .wd-root .group-hover\:block:is(:where(.group):hover *) {
1911
1982
  display: block;
@@ -3424,6 +3495,23 @@
3424
3495
  opacity: 0;
3425
3496
  animation: wd-hold-appear 150ms ease-out 600ms forwards;
3426
3497
  }
3498
+ @keyframes wd-avatar-strip {
3499
+ to {
3500
+ background-position-x: var(--wd-strip-end);
3501
+ }
3502
+ }
3503
+ .wd-root .wd-avatar-strip {
3504
+ image-rendering: pixelated;
3505
+ background-repeat: no-repeat;
3506
+ animation-name: wd-avatar-strip;
3507
+ animation-timing-function: steps(var(--wd-strip-steps));
3508
+ animation-iteration-count: infinite;
3509
+ }
3510
+ @media (prefers-reduced-motion: reduce) {
3511
+ .wd-root .wd-avatar-strip {
3512
+ animation: none;
3513
+ }
3514
+ }
3427
3515
  @keyframes prompt-area-chip-ripple {
3428
3516
  0% {
3429
3517
  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.5.0",
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/protocol": "3.5.0",
43
+ "@workerdeck/client": "3.5.0",
44
+ "@workerdeck/react": "3.5.0"
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
+ }