@workerdeck/ui 0.23.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-DUt2VzXG.mjs → SessionPanel-DDgBkHsi.mjs} +2151 -3357
  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 +15 -23
  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 +5 -126
  14. package/build/workspace.mjs +11 -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 +3 -9
  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 +315 -996
  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 +104 -219
  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 +96 -32
  141. package/src/workspace.ts +1 -24
  142. package/build/SessionPanel-BobynUo4.d.mts +0 -669
  143. package/build/SessionPanel-DUt2VzXG.mjs.map +0 -1
  144. package/build/format-ljc3lKpA.d.mts +0 -59
  145. package/build/status-BE-zg88x.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workerdeck/ui",
3
- "version": "0.23.0",
3
+ "version": "1.0.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.5.0",
40
40
  "tailwind-merge": "^3.6.0",
41
41
  "use-stick-to-bottom": "^1.1.6",
42
- "@workerdeck/client": "0.23.0",
43
- "@workerdeck/protocol": "0.23.0",
44
- "@workerdeck/react": "0.23.0"
42
+ "@workerdeck/client": "1.0.0",
43
+ "@workerdeck/react": "1.0.0",
44
+ "@workerdeck/protocol": "1.0.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "monaco-editor": "^0.56.0",
@@ -4,67 +4,30 @@ import { cn } from '../../lib/utils.ts'
4
4
  import { Spinner } from '../ui/Spinner.tsx'
5
5
 
6
6
  export interface CodeEditorProps {
7
- /** Absolute path — decides the language and identifies the model. */
8
7
  path: string
9
- /** Text to show. Applied to the model when it differs from what is on screen,
10
- * so an external reload lands without fighting the user's cursor. */
11
8
  value: string
12
9
  onChange?: (value: string) => void
13
- /** Ctrl/Cmd+S. Wired inside Monaco because the editor swallows keydown. */
14
10
  onSave?: () => void
15
11
  readOnly?: boolean
16
12
  className?: string
17
13
  }
18
14
 
19
- /**
20
- * Monaco — VS Code's own editor — behind a small React surface.
21
- *
22
- * Two deliberate choices, both about `@workerdeck/ui` being a **published
23
- * library** rather than an app:
24
- *
25
- * 1. **Loaded on demand.** The `import()` is inside an effect, so Monaco is a
26
- * separate chunk that arrives when someone first opens a file. A dashboard
27
- * that never opens one never pays for it, and Monaco's ~90 language grammars
28
- * are themselves lazy (each `registerLanguage` carries an `import()` loader),
29
- * so opening a `.ts` file fetches the TypeScript grammar and nothing else.
30
- * 2. **No `MonacoEnvironment` is configured here, and none is needed.** Workers
31
- * in a library become every embedder's bootstrapping problem, and
32
- * `packages/web` ships prebuilt static files at a domain root, which is
33
- * exactly where hardcoded worker URLs break. The editor is configured so it
34
- * never asks for one: `wordBasedSuggestions` and `quickSuggestions` off,
35
- * no diff editor. A host that wants the worker-backed language services
36
- * (TypeScript IntelliSense, JSON schema validation) sets `MonacoEnvironment`
37
- * itself before the first file is opened — Monaco is a singleton and nothing
38
- * here fights that.
39
- *
40
- * One model per path, kept across tab switches, so undo history and view state
41
- * survive clicking away and back — which is most of what makes tabs feel like
42
- * tabs rather than like re-opening a file.
43
- */
44
- export function CodeEditor({
45
- path,
46
- value,
47
- onChange,
48
- onSave,
49
- readOnly,
50
- className,
51
- }: CodeEditorProps) {
15
+ export function CodeEditor({ path, value, onChange, onSave, readOnly, className }: CodeEditorProps) {
52
16
  const host = useRef<HTMLDivElement>(null)
53
17
  const editor = useRef<Monaco.editor.IStandaloneCodeEditor | null>(null)
54
18
  const monaco = useRef<typeof Monaco | null>(null)
55
19
  const [ready, setReady] = useState(false)
56
20
  const theme = useDocumentTheme()
57
21
 
58
- // Callbacks through refs: they change identity every render, and re-creating
59
- // the editor (or re-registering its listeners) on each one would drop the
60
- // cursor mid-keystroke.
61
22
  const handlers = useRef({ onChange, onSave })
62
23
  handlers.current = { onChange, onSave }
63
24
 
64
25
  useEffect(() => {
65
26
  let disposed = false
66
27
  void loadMonaco().then((api) => {
67
- if (disposed || !host.current) return
28
+ if (disposed || !host.current) {
29
+ return
30
+ }
68
31
  monaco.current = api
69
32
  const instance = api.editor.create(host.current, {
70
33
  value,
@@ -76,14 +39,11 @@ export function CodeEditor({
76
39
  scrollBeyondLastLine: false,
77
40
  fontSize: 12,
78
41
  lineHeight: 20,
79
- fontFamily:
80
- 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace',
42
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace',
81
43
  renderLineHighlight: 'line',
82
44
  smoothScrolling: true,
83
45
  padding: { top: 8, bottom: 8 },
84
46
  scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },
85
- // Without a worker there is no word-based suggestion provider to ask, so
86
- // asking would surface an empty popup on every identifier.
87
47
  wordBasedSuggestions: 'off',
88
48
  quickSuggestions: false,
89
49
  })
@@ -98,74 +58,62 @@ export function CodeEditor({
98
58
  })
99
59
  return () => {
100
60
  disposed = true
101
- // Dispose the editor but NOT its model the model is keyed by path and
102
- // outlives this mount so that reopening a tab restores its undo stack.
61
+ // The editor, never its models: a model is keyed by path and outlives this mount, which is what restores a reopened tab's undo stack.
103
62
  editor.current?.dispose()
104
63
  editor.current = null
105
64
  setReady(false)
106
65
  }
107
- // Deliberately created once, with no dependencies. Path, value and readOnly
108
- // are applied by the effects below instead, because re-creating the editor
109
- // to change any of them would lose the cursor, the scroll position and the
110
- // undo history.
111
66
  }, [])
112
67
 
113
- // Swap the model when the focused file changes. One model per path, created
114
- // lazily and kept, so each tab keeps its own undo history and view state.
115
68
  useEffect(() => {
116
69
  const api = monaco.current
117
70
  const instance = editor.current
118
- if (!api || !instance || !ready) return
71
+ if (!api || !instance || !ready) {
72
+ return
73
+ }
119
74
  const uri = api.Uri.parse(`workerdeck://host${path}`)
120
75
  const model = api.editor.getModel(uri) ?? api.editor.createModel(value, languageOf(path), uri)
121
- if (instance.getModel() !== model) instance.setModel(model)
76
+ if (instance.getModel() !== model) {
77
+ instance.setModel(model)
78
+ }
122
79
  }, [path, ready, value])
123
80
 
124
- // Apply an external change — a reload from disk, a revert — without
125
- // disturbing anything when the text already matches, which is the common case
126
- // because most changes to `value` are echoes of the user's own typing.
127
81
  useEffect(() => {
128
82
  const instance = editor.current
129
- if (!instance || !ready) return
83
+ if (!instance || !ready) {
84
+ return
85
+ }
130
86
  const model = instance.getModel()
131
- if (!model || model.getValue() === value) return
132
- // `pushEditOperations` rather than `setValue` so the replacement joins the
133
- // undo stack instead of clearing it.
134
- model.pushEditOperations(
135
- [],
136
- [{ range: model.getFullModelRange(), text: value }],
137
- () => null,
138
- )
87
+ if (!model || model.getValue() === value) {
88
+ return
89
+ }
90
+ model.pushEditOperations([], [{ range: model.getFullModelRange(), text: value }], () => null)
139
91
  }, [value, ready])
140
92
 
141
93
  useEffect(() => {
142
- if (ready) editor.current?.updateOptions({ readOnly })
94
+ if (ready) {
95
+ editor.current?.updateOptions({ readOnly })
96
+ }
143
97
  }, [readOnly, ready])
144
98
 
145
- // The theme is global to Monaco, not per-editor — `setTheme` is on the
146
- // namespace for that reason.
147
99
  useEffect(() => {
148
- if (ready) monaco.current?.editor.setTheme(monacoTheme(theme))
100
+ if (ready) {
101
+ monaco.current?.editor.setTheme(monacoTheme(theme))
102
+ }
149
103
  }, [theme, ready])
150
104
 
151
105
  return (
152
106
  <div className={cn('relative min-h-0 min-w-0 flex-1', className)}>
153
- <div ref={host} className='absolute inset-0' />
107
+ <div ref={host} className="absolute inset-0" />
154
108
  {!ready ? (
155
- <div className='absolute inset-0 flex items-center justify-center bg-bg'>
156
- <Spinner className='size-4 text-fg-4' />
109
+ <div className="absolute inset-0 flex items-center justify-center bg-bg">
110
+ <Spinner className="size-4 text-fg-4" />
157
111
  </div>
158
112
  ) : null}
159
113
  </div>
160
114
  )
161
115
  }
162
116
 
163
- /**
164
- * Follow the `data-theme` the design tokens already swap on, so the editor is
165
- * never the one light rectangle in a dark app (or the reverse). An attribute
166
- * observer rather than a media query: the app has a manual toggle, and the
167
- * attribute is what that toggle writes.
168
- */
169
117
  function useDocumentTheme(): string | null {
170
118
  const [theme, setTheme] = useState<string | null>(() =>
171
119
  typeof document === 'undefined' ? null : document.documentElement.getAttribute('data-theme'),
@@ -181,26 +129,14 @@ function useDocumentTheme(): string | null {
181
129
  return theme
182
130
  }
183
131
 
184
- /** An unset attribute means the host never opted into the token themes, in which
185
- * case dark matches this package's default surface. */
186
132
  function monacoTheme(theme: string | null): string {
187
133
  return theme === 'light' ? 'wd-light' : 'wd-dark'
188
134
  }
189
135
 
190
- /**
191
- * Load Monaco once, register the themes, and hand back the API.
192
- *
193
- * Cached as a promise rather than a value so that several editors mounting in
194
- * the same frame share one load instead of racing three of them.
195
- */
196
136
  let monacoPromise: Promise<typeof Monaco> | undefined
197
137
  function loadMonaco(): Promise<typeof Monaco> {
198
138
  monacoPromise ??= (async () => {
199
139
  const api = await import('monaco-editor')
200
- // Themes that inherit the surrounding surface instead of Monaco's own
201
- // near-black, so the editor does not sit in the layout as a differently
202
- // coloured rectangle. Both are defined; the CSS variable decides nothing
203
- // here, so the panel picks by `prefers-color-scheme` on the document.
204
140
  api.editor.defineTheme('wd-dark', {
205
141
  base: 'vs-dark',
206
142
  inherit: true,
@@ -218,22 +154,16 @@ function loadMonaco(): Promise<typeof Monaco> {
218
154
  return monacoPromise
219
155
  }
220
156
 
221
- /**
222
- * Monaco's language id for a path.
223
- *
224
- * By extension, with a short table for the files that have none — Monaco's own
225
- * registry is keyed on extensions it knows, and an unmatched file falls through
226
- * to `plaintext`, which is the honest answer rather than a guess.
227
- */
228
157
  function languageOf(path: string): string {
229
158
  const name = path.slice(path.lastIndexOf('/') + 1)
230
159
  const byName = FILENAME_LANGUAGES[name.toLowerCase()]
231
- if (byName) return byName
160
+ if (byName) {
161
+ return byName
162
+ }
232
163
  const extension = name.slice(name.lastIndexOf('.') + 1).toLowerCase()
233
164
  return EXTENSION_LANGUAGES[extension] ?? 'plaintext'
234
165
  }
235
166
 
236
- /** Files whose type is their whole name. */
237
167
  const FILENAME_LANGUAGES: Record<string, string> = {
238
168
  dockerfile: 'dockerfile',
239
169
  makefile: 'plaintext',
@@ -241,8 +171,6 @@ const FILENAME_LANGUAGES: Record<string, string> = {
241
171
  '.env': 'shell',
242
172
  }
243
173
 
244
- /** Extension → Monaco language id. Only where the id differs from the extension
245
- * or the extension is ambiguous; everything else Monaco resolves itself. */
246
174
  const EXTENSION_LANGUAGES: Record<string, string> = {
247
175
  ts: 'typescript',
248
176
  tsx: 'typescript',