@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/index.d.mts +55 -4
- package/build/index.mjs +702 -163
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +131 -0
- package/package.json +4 -4
- package/src/components/agent/AgentAvatar.tsx +117 -0
- package/src/components/agent/SessionBrowser.tsx +246 -43
- package/src/components/agent/SessionGroups.tsx +256 -23
- package/src/components/agent/SessionItem.tsx +200 -68
- package/src/index.ts +11 -0
- package/src/lib/team-drop.ts +65 -0
- package/src/styles/theme.css +22 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Fragment, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { Users } from 'lucide-react'
|
|
2
3
|
import type { ReactNode } from 'react'
|
|
3
4
|
import { projectLabel, projectName, projectSubpath, sessionLabel } from '@workerdeck/protocol'
|
|
4
|
-
import type { SessionRow, SessionTask, StepDisplay, SubagentDisplay } from '@workerdeck/protocol'
|
|
5
|
+
import type { SessionInfo, SessionRow, SessionState, SessionTask, StepDisplay, SubagentDisplay } from '@workerdeck/protocol'
|
|
6
|
+
import { AgentAvatar, LeadChip, avatarOf, type AgentAvatars } from './AgentAvatar.tsx'
|
|
5
7
|
import { ContextRing } from './ContextRing.tsx'
|
|
6
8
|
import { EngineIcon, vendorMarkClass, vendorTextClass } from './EngineIcon.tsx'
|
|
7
9
|
import { ProjectIcon } from './ProjectIcon.tsx'
|
|
@@ -36,6 +38,19 @@ export interface SessionItemProps {
|
|
|
36
38
|
editing?: boolean
|
|
37
39
|
onEditingChange?: (editing: boolean) => void
|
|
38
40
|
actions?: ReactNode
|
|
41
|
+
// Drawn when `row.info.agent` is set: the card then leads with the avatar and names the agent.
|
|
42
|
+
avatars?: AgentAvatars
|
|
43
|
+
// `member` is the one-row card a team draws under its lead.
|
|
44
|
+
variant?: 'card' | 'member'
|
|
45
|
+
// The state the avatar badge draws, for a folded lead standing in for its team.
|
|
46
|
+
badgeState?: SessionState
|
|
47
|
+
// Unread summed over a folded team's members, drawn with the team glyph beside the lead's own.
|
|
48
|
+
teamUnseen?: number
|
|
49
|
+
// A folded lead's members, drawn on its first line before the context ring.
|
|
50
|
+
teamTray?: ReactNode
|
|
51
|
+
// Draws a session with no agent in the agent card's shape (an engine-mark tile for the avatar), so a list mixing
|
|
52
|
+
// agents and plain sessions keeps one column.
|
|
53
|
+
tile?: boolean
|
|
39
54
|
className?: string
|
|
40
55
|
}
|
|
41
56
|
|
|
@@ -64,6 +79,12 @@ export function SessionItem({
|
|
|
64
79
|
editing,
|
|
65
80
|
onEditingChange,
|
|
66
81
|
actions,
|
|
82
|
+
avatars,
|
|
83
|
+
variant = 'card',
|
|
84
|
+
badgeState,
|
|
85
|
+
teamUnseen = 0,
|
|
86
|
+
teamTray,
|
|
87
|
+
tile = false,
|
|
67
88
|
className,
|
|
68
89
|
}: SessionItemProps) {
|
|
69
90
|
const { info } = row
|
|
@@ -87,6 +108,9 @@ export function SessionItem({
|
|
|
87
108
|
].filter((part): part is string => Boolean(part))
|
|
88
109
|
|
|
89
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'
|
|
90
114
|
const parts: ReactNode[] = []
|
|
91
115
|
if (model) {
|
|
92
116
|
parts.push(
|
|
@@ -95,6 +119,13 @@ export function SessionItem({
|
|
|
95
119
|
</span>,
|
|
96
120
|
)
|
|
97
121
|
}
|
|
122
|
+
if (status) {
|
|
123
|
+
parts.push(
|
|
124
|
+
<span key="status" title={status} className="text-fg-3">
|
|
125
|
+
{status}
|
|
126
|
+
</span>,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
98
129
|
if (project !== undefined) {
|
|
99
130
|
parts.push(
|
|
100
131
|
<span key="project" title={projectTitle}>
|
|
@@ -125,6 +156,54 @@ export function SessionItem({
|
|
|
125
156
|
},
|
|
126
157
|
)
|
|
127
158
|
const holdsOpenStep = steps.some((s) => s.key === activeStepKey)
|
|
159
|
+
const agent = info.agent
|
|
160
|
+
const label = agent ? agent.name : sessionLabel(info)
|
|
161
|
+
const editable = agent ? agent.name : (info.title ?? '')
|
|
162
|
+
const time = formatRelativeTime(info.lastActivityAt ?? info.createdAt)
|
|
163
|
+
const nameLabel =
|
|
164
|
+
isEditing && onRename ? (
|
|
165
|
+
<NameEditor
|
|
166
|
+
initial={editable}
|
|
167
|
+
placeholder={agent ? 'Agent name' : 'Session name'}
|
|
168
|
+
onCommit={(title) => {
|
|
169
|
+
setEditing(false)
|
|
170
|
+
if (title !== editable && (title || !agent)) {
|
|
171
|
+
onRename(title)
|
|
172
|
+
}
|
|
173
|
+
}}
|
|
174
|
+
onCancel={() => setEditing(false)}
|
|
175
|
+
/>
|
|
176
|
+
) : (
|
|
177
|
+
<span
|
|
178
|
+
onDoubleClick={
|
|
179
|
+
onRename && renameOn === 'doubleClick'
|
|
180
|
+
? (e) => {
|
|
181
|
+
e.stopPropagation()
|
|
182
|
+
setEditing(true)
|
|
183
|
+
}
|
|
184
|
+
: undefined
|
|
185
|
+
}
|
|
186
|
+
className="min-w-0 shrink truncate text-body-sm font-medium tracking-[-0.005em] text-fg-1"
|
|
187
|
+
>
|
|
188
|
+
{label}
|
|
189
|
+
</span>
|
|
190
|
+
)
|
|
191
|
+
const unread =
|
|
192
|
+
row.unseen > 0 ? (
|
|
193
|
+
<span
|
|
194
|
+
// Prose, not rows: `unseen` counts messages a person has not read (`proseCount`).
|
|
195
|
+
title={`${row.unseen} new message${row.unseen === 1 ? '' : 's'}`}
|
|
196
|
+
className={cn(
|
|
197
|
+
'flex h-4 min-w-6 shrink-0 items-center justify-center rounded-full px-2',
|
|
198
|
+
'text-[0.75rem] leading-none tracking-[-0.005em] tabular-nums',
|
|
199
|
+
// Grey while the turn is still producing, accent once it has stopped: the badge's colour
|
|
200
|
+
// answers "is this waiting for me", and a working session is not yet.
|
|
201
|
+
row.state === 'working' ? 'bg-badge text-badge-fg' : 'bg-accent text-accent-fg',
|
|
202
|
+
)}
|
|
203
|
+
>
|
|
204
|
+
{row.unseen}
|
|
205
|
+
</span>
|
|
206
|
+
) : null
|
|
128
207
|
|
|
129
208
|
return (
|
|
130
209
|
<div
|
|
@@ -152,79 +231,118 @@ export function SessionItem({
|
|
|
152
231
|
className={cn(
|
|
153
232
|
'group flex w-full cursor-pointer flex-col p-1 text-left outline-none',
|
|
154
233
|
'rounded-[4px] transition-colors focus-visible:ring-2 focus-visible:ring-ring',
|
|
155
|
-
|
|
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',
|
|
156
241
|
className,
|
|
157
242
|
)}
|
|
158
243
|
>
|
|
159
|
-
|
|
160
|
-
<div className=
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
initial={info.title ?? ''}
|
|
167
|
-
onCommit={(title) => {
|
|
168
|
-
setEditing(false)
|
|
169
|
-
if (title !== (info.title ?? '')) {
|
|
170
|
-
onRename(title)
|
|
171
|
-
}
|
|
172
|
-
}}
|
|
173
|
-
onCancel={() => setEditing(false)}
|
|
174
|
-
/>
|
|
175
|
-
) : (
|
|
176
|
-
<span
|
|
177
|
-
onDoubleClick={
|
|
178
|
-
onRename && renameOn === 'doubleClick'
|
|
179
|
-
? (e) => {
|
|
180
|
-
e.stopPropagation()
|
|
181
|
-
setEditing(true)
|
|
182
|
-
}
|
|
183
|
-
: undefined
|
|
184
|
-
}
|
|
185
|
-
className="min-w-0 flex-1 truncate text-body-sm font-medium tracking-[-0.005em] text-fg-1"
|
|
186
|
-
>
|
|
187
|
-
{sessionLabel(info)}
|
|
244
|
+
{agent && variant === 'member' ? (
|
|
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')}>
|
|
246
|
+
<AgentAvatar row={row} image={avatarOf(avatars, agent)} size={22} />
|
|
247
|
+
{nameLabel}
|
|
248
|
+
{status ? (
|
|
249
|
+
<span title={status} className="min-w-0 flex-1 truncate text-body-sm text-fg-3">
|
|
250
|
+
{status}
|
|
188
251
|
</span>
|
|
252
|
+
) : (
|
|
253
|
+
<span className="flex-1" />
|
|
189
254
|
)}
|
|
190
|
-
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
255
|
+
{unread}
|
|
256
|
+
<span className="shrink-0 text-body-sm text-fg-4">{time}</span>
|
|
257
|
+
{actions}
|
|
258
|
+
</div>
|
|
259
|
+
) : agent || tile ? (
|
|
260
|
+
<div className={cn('flex items-center gap-2 py-0.5 pr-0.5 pl-1.5', info.engineAsleep && 'opacity-60')}>
|
|
261
|
+
<AgentAvatar row={row} image={avatarOf(avatars, agent)} state={badgeState} />
|
|
262
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
263
|
+
<div className="flex h-5 items-center gap-1.5 overflow-hidden">
|
|
264
|
+
{nameLabel}
|
|
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}
|
|
267
|
+
<span className="flex-1" />
|
|
268
|
+
{unread}
|
|
269
|
+
{teamUnseen > 0 ? (
|
|
270
|
+
<span
|
|
271
|
+
title={`${teamUnseen} new message${teamUnseen === 1 ? '' : 's'} in the team`}
|
|
272
|
+
className="flex h-4 shrink-0 items-center gap-0.5 rounded-full bg-accent px-1.5 text-[0.75rem] leading-none text-accent-fg tabular-nums"
|
|
273
|
+
>
|
|
274
|
+
<Users className="size-3" />
|
|
275
|
+
{teamUnseen}
|
|
276
|
+
</span>
|
|
277
|
+
) : null}
|
|
278
|
+
{teamTray}
|
|
279
|
+
<ContextRing usage={info.contextUsage} engine={engine} size={16} className="p-0.5" />
|
|
280
|
+
</div>
|
|
281
|
+
<div className="flex h-5 items-center gap-1.5 overflow-hidden text-body-sm tracking-[-0.005em]">
|
|
282
|
+
{agent ? (
|
|
283
|
+
<>
|
|
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}
|
|
291
|
+
</>
|
|
292
|
+
) : (
|
|
293
|
+
<>
|
|
294
|
+
<span className="min-w-0 truncate text-fg-4">
|
|
295
|
+
{parts.map((part, i) => (
|
|
296
|
+
<Fragment key={i}>
|
|
297
|
+
{i > 0 ? ' · ' : ''}
|
|
298
|
+
{part}
|
|
299
|
+
</Fragment>
|
|
300
|
+
))}
|
|
301
|
+
</span>
|
|
302
|
+
<span className="shrink-0 text-fg-4">
|
|
303
|
+
{parts.length > 0 ? '· ' : ''}
|
|
304
|
+
{time}
|
|
305
|
+
</span>
|
|
306
|
+
</>
|
|
200
307
|
)}
|
|
201
|
-
|
|
202
|
-
{
|
|
203
|
-
</
|
|
204
|
-
|
|
205
|
-
<ContextRing usage={info.contextUsage} engine={engine} size={16} className="p-0.5" />
|
|
308
|
+
<span className="min-w-0 flex-1" />
|
|
309
|
+
{actions}
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
206
312
|
</div>
|
|
313
|
+
) : (
|
|
314
|
+
<div className={cn('flex flex-col gap-1 py-0.5 pr-0.5 pl-1.5', info.engineAsleep && 'opacity-60')}>
|
|
315
|
+
<div className="flex h-5 items-center gap-1.5 overflow-hidden">
|
|
316
|
+
<Gutter>
|
|
317
|
+
<SessionStatusIcon row={row} />
|
|
318
|
+
</Gutter>
|
|
319
|
+
{nameLabel}
|
|
320
|
+
<span className="flex-1" />
|
|
321
|
+
{unread}
|
|
322
|
+
<ContextRing usage={info.contextUsage} engine={engine} size={16} className="p-0.5" />
|
|
323
|
+
</div>
|
|
207
324
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
325
|
+
<div className="flex h-5 items-center gap-1.5 overflow-hidden text-body-sm tracking-[-0.005em]">
|
|
326
|
+
<Gutter>
|
|
327
|
+
<EngineIcon engine={engine} model={info.model} className={cn('size-4', vendorMarkClass(engine, info.model))} />
|
|
328
|
+
</Gutter>
|
|
329
|
+
<span className="min-w-0 truncate text-fg-4">
|
|
330
|
+
{parts.map((part, i) => (
|
|
331
|
+
<Fragment key={i}>
|
|
332
|
+
{i > 0 ? ' · ' : ''}
|
|
333
|
+
{part}
|
|
334
|
+
</Fragment>
|
|
335
|
+
))}
|
|
336
|
+
</span>
|
|
337
|
+
<span className="shrink-0 text-fg-4">
|
|
338
|
+
{parts.length > 0 ? '· ' : ''}
|
|
339
|
+
{time}
|
|
340
|
+
</span>
|
|
341
|
+
<span className="min-w-0 flex-1" />
|
|
342
|
+
{actions}
|
|
343
|
+
</div>
|
|
226
344
|
</div>
|
|
227
|
-
|
|
345
|
+
)}
|
|
228
346
|
|
|
229
347
|
{steps.length > 0 ? (
|
|
230
348
|
<div className="flex flex-col">
|
|
@@ -241,7 +359,17 @@ function Gutter({ children }: { children: ReactNode }) {
|
|
|
241
359
|
return <span className="flex size-4 shrink-0 items-center justify-center">{children}</span>
|
|
242
360
|
}
|
|
243
361
|
|
|
244
|
-
function NameEditor({
|
|
362
|
+
function NameEditor({
|
|
363
|
+
initial,
|
|
364
|
+
placeholder,
|
|
365
|
+
onCommit,
|
|
366
|
+
onCancel,
|
|
367
|
+
}: {
|
|
368
|
+
initial: string
|
|
369
|
+
placeholder: string
|
|
370
|
+
onCommit: (title: string) => void
|
|
371
|
+
onCancel: () => void
|
|
372
|
+
}) {
|
|
245
373
|
const [value, setValue] = useState(initial)
|
|
246
374
|
const ref = useRef<HTMLInputElement>(null)
|
|
247
375
|
useEffect(() => {
|
|
@@ -261,8 +389,8 @@ function NameEditor({ initial, onCommit, onCancel }: { initial: string; onCommit
|
|
|
261
389
|
ref={ref}
|
|
262
390
|
value={value}
|
|
263
391
|
spellCheck={false}
|
|
264
|
-
placeholder=
|
|
265
|
-
aria-label=
|
|
392
|
+
placeholder={placeholder}
|
|
393
|
+
aria-label={placeholder}
|
|
266
394
|
onClick={(e) => e.stopPropagation()}
|
|
267
395
|
onDoubleClick={(e) => e.stopPropagation()}
|
|
268
396
|
onChange={(e) => setValue(e.target.value)}
|
|
@@ -287,3 +415,7 @@ function NameEditor({ initial, onCommit, onCancel }: { initial: string; onCommit
|
|
|
287
415
|
/>
|
|
288
416
|
)
|
|
289
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/index.ts
CHANGED
|
@@ -164,6 +164,7 @@ export {
|
|
|
164
164
|
type GroupTarget,
|
|
165
165
|
type SessionBrowserProps,
|
|
166
166
|
} from './components/agent/SessionBrowser.tsx'
|
|
167
|
+
export { teamDropRefusal, type TeamMove } from './lib/team-drop.ts'
|
|
167
168
|
export {
|
|
168
169
|
STEP_DISPLAY_OPTIONS,
|
|
169
170
|
SessionFilters,
|
|
@@ -173,6 +174,16 @@ export {
|
|
|
173
174
|
type SessionFiltersProps,
|
|
174
175
|
} from './components/agent/SessionFilters.tsx'
|
|
175
176
|
export { SessionItem, type SelectModifiers, type SessionItemProps } from './components/agent/SessionItem.tsx'
|
|
177
|
+
export {
|
|
178
|
+
AgentAvatar,
|
|
179
|
+
AgentHeading,
|
|
180
|
+
LeadChip,
|
|
181
|
+
avatarOf,
|
|
182
|
+
type AgentAvatarImage,
|
|
183
|
+
type AgentAvatarProps,
|
|
184
|
+
type AgentAvatars,
|
|
185
|
+
type AgentHeadingProps,
|
|
186
|
+
} from './components/agent/AgentAvatar.tsx'
|
|
176
187
|
export { SessionStatusIcon } from './components/agent/SessionStatusIcon.tsx'
|
|
177
188
|
export { EngineIcon, engineMark, vendorMarkClass, vendorTextClass } from './components/agent/EngineIcon.tsx'
|
|
178
189
|
export {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { SessionRow } from '@workerdeck/protocol'
|
|
2
|
+
|
|
3
|
+
export type DropZone = 'before' | 'join' | 'after'
|
|
4
|
+
|
|
5
|
+
// What a drop asks of the host: `lead: null` leaves the team. Joining renumbers the team densely from 0, so
|
|
6
|
+
// `siblings` lists the other members whose `order` changes; teams are small and most of them never moved.
|
|
7
|
+
export type TeamMove = { row: SessionRow; lead: SessionRow | null; order?: number; siblings?: { row: SessionRow; order: number }[] }
|
|
8
|
+
|
|
9
|
+
export function dropZone(offsetY: number, height: number): DropZone {
|
|
10
|
+
if (height <= 0) {
|
|
11
|
+
return 'join'
|
|
12
|
+
}
|
|
13
|
+
const at = offsetY / height
|
|
14
|
+
return at < 0.25 ? 'before' : at > 0.75 ? 'after' : 'join'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Mirrors the gateway's `leadRefusal`, so a drag can say no before it asks; the 409 stays the authority.
|
|
18
|
+
export function teamDropRefusal(dragged: SessionRow, lead: SessionRow): string | undefined {
|
|
19
|
+
const mover = dragged.info.agent
|
|
20
|
+
const target = lead.info.agent
|
|
21
|
+
if (dragged.hostId !== lead.hostId) {
|
|
22
|
+
return 'a team stays on one gateway'
|
|
23
|
+
}
|
|
24
|
+
if (!target) {
|
|
25
|
+
return 'only an agent can lead a team'
|
|
26
|
+
}
|
|
27
|
+
if (mover?.id === target.id || dragged.info.id === lead.info.id) {
|
|
28
|
+
return 'an agent cannot lead its own team'
|
|
29
|
+
}
|
|
30
|
+
if (target.lead !== undefined) {
|
|
31
|
+
return `${target.name} is a member of a team; teams are one level deep`
|
|
32
|
+
}
|
|
33
|
+
if (mover?.leads) {
|
|
34
|
+
return `${mover.name} leads a team; teams are one level deep`
|
|
35
|
+
}
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The move a drop over a member asks for: join (or reorder within) that member's team, at the member's place.
|
|
40
|
+
export function memberDrop(dragged: SessionRow, lead: SessionRow, member: SessionRow, zone: DropZone): TeamMove | string {
|
|
41
|
+
const refusal = teamDropRefusal(dragged, lead)
|
|
42
|
+
if (refusal) {
|
|
43
|
+
return refusal
|
|
44
|
+
}
|
|
45
|
+
const others = (lead.members ?? []).filter((row) => row.info.id !== dragged.info.id || row.hostId !== dragged.hostId)
|
|
46
|
+
const at = others.findIndex((row) => row.info.id === member.info.id && row.hostId === member.hostId)
|
|
47
|
+
const index = at < 0 ? others.length : zone === 'before' ? at : at + 1
|
|
48
|
+
return placeMember(dragged, lead, others, index)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function joinDrop(dragged: SessionRow, lead: SessionRow): TeamMove | string {
|
|
52
|
+
const refusal = teamDropRefusal(dragged, lead)
|
|
53
|
+
if (refusal) {
|
|
54
|
+
return refusal
|
|
55
|
+
}
|
|
56
|
+
const others = (lead.members ?? []).filter((row) => row.info.id !== dragged.info.id || row.hostId !== dragged.hostId)
|
|
57
|
+
return placeMember(dragged, lead, others, others.length)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function placeMember(dragged: SessionRow, lead: SessionRow, others: readonly SessionRow[], index: number): TeamMove {
|
|
61
|
+
const siblings = others
|
|
62
|
+
.map((row, i) => ({ row, order: i < index ? i : i + 1 }))
|
|
63
|
+
.filter(({ row, order }) => row.info.agent?.order !== order)
|
|
64
|
+
return { row: dragged, lead, order: index, ...(siblings.length ? { siblings } : {}) }
|
|
65
|
+
}
|
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:
|
|
@@ -666,6 +671,23 @@
|
|
|
666
671
|
opacity: 0;
|
|
667
672
|
animation: wd-hold-appear 150ms ease-out 600ms forwards;
|
|
668
673
|
}
|
|
674
|
+
@keyframes wd-avatar-strip {
|
|
675
|
+
to {
|
|
676
|
+
background-position-x: var(--wd-strip-end);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
.wd-avatar-strip {
|
|
680
|
+
image-rendering: pixelated;
|
|
681
|
+
background-repeat: no-repeat;
|
|
682
|
+
animation-name: wd-avatar-strip;
|
|
683
|
+
animation-timing-function: steps(var(--wd-strip-steps));
|
|
684
|
+
animation-iteration-count: infinite;
|
|
685
|
+
}
|
|
686
|
+
@media (prefers-reduced-motion: reduce) {
|
|
687
|
+
.wd-avatar-strip {
|
|
688
|
+
animation: none;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
669
691
|
|
|
670
692
|
@keyframes prompt-area-chip-ripple {
|
|
671
693
|
0% {
|