@workerdeck/ui 0.15.0 → 0.16.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.
Files changed (65) hide show
  1. package/README.md +53 -0
  2. package/build/{SessionPanel-J2U8v88q.d.mts → SessionPanel-B9CHoq8x.d.mts} +158 -21
  3. package/build/{SessionPanel-DI1NO4l8.mjs → SessionPanel-DII9MmQ8.mjs} +4254 -1661
  4. package/build/SessionPanel-DII9MmQ8.mjs.map +1 -0
  5. package/build/{format-ljc3lKpA.d.mts → format-DfI_je9S.d.mts} +1 -1
  6. package/build/format.d.mts +39 -4
  7. package/build/format.mjs +2 -118
  8. package/build/index.d.mts +442 -45
  9. package/build/index.mjs +105 -2
  10. package/build/index.mjs.map +1 -1
  11. package/build/status-Ydzi7n6j.mjs +143 -0
  12. package/build/status-Ydzi7n6j.mjs.map +1 -0
  13. package/build/workspace.d.mts +9 -1
  14. package/build/workspace.mjs +108 -5
  15. package/build/workspace.mjs.map +1 -1
  16. package/package.json +14 -7
  17. package/src/components/agent/Composer.tsx +189 -89
  18. package/src/components/agent/Conversation.tsx +12 -12
  19. package/src/components/agent/FileCard.tsx +0 -26
  20. package/src/components/agent/FileTree.tsx +9 -8
  21. package/src/components/agent/Loader.tsx +22 -72
  22. package/src/components/agent/Message.tsx +11 -46
  23. package/src/components/agent/PermissionPrompt.tsx +0 -92
  24. package/src/components/agent/QuestionPrompt.tsx +0 -122
  25. package/src/components/agent/Reasoning.tsx +5 -19
  26. package/src/components/agent/Response.tsx +1 -132
  27. package/src/components/agent/SessionPanel.tsx +224 -28
  28. package/src/components/agent/SessionWorkspace.tsx +29 -0
  29. package/src/components/agent/StatusBar.tsx +20 -4
  30. package/src/components/agent/ToolCallCard.tsx +17 -111
  31. package/src/components/agent/Transcript.tsx +710 -203
  32. package/src/components/agent/UsageDialog.tsx +20 -106
  33. package/src/components/agent/UsageMeters.tsx +133 -0
  34. package/src/components/agent/pulse.tsx +3 -2
  35. package/src/components/agent/transcript-rows.ts +82 -0
  36. package/src/components/agent/transcript-variant.tsx +29 -51
  37. package/src/components/agent/use-height-epoch.ts +60 -0
  38. package/src/components/agent/use-path-links.ts +147 -0
  39. package/src/components/agent/use-transcript-jumps.ts +190 -0
  40. package/src/components/prompt-area/cursor-helpers.ts +65 -0
  41. package/src/components/prompt-area/use-prompt-area.ts +16 -10
  42. package/src/components/terminal/PermissionPrompt.tsx +119 -0
  43. package/src/components/terminal/QuestionPrompt.tsx +322 -0
  44. package/src/components/terminal/StatusLine.tsx +159 -0
  45. package/src/components/terminal/TerminalTranscript.tsx +147 -0
  46. package/src/components/terminal/affordances.tsx +118 -0
  47. package/src/components/terminal/diff.tsx +130 -0
  48. package/src/components/terminal/height.ts +727 -0
  49. package/src/components/terminal/items.tsx +449 -0
  50. package/src/components/terminal/markdown.tsx +191 -0
  51. package/src/components/terminal/press.tsx +120 -0
  52. package/src/components/terminal/prompt.tsx +343 -0
  53. package/src/components/terminal/result-preview.ts +72 -0
  54. package/src/components/terminal/row.tsx +132 -0
  55. package/src/components/terminal/scrubber.tsx +663 -0
  56. package/src/components/terminal/surface.tsx +80 -0
  57. package/src/components/terminal/tool-run.ts +91 -0
  58. package/src/index.ts +34 -0
  59. package/src/lib/status.ts +59 -3
  60. package/src/lib/tool-icon.ts +14 -0
  61. package/src/styles/terminal.css +1011 -0
  62. package/src/styles/theme.css +41 -0
  63. package/build/SessionPanel-DI1NO4l8.mjs.map +0 -1
  64. package/build/format.mjs.map +0 -1
  65. package/src/components/agent/line-prompt.tsx +0 -249
@@ -6,10 +6,7 @@ import { CodeBlock } from '../ui/CodeBlock.tsx'
6
6
  import { Spinner } from '../ui/Spinner.tsx'
7
7
  import { cn } from '../../lib/utils.ts'
8
8
  import { toolInputPreview } from '../../lib/format.ts'
9
- import { isMutatingTool, toolIcon } from '../../lib/tool-icon.ts'
10
- import { LINE_INDENT, LinePayload } from './line-prompt.tsx'
11
- import { usePulse } from './pulse.tsx'
12
- import { LineGlyph, useLines } from './transcript-variant.tsx'
9
+ import { toolIcon } from '../../lib/tool-icon.ts'
13
10
 
14
11
  export type ToolCallItem = Extract<TranscriptItem, { kind: 'tool_call' }>
15
12
 
@@ -51,15 +48,6 @@ const STATE_BADGE = {
51
48
  failed: { label: 'Error', variant: 'danger', busy: false },
52
49
  } as const
53
50
 
54
- /** The gutter dot's colour in `lines`: the state, said without a badge. */
55
- const STATE_GLYPH = {
56
- running: 'text-info',
57
- pending: 'text-info',
58
- deferred: 'text-accent',
59
- settled: 'text-fg-4',
60
- failed: 'text-danger',
61
- } as const
62
-
63
51
  /**
64
52
  * The language to highlight a payload as.
65
53
  *
@@ -91,47 +79,35 @@ function resultLanguage(item: ToolCallItem): string | undefined {
91
79
  type Status = keyof typeof STATE_BADGE
92
80
 
93
81
  export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps) {
94
- const lines = useLines()
95
82
  const [open, setOpen] = useState(false)
96
83
  const [fullResult, setFullResult] = useState(false)
97
84
  const imagePath = imagePathOf(item)
98
85
  const status: Status = item.status ?? (item.result === undefined ? 'running' : 'settled')
99
86
  const badge = STATE_BADGE[status]
100
87
  const isError = status === 'failed' || item.result?.isError === true
101
- // Ticks only while this row is actually running, and only in the variant with a
102
- // gutter to pulse in — an idle transcript of a hundred settled tools starts no
103
- // timers at all.
104
- const pulse = usePulse(lines && badge.busy)
105
88
  const Icon = toolIcon(item.name)
106
89
 
107
90
  const resultText = item.result?.text ?? ''
108
91
  const truncated = !fullResult && resultText.length > RESULT_PREVIEW_CHARS
109
92
  const shownResult = truncated ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText
110
93
 
111
- // In `lines` the payloads are dim label + highlighted band, not framed cards:
112
- // a tool call is already one row, and boxing what it expands into puts back
113
- // the chrome the variant exists to remove.
114
- const blockVariant = lines ? 'plain' : 'panel'
115
- const Payload = lines ? HighlightedPayload : PlainPayload
116
94
  const details = open ? (
117
- <div className={cn('flex flex-col', lines ? 'gap-1 py-1' : 'gap-2 border-t border-border p-2.5')}>
118
- <Payload
95
+ <div className='flex flex-col gap-2 border-t border-border p-2.5'>
96
+ <PlainPayload
119
97
  code={JSON.stringify(item.input, null, 2)}
120
98
  language='json'
121
99
  label='Parameters'
122
- variant={blockVariant}
123
100
  />
124
101
  {item.logs?.length ? (
125
- <Payload code={item.logs.join('\n')} label='Logs' variant={blockVariant} />
102
+ <PlainPayload code={item.logs.join('\n')} label='Logs'/>
126
103
  ) : null}
127
104
  {item.result !== undefined ? (
128
105
  <div>
129
- <Payload
106
+ <PlainPayload
130
107
  code={shownResult || '(empty result)'}
131
108
  language={resultLanguage(item)}
132
109
  label={isError ? 'Error' : 'Result'}
133
- variant={blockVariant}
134
- className={cn(isError && (lines ? '[&_pre]:text-danger' : 'border-danger/40 [&_pre]:text-danger'))}
110
+ className={cn(isError && 'border-danger/40 [&_pre]:text-danger')}
135
111
  />
136
112
  {truncated ? (
137
113
  <button
@@ -148,63 +124,7 @@ export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps)
148
124
 
149
125
  // The picture is the point of the call — shown without expanding, the way the
150
126
  // tool's own output would be if the engine had sent bytes.
151
- const image = imagePath && hostImage ? <HostImage path={imagePath} load={hostImage} lines={lines} /> : null
152
-
153
- if (lines) {
154
- return (
155
- <div data-slot='tool-call' data-state={status} className={cn('w-full', className)}>
156
- <button
157
- type='button'
158
- onClick={() => setOpen((v) => !v)}
159
- className='flex w-full items-baseline gap-2 text-left outline-none'>
160
- <LineGlyph
161
- className={
162
- // A settled write is green: skimming a run, "what did it change"
163
- // is the question you come back to, and it is the one you might
164
- // need to undo. Every other state keeps its own colour — a failed
165
- // write is a failure first.
166
- status === 'settled' && !isError && isMutatingTool(item.name)
167
- ? 'text-success'
168
- : STATE_GLYPH[status]
169
- }>
170
- {/* Running: the mark's own pulse, so a working tool row and the
171
- transcript's working line beat together. Settled: a plain dot,
172
- which reads as "done" precisely by not moving. */}
173
- {badge.busy ? pulse : '●'}
174
- </LineGlyph>
175
- <span className='min-w-0 flex-1 truncate text-body-sm leading-5 text-fg-3'>
176
- <span className='font-medium text-fg-1'>{item.name}</span>
177
- <span className='text-fg-4'>({toolInputPreview(item.input)})</span>
178
- </span>
179
- {item.backend && item.backend !== 'server' ? (
180
- <span className='shrink-0 text-label text-fg-4'>{item.backend}</span>
181
- ) : null}
182
- {/* No Spinner here: the gutter glyph animates now, and two spinners on
183
- one row is one too many. `cards` keeps its own — it has no gutter. */}
184
- {status === 'deferred' ? <Clock className='size-3 shrink-0 self-center text-fg-4' /> : null}
185
- {isError && !badge.busy ? (
186
- <span className='shrink-0 text-label text-danger'>error</span>
187
- ) : null}
188
- </button>
189
- {/* Collapsed, the first line of the output is the whole story most of
190
- the time — a summary line costs one row and saves an expand. */}
191
- {!open && resultText ? (
192
- <div className='flex items-baseline gap-2'>
193
- <LineGlyph className='text-fg-4'>⎿</LineGlyph>
194
- <span
195
- className={cn(
196
- 'min-w-0 flex-1 truncate text-label leading-5',
197
- isError ? 'text-danger' : 'text-fg-4',
198
- )}>
199
- {resultSummary(resultText)}
200
- </span>
201
- </div>
202
- ) : null}
203
- {image}
204
- {details ? <div className={LINE_INDENT}>{details}</div> : null}
205
- </div>
206
- )
207
- }
127
+ const image = imagePath && hostImage ? <HostImage path={imagePath} load={hostImage} /> : null
208
128
 
209
129
  return (
210
130
  <div
@@ -243,31 +163,19 @@ export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps)
243
163
  )
244
164
  }
245
165
 
246
- /** The first line worth showing of a tool result, for the collapsed summary. */
247
- function resultSummary(text: string): string {
248
- const first = text.split('\n').find((line) => line.trim() !== '') ?? ''
249
- const rest = text.trimEnd().split('\n').length - 1
250
- const trimmed = first.trim()
251
- return rest > 0 ? `${trimmed} (+${rest} lines)` : trimmed
252
- }
253
-
254
- type PayloadProps = {
166
+ /** The framed card the `cards` transcript expands into. Unhighlighted by
167
+ * design: it is structured data in a panel, not a file. */
168
+ function PlainPayload({
169
+ code,
170
+ label,
171
+ className,
172
+ }: {
255
173
  code: string
256
174
  label: string
257
175
  language?: string
258
- variant: 'panel' | 'plain'
259
176
  className?: string
260
- }
261
-
262
- /** The framed card, for the `cards` transcript. Unhighlighted by design: it is
263
- * structured data in a panel, not a file. */
264
- function PlainPayload({ code, label, variant, className }: PayloadProps) {
265
- return <CodeBlock code={code} label={label} variant={variant} className={className} />
266
- }
267
-
268
- /** The terminal payload — shared with the line-shaped prompts. */
269
- function HighlightedPayload({ code, label, language, className }: PayloadProps) {
270
- return <LinePayload code={code} label={label} language={language} className={className} />
177
+ }) {
178
+ return <CodeBlock code={code} label={label} variant='panel' className={className} />
271
179
  }
272
180
 
273
181
  /**
@@ -282,11 +190,9 @@ function HighlightedPayload({ code, label, language, className }: PayloadProps)
282
190
  function HostImage({
283
191
  path,
284
192
  load,
285
- lines,
286
193
  }: {
287
194
  path: string
288
195
  load: (path: string) => Promise<string | undefined>
289
- lines?: boolean
290
196
  }) {
291
197
  const [src, setSrc] = useState<string | undefined>()
292
198
  useEffect(() => {
@@ -305,7 +211,7 @@ function HostImage({
305
211
  }, [path, load])
306
212
  if (!src) return null
307
213
  return (
308
- <div className={cn(lines ? cn('py-1', LINE_INDENT) : 'border-t border-border p-2.5')}>
214
+ <div className='border-t border-border p-2.5'>
309
215
  <img
310
216
  src={src}
311
217
  alt={path.split('/').pop() ?? 'Generated image'}