@workerdeck/ui 0.22.0 → 1.0.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/{SessionPanel-DgwjH4Ve.mjs → SessionPanel-DDgBkHsi.mjs} +2196 -3364
- package/build/SessionPanel-DDgBkHsi.mjs.map +1 -0
- package/build/SessionPanel-E_L_ldQ5.d.mts +207 -0
- package/build/format-B8ZM9LPy.d.mts +16 -0
- package/build/format.d.mts +8 -75
- package/build/format.mjs +2 -2
- package/build/index.d.mts +63 -1138
- package/build/index.mjs +4 -300
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +80 -24
- package/build/{status-BE-zg88x.mjs → status-C0HSLZas.mjs} +14 -85
- package/build/status-C0HSLZas.mjs.map +1 -0
- package/build/workspace.d.mts +9 -124
- package/build/workspace.mjs +13 -143
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/CodeEditor.tsx +31 -103
- package/src/components/agent/Composer.tsx +114 -333
- package/src/components/agent/ContextDialog.tsx +20 -41
- package/src/components/agent/ContextRing.tsx +6 -28
- package/src/components/agent/Conversation.tsx +21 -33
- package/src/components/agent/EditorTabs.tsx +39 -67
- package/src/components/agent/EngineIcon.tsx +29 -72
- package/src/components/agent/EntryIcon.tsx +16 -0
- package/src/components/agent/FileCard.tsx +10 -16
- package/src/components/agent/FileTree.tsx +52 -125
- package/src/components/agent/FileViewer.tsx +30 -60
- package/src/components/agent/HostFilesDialog.tsx +40 -69
- package/src/components/agent/Loader.tsx +3 -24
- package/src/components/agent/McpDialog.tsx +64 -137
- package/src/components/agent/Message.tsx +2 -21
- package/src/components/agent/ModelSelect.tsx +33 -62
- package/src/components/agent/PermissionModeSelect.tsx +21 -79
- package/src/components/agent/PermissionPrompt.tsx +59 -63
- package/src/components/agent/ProjectIcon.tsx +228 -93
- package/src/components/agent/PromptTokenText.tsx +10 -16
- package/src/components/agent/QuestionPrompt.tsx +43 -64
- package/src/components/agent/Reasoning.tsx +13 -16
- package/src/components/agent/Response.tsx +4 -6
- package/src/components/agent/Scrubber.tsx +31 -80
- package/src/components/agent/SessionBrowser.tsx +64 -243
- package/src/components/agent/SessionEmptyState.tsx +10 -40
- package/src/components/agent/SessionInfoDialog.tsx +46 -78
- package/src/components/agent/SessionItem.tsx +53 -320
- package/src/components/agent/SessionList.tsx +23 -39
- package/src/components/agent/SessionPanel.tsx +345 -923
- package/src/components/agent/SessionStatusIcon.tsx +14 -28
- package/src/components/agent/SessionSteps.tsx +34 -187
- package/src/components/agent/SessionWorkspace.tsx +108 -215
- package/src/components/agent/SkillsDialog.tsx +35 -80
- package/src/components/agent/StatusBar.tsx +51 -136
- package/src/components/agent/SubagentStrip.tsx +12 -77
- package/src/components/agent/ToolCallCard.tsx +94 -135
- package/src/components/agent/Transcript.tsx +103 -1288
- package/src/components/agent/TranscriptItemView.tsx +155 -0
- package/src/components/agent/TranscriptRows.tsx +418 -0
- package/src/components/agent/UsageDialog.tsx +7 -29
- package/src/components/agent/UsageMeters.tsx +18 -73
- package/src/components/agent/pulse.tsx +9 -32
- package/src/components/agent/scrubber-marks.ts +33 -103
- package/src/components/agent/status.ts +1 -5
- package/src/components/agent/tool-result-fetch.tsx +3 -25
- package/src/components/agent/tool-result-image.tsx +32 -111
- package/src/components/agent/transcript-rows.ts +41 -118
- package/src/components/agent/transcript-variant.tsx +3 -80
- package/src/components/agent/use-height-epoch.ts +10 -24
- package/src/components/agent/use-path-links.ts +45 -63
- package/src/components/agent/use-subagent-frame.ts +106 -0
- package/src/components/agent/use-transcript-jumps.ts +49 -107
- package/src/components/prompt-area/animated-placeholder.tsx +6 -5
- package/src/components/prompt-area/clipboard-helpers.ts +26 -21
- package/src/components/prompt-area/cursor-helpers.ts +79 -37
- package/src/components/prompt-area/dom-helpers.ts +97 -69
- package/src/components/prompt-area/file-strip.tsx +90 -80
- package/src/components/prompt-area/html-to-markdown.ts +67 -46
- package/src/components/prompt-area/image-strip.tsx +8 -18
- package/src/components/prompt-area/index.ts +2 -15
- package/src/components/prompt-area/prompt-area-engine.ts +100 -115
- package/src/components/prompt-area/prompt-area-list-ops.ts +66 -59
- package/src/components/prompt-area/prompt-area.tsx +31 -38
- package/src/components/prompt-area/remove-button.tsx +3 -9
- package/src/components/prompt-area/segment-helpers.ts +4 -12
- package/src/components/prompt-area/trigger-popover.tsx +15 -27
- package/src/components/prompt-area/trigger-presets.ts +0 -10
- package/src/components/prompt-area/types.ts +3 -8
- package/src/components/prompt-area/use-chip-editing.ts +305 -0
- package/src/components/prompt-area/use-markdown-mode.ts +7 -17
- package/src/components/prompt-area/use-prompt-area-events.ts +80 -86
- package/src/components/prompt-area/use-prompt-area-keydown.ts +598 -0
- package/src/components/prompt-area/use-prompt-area-state.ts +5 -23
- package/src/components/prompt-area/use-prompt-area.ts +170 -896
- package/src/components/prompt-area/use-trigger-search.ts +21 -19
- package/src/components/terminal/PermissionPrompt.tsx +56 -59
- package/src/components/terminal/QuestionPrompt.tsx +28 -79
- package/src/components/terminal/StatusLine.tsx +13 -63
- package/src/components/terminal/TerminalTranscript.tsx +44 -160
- package/src/components/terminal/affordances.tsx +68 -98
- package/src/components/terminal/blocks.ts +20 -156
- package/src/components/terminal/diff.tsx +19 -67
- package/src/components/terminal/height.ts +163 -313
- package/src/components/terminal/image-box.ts +0 -44
- package/src/components/terminal/items.tsx +150 -305
- package/src/components/terminal/markdown.tsx +36 -95
- package/src/components/terminal/press.tsx +26 -53
- package/src/components/terminal/prompt.tsx +40 -170
- package/src/components/terminal/result-preview.ts +8 -56
- package/src/components/terminal/row.tsx +11 -98
- package/src/components/terminal/scrubber.tsx +108 -401
- package/src/components/terminal/surface.tsx +6 -54
- package/src/components/terminal/todos.ts +66 -0
- package/src/components/terminal/tool-run.ts +21 -192
- package/src/components/ui/AlertDialog.tsx +8 -23
- package/src/components/ui/Badge.tsx +18 -29
- package/src/components/ui/Button.tsx +7 -15
- package/src/components/ui/Card.tsx +2 -13
- package/src/components/ui/CodeBlock.tsx +10 -37
- package/src/components/ui/CopyButton.tsx +10 -23
- package/src/components/ui/Dialog.tsx +23 -36
- package/src/components/ui/Empty.tsx +6 -29
- package/src/components/ui/Input.tsx +14 -16
- package/src/components/ui/Menu.tsx +6 -18
- package/src/components/ui/PortalScope.tsx +1 -21
- package/src/components/ui/ProgressRing.tsx +8 -19
- package/src/components/ui/Select.tsx +18 -31
- package/src/components/ui/Sonner.tsx +1 -2
- package/src/components/ui/Spinner.tsx +1 -1
- package/src/components/ui/Splitter.tsx +30 -64
- package/src/components/ui/Textarea.tsx +2 -5
- package/src/components/ui/Tooltip.tsx +3 -13
- package/src/format.ts +0 -9
- package/src/index.ts +16 -113
- package/src/lib/clipboard.ts +8 -28
- package/src/lib/css.ts +8 -0
- package/src/lib/format.ts +101 -86
- package/src/lib/plan-request.ts +16 -0
- package/src/lib/status.ts +45 -95
- package/src/lib/tool-icon.ts +39 -47
- package/src/lib/utils.ts +1 -3
- package/src/styles/terminal.css +81 -49
- package/src/styles/theme.css +136 -19
- package/src/workspace.ts +1 -24
- package/build/SessionPanel-13l25ubU.d.mts +0 -609
- package/build/SessionPanel-DgwjH4Ve.mjs.map +0 -1
- package/build/format-ljc3lKpA.d.mts +0 -59
- package/build/status-BE-zg88x.mjs.map +0 -1
|
@@ -3,41 +3,27 @@ import type { SessionRow } from '@workerdeck/protocol'
|
|
|
3
3
|
import { Spinner } from '../ui/Spinner.tsx'
|
|
4
4
|
import { cn } from '../../lib/utils.ts'
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* State as one glyph — a ringing bell when it wants a human, a spinner while it
|
|
8
|
-
* works, a moon when it is only sleeping. Replaces the text badge: in a sidebar
|
|
9
|
-
* the word costs more room than it earns, and the states that matter are the two
|
|
10
|
-
* you can recognise without reading.
|
|
11
|
-
*
|
|
12
|
-
* **It reads `row.state`, not `info.status`, and that distinction is the whole
|
|
13
|
-
* point of the row model.** `sessionState` already folds in the arm this glyph
|
|
14
|
-
* cannot see for itself: a *background* sub-agent outlives its turn by design,
|
|
15
|
-
* so the turn ends, `status` comes to rest at `idle`, and the agent keeps
|
|
16
|
-
* working. Reading the raw status drew a **moon on a row filed under the
|
|
17
|
-
* "Working" header** — the list contradicting itself on one line, which is
|
|
18
|
-
* exactly what a derived view model exists to prevent. The value was in scope
|
|
19
|
-
* and unread.
|
|
20
|
-
*
|
|
21
|
-
* The terminal statuses still come off `info.status`, because `ended` collapses
|
|
22
|
-
* `failed` and `closed` into one bucket and those are worth telling apart here.
|
|
23
|
-
*
|
|
24
|
-
* It lives in its own file rather than inside the dashboard's browser because
|
|
25
|
-
* the extension's cards draw it too, and a second copy there is how the two
|
|
26
|
-
* lists last disagreed about what a parked session looks like.
|
|
27
|
-
*/
|
|
28
6
|
export function SessionStatusIcon({ row, className }: { row: SessionRow; className?: string }) {
|
|
29
7
|
const { info } = row
|
|
30
8
|
const size = cn('size-4 shrink-0', className)
|
|
31
|
-
if (row.state === 'attention')
|
|
32
|
-
|
|
9
|
+
if (row.state === 'attention') {
|
|
10
|
+
return <BellRing className={cn(size, 'animate-pulse text-warning')} />
|
|
11
|
+
}
|
|
12
|
+
if (row.state === 'working') {
|
|
13
|
+
return <Spinner className={cn(size, 'text-info')} />
|
|
14
|
+
}
|
|
33
15
|
switch (info.status) {
|
|
34
|
-
case 'failed':
|
|
16
|
+
case 'failed': {
|
|
35
17
|
return <CircleAlert className={cn(size, 'text-danger')} />
|
|
36
|
-
|
|
18
|
+
}
|
|
19
|
+
case 'closed': {
|
|
37
20
|
return <CircleSlash className={cn(size, 'text-fg-4')} />
|
|
38
|
-
|
|
21
|
+
}
|
|
22
|
+
case 'parked': {
|
|
39
23
|
return <PauseCircle className={cn(size, 'text-fg-3')} />
|
|
40
|
-
|
|
24
|
+
}
|
|
25
|
+
default: {
|
|
41
26
|
return <Moon className={cn(size, 'text-fg-4')} />
|
|
27
|
+
}
|
|
42
28
|
}
|
|
43
29
|
}
|
|
@@ -1,96 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrowRight,
|
|
3
|
-
Check,
|
|
4
|
-
ChevronDown,
|
|
5
|
-
ChevronRight,
|
|
6
|
-
CircleAlert,
|
|
7
|
-
Dot,
|
|
8
|
-
PauseCircle,
|
|
9
|
-
} from 'lucide-react'
|
|
1
|
+
import { ArrowRight, Check, ChevronDown, ChevronRight, CircleAlert, Dot, PauseCircle } from 'lucide-react'
|
|
10
2
|
import { isAgentRecord, subagentLabel } from '@workerdeck/protocol'
|
|
11
3
|
import type { SessionInfo, SubagentInfo } from '@workerdeck/protocol'
|
|
12
4
|
import { Spinner } from '../ui/Spinner.tsx'
|
|
13
5
|
import { cn } from '../../lib/utils.ts'
|
|
14
6
|
|
|
15
|
-
/**
|
|
16
|
-
* The work *under* a session row, and the one row shape every client renders it
|
|
17
|
-
* through.
|
|
18
|
-
*
|
|
19
|
-
* This lived inside the VS Code webview first, which is exactly why the
|
|
20
|
-
* dashboard had none of it. Nothing in here is extension-specific: a session's
|
|
21
|
-
* sub-agents are a protocol fact, and a disclosure over them is a list
|
|
22
|
-
* affordance. It sits in `packages/ui` so the sidebar, the dashboard and (in its
|
|
23
|
-
* own idiom) the phone are annotating rows the same way rather than three ways.
|
|
24
|
-
*
|
|
25
|
-
* Today the only source is `SessionInfo.subagents`. The other — the CLI's own
|
|
26
|
-
* **task checklist**, the to-do list it keeps for the current turn — is what the
|
|
27
|
-
* design was drawn from, and it is not built: nothing on the wire carries it yet
|
|
28
|
-
* (see `_docs/features/sub-agent-handling.md`, second thread, which opens with
|
|
29
|
-
* "check a capture" rather than "design a surface"). When it arrives it is a
|
|
30
|
-
* *source*, not a second row component: checklist when the session has one, its
|
|
31
|
-
* sub-agents otherwise.
|
|
32
|
-
*
|
|
33
|
-
* That is also why `state` has a `pending` arm no sub-agent can produce. A
|
|
34
|
-
* sub-agent record exists only once dispatched, so it is never queued; a to-do
|
|
35
|
-
* is queued for most of its life, and dropping the state would mean widening the
|
|
36
|
-
* union later — the shape is cheaper to state now than to retrofit.
|
|
37
|
-
*/
|
|
38
7
|
export type Step = {
|
|
39
8
|
key: string
|
|
40
9
|
label: string
|
|
41
|
-
/** What one of these is called, for the disclosure's count. */
|
|
42
10
|
noun: string
|
|
43
|
-
/**
|
|
44
|
-
* Both kinds press. The kind decides **where the press goes**, which is the
|
|
45
|
-
* whole of the distinction.
|
|
46
|
-
*
|
|
47
|
-
* An **agent** has an identity and work of its own, so it wears the sub-agent
|
|
48
|
-
* colour and opens that agent's own frame. A **task** is something the model
|
|
49
|
-
* described with no agent behind it (`isAgentRecord`), so there is no frame:
|
|
50
|
-
* framing its id selects no items and draws an empty screen. What it has
|
|
51
|
-
* instead is a *place* — the spawning call's row in the transcript — so it is
|
|
52
|
-
* muted, never selected, and travels there. See `StepRow` for why an inert
|
|
53
|
-
* row was the worse answer.
|
|
54
|
-
*
|
|
55
|
-
* Two values and not a boolean because the checklist source this shape was
|
|
56
|
-
* drawn for (see above) produces the second kind natively — a to-do is a task
|
|
57
|
-
* in exactly this sense, and it will slot in here rather than widening
|
|
58
|
-
* anything.
|
|
59
|
-
*/
|
|
60
11
|
kind: 'agent' | 'task'
|
|
61
12
|
state: 'done' | 'running' | 'pending' | 'failed'
|
|
62
|
-
/** A trailing reading — a sub-agent's tool count. Absent draws nothing. */
|
|
63
13
|
detail?: string
|
|
64
14
|
title: string
|
|
65
15
|
onSelect: () => void
|
|
66
16
|
}
|
|
67
17
|
|
|
68
|
-
|
|
69
|
-
* The steps under one session, **agents first**.
|
|
70
|
-
*
|
|
71
|
-
* The two kinds do different things when pressed and are worth different
|
|
72
|
-
* amounts of attention: an agent has work of its own to go and read, a task is a
|
|
73
|
-
* marker in a transcript. Interleaved in dispatch order they read as one
|
|
74
|
-
* undifferentiated list, and the rows you can actually open are scattered
|
|
75
|
-
* through it. Grouped, the openable ones are a block at the top and the markers
|
|
76
|
-
* are a tail you can skip.
|
|
77
|
-
*
|
|
78
|
-
* Stable **within** each group, deliberately: dispatch order is the only order
|
|
79
|
-
* these records have that means anything (it is the order the work was started
|
|
80
|
-
* in), so the sort partitions and never reorders inside a partition.
|
|
81
|
-
*
|
|
82
|
-
* `onSelect` is handed the **kind** as well as the id, because the two kinds go
|
|
83
|
-
* to different places — see `SessionItem`, which is what routes them. Passing
|
|
84
|
-
* only the id is what let a task be opened as if it were an agent, and a framed
|
|
85
|
-
* task id matches no items, so the panel drew an **empty agent view**.
|
|
86
|
-
*/
|
|
87
|
-
export function sessionSteps(
|
|
88
|
-
info: SessionInfo,
|
|
89
|
-
onSelect: (toolUseId: string, kind: Step['kind']) => void,
|
|
90
|
-
): Step[] {
|
|
91
|
-
// The label is protocol's `subagentLabel`, not a spelling of its own: the
|
|
92
|
-
// dashboard and the phone render the same rows from the same records, and two
|
|
93
|
-
// spellings would be two different answers to "which agent is this".
|
|
18
|
+
export function sessionSteps(info: SessionInfo, onSelect: (toolUseId: string, kind: Step['kind']) => void): Step[] {
|
|
94
19
|
const steps = (info.subagents ?? []).map((sub) => {
|
|
95
20
|
const kind = isAgentRecord(sub) ? ('agent' as const) : ('task' as const)
|
|
96
21
|
return {
|
|
@@ -109,30 +34,22 @@ export function sessionSteps(
|
|
|
109
34
|
|
|
110
35
|
function stepState(status: SubagentInfo['status']): Step['state'] {
|
|
111
36
|
switch (status) {
|
|
112
|
-
case 'running':
|
|
37
|
+
case 'running': {
|
|
113
38
|
return 'running'
|
|
114
|
-
|
|
39
|
+
}
|
|
40
|
+
case 'failed': {
|
|
115
41
|
return 'failed'
|
|
116
|
-
|
|
42
|
+
}
|
|
43
|
+
default: {
|
|
117
44
|
return 'done'
|
|
45
|
+
}
|
|
118
46
|
}
|
|
119
47
|
}
|
|
120
48
|
|
|
121
|
-
/** How many of these are still going — the live half of the disclosure's count. */
|
|
122
49
|
export function runningSteps(steps: readonly Step[]): number {
|
|
123
50
|
return steps.filter((s) => s.state === 'running').length
|
|
124
51
|
}
|
|
125
52
|
|
|
126
|
-
/**
|
|
127
|
-
* The disclosure, which is also the reading: `3 agents` — or `2 of 3 agents`
|
|
128
|
-
* while some have settled, because "how many are still going" is the live
|
|
129
|
-
* question and a bare total answers it wrong the moment one finishes.
|
|
130
|
-
*
|
|
131
|
-
* Sub-agents are an annotation on a working row rather than a state of their own
|
|
132
|
-
* (see `runningSubagents` in protocol's `session-list.ts`), so this never
|
|
133
|
-
* competes with the row's status glyph: that still says what the *session* is
|
|
134
|
-
* doing.
|
|
135
|
-
*/
|
|
136
53
|
export function StepToggle({
|
|
137
54
|
expanded,
|
|
138
55
|
running,
|
|
@@ -146,27 +63,16 @@ export function StepToggle({
|
|
|
146
63
|
noun: string
|
|
147
64
|
onToggle: () => void
|
|
148
65
|
}) {
|
|
149
|
-
// Two spellings of one count. The **words** are the honest reading and go in
|
|
150
|
-
// the tooltip and to a screen reader; the line itself gets the digits, because
|
|
151
|
-
// this sits on the second line of a 280px row next to the folder and the age,
|
|
152
|
-
// and `1 of 6 agents` truncated the folder name away to say something the row
|
|
153
|
-
// could say in three characters.
|
|
154
66
|
const label = running > 0 && running < total ? `${running}/${total}` : String(total)
|
|
155
|
-
const words =
|
|
156
|
-
running > 0 && running < total
|
|
157
|
-
? `${running} of ${total} ${noun}s running`
|
|
158
|
-
: `${total} ${noun}${total === 1 ? '' : 's'}`
|
|
67
|
+
const words = running > 0 && running < total ? `${running} of ${total} ${noun}s running` : `${total} ${noun}${total === 1 ? '' : 's'}`
|
|
159
68
|
const Chevron = expanded ? ChevronDown : ChevronRight
|
|
160
69
|
return (
|
|
161
70
|
<button
|
|
162
|
-
type=
|
|
71
|
+
type="button"
|
|
163
72
|
aria-expanded={expanded}
|
|
164
73
|
aria-label={`${expanded ? 'Hide' : 'Show'} ${words}`}
|
|
165
74
|
title={`${expanded ? 'Hide' : 'Show'} ${words}`}
|
|
166
75
|
onClick={(e) => {
|
|
167
|
-
// The whole row is a button and this one does not mean "select" — the
|
|
168
|
-
// same guard the row's overflow needs, and the reason a drag-select
|
|
169
|
-
// inside the row does not toggle it.
|
|
170
76
|
e.stopPropagation()
|
|
171
77
|
onToggle()
|
|
172
78
|
}}
|
|
@@ -174,83 +80,24 @@ export function StepToggle({
|
|
|
174
80
|
'flex shrink-0 items-center gap-0.5 rounded-[4px] py-0.5 pr-1 pl-0.5 outline-none',
|
|
175
81
|
'text-[0.75rem] leading-3 hover:bg-row-hover hover:text-fg-2',
|
|
176
82
|
running > 0 ? 'text-info' : 'text-fg-4',
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
|
-
<
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
<Chevron className="size-3" />
|
|
86
|
+
<span className="tabular-nums">{label}</span>
|
|
180
87
|
</button>
|
|
181
88
|
)
|
|
182
89
|
}
|
|
183
90
|
|
|
184
|
-
|
|
185
|
-
* One step under its session — **pressable, all of them**, and what a press
|
|
186
|
-
* means is what tells the two kinds apart.
|
|
187
|
-
*
|
|
188
|
-
* Pressing an **agent** hands the panel over to that agent's own work
|
|
189
|
-
* (`SessionPanel.openSubagent`): it is not a session and never becomes one, but
|
|
190
|
-
* it has a surface, so it can be the selected thing. Pressing a **task** selects
|
|
191
|
-
* the *session* and travels to that task's marker inside it — a task is a
|
|
192
|
-
* reference to a place in a transcript, not a thing with a screen, so it can be
|
|
193
|
-
* followed but never held.
|
|
194
|
-
*
|
|
195
|
-
* That is a reversal, and a deliberate one. A task used to be inert markup on
|
|
196
|
-
* the argument that "a disabled-looking button still announces itself as one" —
|
|
197
|
-
* correct about the markup, wrong about the premise, because there *was* always
|
|
198
|
-
* somewhere to go and the row simply swallowed the click on its way there. A row
|
|
199
|
-
* that looks like a list item, sits in a list, and does nothing when pressed is
|
|
200
|
-
* the worse lie. So every step answers the pointer and every step answers a
|
|
201
|
-
* press; only what the press does differs.
|
|
202
|
-
*
|
|
203
|
-
* Divided from the card's header by **indentation and its own hit shape**, not
|
|
204
|
-
* by a rule. The rules came first, on the argument that at 11px an indent is not
|
|
205
|
-
* enough to say "list inside a row" — and they were right about the reading and
|
|
206
|
-
* wrong about the cost: a stack of hairlines across every open card turned the
|
|
207
|
-
* list into a ledger, and a rule cannot answer a pointer. A step that lights up
|
|
208
|
-
* under the cursor and fills when it is the one on screen says *list* far more
|
|
209
|
-
* plainly than a line between two of them, and it says it while doing the job
|
|
210
|
-
* the rule could not.
|
|
211
|
-
*
|
|
212
|
-
* **Only an agent can wear the selection**, and `active` is guarded on that here
|
|
213
|
-
* rather than trusted from the caller: a host that hands back a task's key is
|
|
214
|
-
* describing where it navigated, not what it selected, and the row must not
|
|
215
|
-
* paint itself blue for it.
|
|
216
|
-
*
|
|
217
|
-
* The hover is `--row-active` — **alpha, not a flat fill** — because this row
|
|
218
|
-
* has to answer the pointer on three different grounds: a transparent card, a
|
|
219
|
-
* blue one (its session is selected), and a grey one (a sibling agent is). A
|
|
220
|
-
* flat value tuned for any of those is wrong on the other two; a tint darkens or
|
|
221
|
-
* lifts whatever it lands on. It is the one place in the list where the alpha
|
|
222
|
-
* token earns its keep.
|
|
223
|
-
*/
|
|
224
|
-
export function StepRow({
|
|
225
|
-
step,
|
|
226
|
-
active = false,
|
|
227
|
-
onSelect,
|
|
228
|
-
}: {
|
|
229
|
-
step: Step
|
|
230
|
-
/** The panel is showing this step's own work. Ignored for tasks, which cannot
|
|
231
|
-
* be the selected thing — see above. */
|
|
232
|
-
active?: boolean
|
|
233
|
-
onSelect: () => void
|
|
234
|
-
}) {
|
|
91
|
+
export function StepRow({ step, active = false, onSelect }: { step: Step; active?: boolean; onSelect: () => void }) {
|
|
235
92
|
const agent = step.kind === 'agent'
|
|
236
93
|
const selected = active && agent
|
|
237
|
-
// Body colour by *kind*, state carried by the icon — the rule the transcript's
|
|
238
|
-
// own `TaskRow` already follows, where the body is green and the marker holds
|
|
239
|
-
// the beat. Green means sub-agent across this product (it is the one hue
|
|
240
|
-
// nothing else on a session surface claims), so a list that spent blue on
|
|
241
|
-
// "running" here would be saying something different from the transcript
|
|
242
|
-
// about the same agent. Failure still outranks it: an alarm is not a category.
|
|
243
94
|
const body = step.state === 'failed' ? 'text-danger' : agent ? 'text-success' : 'text-fg-4'
|
|
244
95
|
return (
|
|
245
96
|
<button
|
|
246
|
-
type=
|
|
97
|
+
type="button"
|
|
247
98
|
title={step.title}
|
|
248
99
|
aria-current={selected || undefined}
|
|
249
100
|
onClick={(e) => {
|
|
250
|
-
// The whole card is pressable underneath and means "open the session".
|
|
251
|
-
// This row has its own answer — for an agent a different destination,
|
|
252
|
-
// for a task the same one plus a place to land — so it must not also
|
|
253
|
-
// fire the card's.
|
|
254
101
|
e.stopPropagation()
|
|
255
102
|
onSelect()
|
|
256
103
|
}}
|
|
@@ -259,32 +106,32 @@ export function StepRow({
|
|
|
259
106
|
'text-left text-micro outline-none',
|
|
260
107
|
selected ? 'bg-row-selected' : 'hover:bg-row-active',
|
|
261
108
|
body,
|
|
262
|
-
)}
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
263
111
|
<StepIcon state={step.state} kind={step.kind} />
|
|
264
|
-
<span className=
|
|
265
|
-
{
|
|
266
|
-
|
|
267
|
-
which is the same call `taskSummary` makes one surface over. */}
|
|
268
|
-
{step.detail ? <span className='shrink-0 tabular-nums text-fg-4'>{step.detail}</span> : null}
|
|
269
|
-
{agent ? <ArrowRight className='size-3.5 shrink-0 text-fg-4' /> : null}
|
|
112
|
+
<span className="min-w-0 flex-1 truncate">{step.label}</span>
|
|
113
|
+
{step.detail ? <span className="shrink-0 tabular-nums text-fg-4">{step.detail}</span> : null}
|
|
114
|
+
{agent ? <ArrowRight className="size-3.5 shrink-0 text-fg-4" /> : null}
|
|
270
115
|
</button>
|
|
271
116
|
)
|
|
272
117
|
}
|
|
273
118
|
|
|
274
119
|
function StepIcon({ state, kind }: { state: Step['state']; kind: Step['kind'] }) {
|
|
275
|
-
// A task that is neither running nor failed gets a neutral dot rather than a
|
|
276
|
-
// tick: `done` is a claim about work, and nothing here did any.
|
|
277
120
|
if (kind === 'task' && state !== 'running' && state !== 'failed') {
|
|
278
|
-
return <Dot className=
|
|
121
|
+
return <Dot className="size-[11px] shrink-0" />
|
|
279
122
|
}
|
|
280
123
|
switch (state) {
|
|
281
|
-
case 'running':
|
|
282
|
-
return <Spinner className=
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return <
|
|
124
|
+
case 'running': {
|
|
125
|
+
return <Spinner className="size-[11px] shrink-0" />
|
|
126
|
+
}
|
|
127
|
+
case 'failed': {
|
|
128
|
+
return <CircleAlert className="size-[11px] shrink-0" />
|
|
129
|
+
}
|
|
130
|
+
case 'pending': {
|
|
131
|
+
return <PauseCircle className="size-[11px] shrink-0" />
|
|
132
|
+
}
|
|
133
|
+
default: {
|
|
134
|
+
return <Check className="size-[11px] shrink-0" />
|
|
135
|
+
}
|
|
289
136
|
}
|
|
290
137
|
}
|