@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.
Files changed (145) hide show
  1. package/build/{SessionPanel-DgwjH4Ve.mjs → SessionPanel-DDgBkHsi.mjs} +2196 -3364
  2. package/build/SessionPanel-DDgBkHsi.mjs.map +1 -0
  3. package/build/SessionPanel-E_L_ldQ5.d.mts +207 -0
  4. package/build/format-B8ZM9LPy.d.mts +16 -0
  5. package/build/format.d.mts +8 -75
  6. package/build/format.mjs +2 -2
  7. package/build/index.d.mts +63 -1138
  8. package/build/index.mjs +4 -300
  9. package/build/index.mjs.map +1 -1
  10. package/build/scoped.css +80 -24
  11. package/build/{status-BE-zg88x.mjs → status-C0HSLZas.mjs} +14 -85
  12. package/build/status-C0HSLZas.mjs.map +1 -0
  13. package/build/workspace.d.mts +9 -124
  14. package/build/workspace.mjs +13 -143
  15. package/build/workspace.mjs.map +1 -1
  16. package/package.json +4 -4
  17. package/src/components/agent/CodeEditor.tsx +31 -103
  18. package/src/components/agent/Composer.tsx +114 -333
  19. package/src/components/agent/ContextDialog.tsx +20 -41
  20. package/src/components/agent/ContextRing.tsx +6 -28
  21. package/src/components/agent/Conversation.tsx +21 -33
  22. package/src/components/agent/EditorTabs.tsx +39 -67
  23. package/src/components/agent/EngineIcon.tsx +29 -72
  24. package/src/components/agent/EntryIcon.tsx +16 -0
  25. package/src/components/agent/FileCard.tsx +10 -16
  26. package/src/components/agent/FileTree.tsx +52 -125
  27. package/src/components/agent/FileViewer.tsx +30 -60
  28. package/src/components/agent/HostFilesDialog.tsx +40 -69
  29. package/src/components/agent/Loader.tsx +3 -24
  30. package/src/components/agent/McpDialog.tsx +64 -137
  31. package/src/components/agent/Message.tsx +2 -21
  32. package/src/components/agent/ModelSelect.tsx +33 -62
  33. package/src/components/agent/PermissionModeSelect.tsx +21 -79
  34. package/src/components/agent/PermissionPrompt.tsx +59 -63
  35. package/src/components/agent/ProjectIcon.tsx +228 -93
  36. package/src/components/agent/PromptTokenText.tsx +10 -16
  37. package/src/components/agent/QuestionPrompt.tsx +43 -64
  38. package/src/components/agent/Reasoning.tsx +13 -16
  39. package/src/components/agent/Response.tsx +4 -6
  40. package/src/components/agent/Scrubber.tsx +31 -80
  41. package/src/components/agent/SessionBrowser.tsx +64 -243
  42. package/src/components/agent/SessionEmptyState.tsx +10 -40
  43. package/src/components/agent/SessionInfoDialog.tsx +46 -78
  44. package/src/components/agent/SessionItem.tsx +53 -320
  45. package/src/components/agent/SessionList.tsx +23 -39
  46. package/src/components/agent/SessionPanel.tsx +345 -923
  47. package/src/components/agent/SessionStatusIcon.tsx +14 -28
  48. package/src/components/agent/SessionSteps.tsx +34 -187
  49. package/src/components/agent/SessionWorkspace.tsx +108 -215
  50. package/src/components/agent/SkillsDialog.tsx +35 -80
  51. package/src/components/agent/StatusBar.tsx +51 -136
  52. package/src/components/agent/SubagentStrip.tsx +12 -77
  53. package/src/components/agent/ToolCallCard.tsx +94 -135
  54. package/src/components/agent/Transcript.tsx +103 -1288
  55. package/src/components/agent/TranscriptItemView.tsx +155 -0
  56. package/src/components/agent/TranscriptRows.tsx +418 -0
  57. package/src/components/agent/UsageDialog.tsx +7 -29
  58. package/src/components/agent/UsageMeters.tsx +18 -73
  59. package/src/components/agent/pulse.tsx +9 -32
  60. package/src/components/agent/scrubber-marks.ts +33 -103
  61. package/src/components/agent/status.ts +1 -5
  62. package/src/components/agent/tool-result-fetch.tsx +3 -25
  63. package/src/components/agent/tool-result-image.tsx +32 -111
  64. package/src/components/agent/transcript-rows.ts +41 -118
  65. package/src/components/agent/transcript-variant.tsx +3 -80
  66. package/src/components/agent/use-height-epoch.ts +10 -24
  67. package/src/components/agent/use-path-links.ts +45 -63
  68. package/src/components/agent/use-subagent-frame.ts +106 -0
  69. package/src/components/agent/use-transcript-jumps.ts +49 -107
  70. package/src/components/prompt-area/animated-placeholder.tsx +6 -5
  71. package/src/components/prompt-area/clipboard-helpers.ts +26 -21
  72. package/src/components/prompt-area/cursor-helpers.ts +79 -37
  73. package/src/components/prompt-area/dom-helpers.ts +97 -69
  74. package/src/components/prompt-area/file-strip.tsx +90 -80
  75. package/src/components/prompt-area/html-to-markdown.ts +67 -46
  76. package/src/components/prompt-area/image-strip.tsx +8 -18
  77. package/src/components/prompt-area/index.ts +2 -15
  78. package/src/components/prompt-area/prompt-area-engine.ts +100 -115
  79. package/src/components/prompt-area/prompt-area-list-ops.ts +66 -59
  80. package/src/components/prompt-area/prompt-area.tsx +31 -38
  81. package/src/components/prompt-area/remove-button.tsx +3 -9
  82. package/src/components/prompt-area/segment-helpers.ts +4 -12
  83. package/src/components/prompt-area/trigger-popover.tsx +15 -27
  84. package/src/components/prompt-area/trigger-presets.ts +0 -10
  85. package/src/components/prompt-area/types.ts +3 -8
  86. package/src/components/prompt-area/use-chip-editing.ts +305 -0
  87. package/src/components/prompt-area/use-markdown-mode.ts +7 -17
  88. package/src/components/prompt-area/use-prompt-area-events.ts +80 -86
  89. package/src/components/prompt-area/use-prompt-area-keydown.ts +598 -0
  90. package/src/components/prompt-area/use-prompt-area-state.ts +5 -23
  91. package/src/components/prompt-area/use-prompt-area.ts +170 -896
  92. package/src/components/prompt-area/use-trigger-search.ts +21 -19
  93. package/src/components/terminal/PermissionPrompt.tsx +56 -59
  94. package/src/components/terminal/QuestionPrompt.tsx +28 -79
  95. package/src/components/terminal/StatusLine.tsx +13 -63
  96. package/src/components/terminal/TerminalTranscript.tsx +44 -160
  97. package/src/components/terminal/affordances.tsx +68 -98
  98. package/src/components/terminal/blocks.ts +20 -156
  99. package/src/components/terminal/diff.tsx +19 -67
  100. package/src/components/terminal/height.ts +163 -313
  101. package/src/components/terminal/image-box.ts +0 -44
  102. package/src/components/terminal/items.tsx +150 -305
  103. package/src/components/terminal/markdown.tsx +36 -95
  104. package/src/components/terminal/press.tsx +26 -53
  105. package/src/components/terminal/prompt.tsx +40 -170
  106. package/src/components/terminal/result-preview.ts +8 -56
  107. package/src/components/terminal/row.tsx +11 -98
  108. package/src/components/terminal/scrubber.tsx +108 -401
  109. package/src/components/terminal/surface.tsx +6 -54
  110. package/src/components/terminal/todos.ts +66 -0
  111. package/src/components/terminal/tool-run.ts +21 -192
  112. package/src/components/ui/AlertDialog.tsx +8 -23
  113. package/src/components/ui/Badge.tsx +18 -29
  114. package/src/components/ui/Button.tsx +7 -15
  115. package/src/components/ui/Card.tsx +2 -13
  116. package/src/components/ui/CodeBlock.tsx +10 -37
  117. package/src/components/ui/CopyButton.tsx +10 -23
  118. package/src/components/ui/Dialog.tsx +23 -36
  119. package/src/components/ui/Empty.tsx +6 -29
  120. package/src/components/ui/Input.tsx +14 -16
  121. package/src/components/ui/Menu.tsx +6 -18
  122. package/src/components/ui/PortalScope.tsx +1 -21
  123. package/src/components/ui/ProgressRing.tsx +8 -19
  124. package/src/components/ui/Select.tsx +18 -31
  125. package/src/components/ui/Sonner.tsx +1 -2
  126. package/src/components/ui/Spinner.tsx +1 -1
  127. package/src/components/ui/Splitter.tsx +30 -64
  128. package/src/components/ui/Textarea.tsx +2 -5
  129. package/src/components/ui/Tooltip.tsx +3 -13
  130. package/src/format.ts +0 -9
  131. package/src/index.ts +16 -113
  132. package/src/lib/clipboard.ts +8 -28
  133. package/src/lib/css.ts +8 -0
  134. package/src/lib/format.ts +101 -86
  135. package/src/lib/plan-request.ts +16 -0
  136. package/src/lib/status.ts +45 -95
  137. package/src/lib/tool-icon.ts +39 -47
  138. package/src/lib/utils.ts +1 -3
  139. package/src/styles/terminal.css +81 -49
  140. package/src/styles/theme.css +136 -19
  141. package/src/workspace.ts +1 -24
  142. package/build/SessionPanel-13l25ubU.d.mts +0 -609
  143. package/build/SessionPanel-DgwjH4Ve.mjs.map +0 -1
  144. package/build/format-ljc3lKpA.d.mts +0 -59
  145. package/build/status-BE-zg88x.mjs.map +0 -1
@@ -4,13 +4,10 @@ import { cn } from '../../lib/utils.ts'
4
4
 
5
5
  export interface ResponseProps {
6
6
  children: string
7
- /** Streaming text: tolerate incomplete markdown (unclosed fences, half links). */
8
7
  streaming?: boolean
9
8
  className?: string
10
9
  }
11
10
 
12
- /** Markdown renderer for assistant output — streaming-safe via streamdown, code
13
- * highlighted with shiki (dual theme follows [data-theme] through the dark: variant). */
14
11
  export const Response = memo(
15
12
  function Response({ children, streaming, className }: ResponseProps) {
16
13
  return (
@@ -18,11 +15,12 @@ export const Response = memo(
18
15
  mode={streaming ? 'streaming' : 'static'}
19
16
  parseIncompleteMarkdown={streaming}
20
17
  shikiTheme={['github-light', 'github-dark']}
21
- className={cn('size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0', className)}>
18
+ linkSafety={{ enabled: false }}
19
+ className={cn('size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0', className)}
20
+ >
22
21
  {children}
23
22
  </Streamdown>
24
23
  )
25
24
  },
26
- (prev, next) =>
27
- prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className,
25
+ (prev, next) => prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className,
28
26
  )
@@ -15,48 +15,19 @@ import {
15
15
  type Mark,
16
16
  } from './scrubber-marks.ts'
17
17
 
18
- /**
19
- * The cards variant's overview rail — the terminal scrubber's proportional
20
- * sibling.
21
- *
22
- * Positions are **index space**, not pixel space: mark y is
23
- * `itemIndex / items.length` of the rail, because proportional text and
24
- * variable row heights leave the cards transcript with no honest pixel claim
25
- * (the terminal rail's positions ride the height calculator, which has none
26
- * here). Less precise, but it still answers the reader's questions — where did
27
- * I type, where did it fail, where is the approval waiting.
28
- *
29
- * And because the positions are approximate, the rail is an **annotation, not
30
- * a scrollbar**: no drag-to-scrub, no viewport band (meaningless without pixel
31
- * positions), and the native scrollbar stays. The container never takes the
32
- * pointer — only the marks do, when interactive — so the scrollbar keeps
33
- * working straight through the paint. Hover peeks (from `items`, never the DOM
34
- * — the row a mark describes is usually unmounted) and a click jumps to the
35
- * mark's item through `onJumpToItem`.
36
- */
37
-
38
18
  export interface ScrubberProps {
39
19
  items: readonly TranscriptItem[]
40
20
  pendingApprovals: readonly PermissionRequest[]
41
- /** The catch-up boundary's item index, when the recap is spliced in. */
42
21
  recapItemIndex?: number
43
- /** Bookmarked item indices. Paint only — no store, no set affordance. */
44
22
  bookmarks?: readonly number[]
45
- /** The sub-agent takeover's parent id, when this rail belongs to a frame —
46
- * what "top level" means to the mark walk (see `buildMarks`). */
47
23
  frameParentId?: string
48
- /** Whether the rail answers the pointer (hover peek, click to jump). False
49
- * renders passive paint. */
50
24
  interactive?: boolean
51
- /** Jump to an item index. The caller owns the row mapping — item indices are
52
- * not row indices (`rowIndexForItem`). */
53
25
  onJumpToItem?: (itemIndex: number) => void
54
26
  className?: string
55
27
  }
56
28
 
57
29
  function peekContent(
58
30
  cluster: Cluster,
59
- /** The member the pointer resolved to — see {@link nearestMember}. */
60
31
  first: Mark | undefined,
61
32
  items: readonly TranscriptItem[],
62
33
  pendingApprovals: readonly PermissionRequest[],
@@ -68,7 +39,7 @@ function peekContent(
68
39
  body = request ? (
69
40
  <>
70
41
  <div>{request.title ?? 'Permission required'}</div>
71
- <div className='wd-scrub-ex' data-tone='muted'>
42
+ <div className="wd-scrub-ex" data-tone="muted">
72
43
  {`${request.displayName ?? request.toolName}(${toolInputPreview(request.input)})`}
73
44
  </div>
74
45
  </>
@@ -76,17 +47,15 @@ function peekContent(
76
47
  } else if (first && first.kind !== 'recap') {
77
48
  const item = items[first.itemIndex]
78
49
  if (first.kind === 'turn' || first.kind === 'turnFailed') {
79
- // The merged mark's peek carries both halves: the message the turn ended
80
- // on, and the done-line (with its reasons, when it failed).
81
50
  const turn = first.turnIndex === undefined ? undefined : items[first.turnIndex]
82
51
  body = (
83
52
  <>
84
- {item?.kind === 'assistant_text' ? <div className='wd-scrub-ex'>{item.text}</div> : null}
53
+ {item?.kind === 'assistant_text' ? <div className="wd-scrub-ex">{item.text}</div> : null}
85
54
  {turn?.kind === 'turn_result' ? (
86
55
  <>
87
56
  <div data-tone={turn.isError ? 'danger' : 'muted'}>{doneLine(turn)}</div>
88
57
  {turn.errors?.map((message, index) => (
89
- <div key={index} data-tone='danger'>
58
+ <div key={index} data-tone="danger">
90
59
  {message}
91
60
  </div>
92
61
  ))}
@@ -101,18 +70,12 @@ function peekContent(
101
70
  : undefined
102
71
  body = (
103
72
  <>
104
- <div
105
- className='wd-scrub-ex'
106
- data-tone={
107
- first.kind === 'error' || first.kind === 'toolFailed' ? 'danger' : undefined
108
- }>
109
- {first.kind === 'user' ? <span data-tone='muted'>{'❯ '}</span> : null}
73
+ <div className="wd-scrub-ex" data-tone={first.kind === 'error' || first.kind === 'toolFailed' ? 'danger' : undefined}>
74
+ {first.kind === 'user' ? <span data-tone="muted">{'❯ '}</span> : null}
110
75
  {excerpt(item)}
111
76
  </div>
112
- {/* Which tool failed is rarely the question — the first non-blank
113
- line of what it said back is the thing worth peeking at. */}
114
77
  {failure ? (
115
- <div className='wd-scrub-ex' data-tone='danger'>
78
+ <div className="wd-scrub-ex" data-tone="danger">
116
79
  {failure}
117
80
  </div>
118
81
  ) : null}
@@ -122,7 +85,7 @@ function peekContent(
122
85
  }
123
86
  return (
124
87
  <>
125
- <div data-tone='muted'>
88
+ <div data-tone="muted">
126
89
  {KIND_NAME[first?.kind ?? cluster.kind]}
127
90
  {more}
128
91
  </div>
@@ -144,60 +107,57 @@ export function Scrubber({
144
107
  const railRef = useRef<HTMLDivElement | null>(null)
145
108
  const peekRef = useRef<HTMLDivElement | null>(null)
146
109
  const [railH, setRailH] = useState(0)
147
- const [peek, setPeek] = useState<{ cluster: Cluster; mark: Mark | undefined; y: number } | null>(
148
- null,
149
- )
110
+ const [peek, setPeek] = useState<{ cluster: Cluster; mark: Mark | undefined; y: number } | null>(null)
150
111
 
151
112
  useEffect(() => {
152
113
  const element = railRef.current
153
- if (!element) return
114
+ if (!element) {
115
+ return
116
+ }
154
117
  const observer = new ResizeObserver(() => setRailH(element.clientHeight))
155
118
  observer.observe(element)
156
119
  setRailH(element.clientHeight)
157
120
  return () => observer.disconnect()
158
121
  }, [])
159
122
 
160
- // A peek is a snapshot of the cluster it was opened on; if the transcript
161
- // changes underneath (a fixture/session swap, a burst of new items), drop it
162
- // rather than describe items that no longer exist.
163
123
  useEffect(() => {
164
124
  setPeek(null)
165
125
  }, [items])
166
126
 
167
- // The peek can be taller than the space beside its mark — clamp it into the
168
- // rail after it has a measured height.
169
127
  useLayoutEffect(() => {
170
128
  const element = peekRef.current
171
- if (!element || !peek) return
129
+ if (!element || !peek) {
130
+ return
131
+ }
172
132
  const height = element.offsetHeight
173
133
  const railHeight = railRef.current?.clientHeight ?? 0
174
134
  element.style.top = `${Math.max(4, Math.min(railHeight - height - 4, peek.y - height / 2))}px`
175
135
  }, [peek])
176
136
 
177
137
  const clusters = useMemo(() => {
178
- if (railH <= 0) return []
179
- const built = clusterMarks(
180
- buildMarks(items, { frameParentId, bookmarks, recapItemIndex }),
181
- railH,
182
- items.length,
183
- )
184
- if (pendingApprovals.length > 0) built.push(approvalCluster(railH))
138
+ if (railH <= 0) {
139
+ return []
140
+ }
141
+ const built = clusterMarks(buildMarks(items, { frameParentId, bookmarks, recapItemIndex }), railH, items.length)
142
+ if (pendingApprovals.length > 0) {
143
+ built.push(approvalCluster(railH))
144
+ }
185
145
  return built
186
146
  }, [items, frameParentId, bookmarks, recapItemIndex, pendingApprovals, railH])
187
147
 
188
- /** A pointer's y in rail space. */
189
- const railY = (clientY: number): number =>
190
- clientY - (railRef.current?.getBoundingClientRect().top ?? 0)
148
+ const railY = (clientY: number): number => clientY - (railRef.current?.getBoundingClientRect().top ?? 0)
191
149
 
192
150
  const activate = (cluster: Cluster, clientY: number) => {
193
151
  if (cluster.kind === 'approval' && cluster.marks.length === 0) {
194
- // The approval prompt renders below the transcript — the closest an
195
- // item jump can take the reader is the tail.
196
- if (items.length > 0) onJumpToItem?.(items.length - 1)
152
+ if (items.length > 0) {
153
+ onJumpToItem?.(items.length - 1)
154
+ }
197
155
  return
198
156
  }
199
157
  const mark = nearestMember(cluster, railY(clientY))
200
- if (mark) onJumpToItem?.(mark.itemIndex)
158
+ if (mark) {
159
+ onJumpToItem?.(mark.itemIndex)
160
+ }
201
161
  }
202
162
 
203
163
  const showPeek = (cluster: Cluster, clientY: number) => {
@@ -210,19 +170,12 @@ export function Scrubber({
210
170
  )
211
171
  }
212
172
 
213
- // Paint either way: the rail duplicates information the transcript itself
214
- // carries, and interactive it is a pointer affordance rather than the
215
- // scroll surface — the native scrollbar stays the accessible one.
216
173
  return (
217
- <div
218
- ref={railRef}
219
- className={cn('wd-scrubber', className)}
220
- data-interactive={interactive || undefined}
221
- aria-hidden>
174
+ <div ref={railRef} className={cn('wd-scrubber', className)} data-interactive={interactive || undefined} aria-hidden>
222
175
  {clusters.map((cluster, index) => (
223
176
  <div
224
177
  key={index}
225
- className='wd-scrub-mark'
178
+ className="wd-scrub-mark"
226
179
  data-lane={cluster.lane}
227
180
  data-kind={cluster.kind}
228
181
  style={{ top: cluster.y, height: cluster.h }}
@@ -230,8 +183,6 @@ export function Scrubber({
230
183
  ? {
231
184
  onClick: (event) => activate(cluster, event.clientY),
232
185
  onPointerEnter: (event) => showPeek(cluster, event.clientY),
233
- // A chain-merged bar can span the rail; sliding along it
234
- // retargets the peek to the member under the pointer.
235
186
  onPointerMove: (event) => showPeek(cluster, event.clientY),
236
187
  onPointerLeave: () => setPeek(null),
237
188
  }
@@ -239,7 +190,7 @@ export function Scrubber({
239
190
  />
240
191
  ))}
241
192
  {peek ? (
242
- <div ref={peekRef} className='wd-scrub-peek' style={{ top: peek.y }}>
193
+ <div ref={peekRef} className="wd-scrub-peek" style={{ top: peek.y }}>
243
194
  {peekContent(peek.cluster, peek.mark, items, pendingApprovals)}
244
195
  </div>
245
196
  ) : null}