@workerdeck/ui 2.7.2 → 2.9.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 (71) hide show
  1. package/README.md +30 -30
  2. package/build/{SessionPanel-B5FfiBxE.mjs → SessionPanel-BClL7zUS.mjs} +694 -413
  3. package/build/SessionPanel-BClL7zUS.mjs.map +1 -0
  4. package/build/{SessionPanel-_lQLhWOw.d.mts → SessionPanel-BVbau2eR.d.mts} +68 -4
  5. package/build/format.mjs +1 -1
  6. package/build/index.d.mts +22 -15
  7. package/build/index.mjs +6 -6
  8. package/build/index.mjs.map +1 -1
  9. package/build/scoped.css +26 -2
  10. package/build/{status-BO9wJloK.mjs → status-kmsBhuFG.mjs} +3 -3
  11. package/build/status-kmsBhuFG.mjs.map +1 -0
  12. package/build/workspace.d.mts +3 -2
  13. package/build/workspace.mjs +7 -8
  14. package/build/workspace.mjs.map +1 -1
  15. package/package.json +18 -18
  16. package/src/components/agent/Composer.tsx +86 -84
  17. package/src/components/agent/ContextDialog.tsx +1 -1
  18. package/src/components/agent/EntryIcon.tsx +1 -1
  19. package/src/components/agent/FileTree.tsx +1 -1
  20. package/src/components/agent/FileViewer.tsx +1 -1
  21. package/src/components/agent/HostFilesDialog.tsx +2 -2
  22. package/src/components/agent/PermissionModeSelect.tsx +2 -2
  23. package/src/components/agent/PermissionPrompt.tsx +2 -2
  24. package/src/components/agent/SessionBrowser.tsx +1 -1
  25. package/src/components/agent/SessionItem.tsx +4 -2
  26. package/src/components/agent/SessionPanel.tsx +306 -226
  27. package/src/components/agent/SessionWorkspace.tsx +3 -0
  28. package/src/components/agent/SkillsDialog.tsx +3 -3
  29. package/src/components/agent/StatusBar.tsx +1 -1
  30. package/src/components/agent/TaskList.tsx +2 -2
  31. package/src/components/agent/TranscriptRows.tsx +1 -1
  32. package/src/components/agent/UsageDialog.tsx +1 -1
  33. package/src/components/agent/UsageMeters.tsx +17 -4
  34. package/src/components/agent/composer-commands.ts +142 -0
  35. package/src/components/agent/use-path-links.ts +2 -7
  36. package/src/components/agent/use-subagent-frame.ts +2 -2
  37. package/src/components/agent/use-transcript-jumps.ts +2 -2
  38. package/src/components/prompt-area/clipboard-helpers.ts +2 -2
  39. package/src/components/prompt-area/cursor-helpers.ts +7 -7
  40. package/src/components/prompt-area/dom-helpers.ts +8 -8
  41. package/src/components/prompt-area/html-to-markdown.ts +2 -2
  42. package/src/components/prompt-area/prompt-area-engine.ts +4 -4
  43. package/src/components/prompt-area/prompt-area-list-ops.ts +10 -10
  44. package/src/components/prompt-area/prompt-area.tsx +2 -2
  45. package/src/components/prompt-area/trigger-presets.ts +6 -6
  46. package/src/components/prompt-area/types.ts +6 -6
  47. package/src/components/prompt-area/use-chip-editing.ts +8 -8
  48. package/src/components/prompt-area/use-markdown-mode.ts +3 -3
  49. package/src/components/prompt-area/use-prompt-area-events.ts +3 -3
  50. package/src/components/prompt-area/use-prompt-area-keydown.ts +3 -3
  51. package/src/components/prompt-area/use-prompt-area-state.ts +4 -4
  52. package/src/components/prompt-area/use-prompt-area.ts +5 -5
  53. package/src/components/prompt-area/use-trigger-search.ts +1 -1
  54. package/src/components/terminal/PermissionPrompt.tsx +4 -4
  55. package/src/components/terminal/affordances.tsx +1 -1
  56. package/src/components/terminal/file-link.tsx +20 -0
  57. package/src/components/terminal/height.ts +2 -2
  58. package/src/components/terminal/items.tsx +2 -2
  59. package/src/components/terminal/markdown.tsx +31 -5
  60. package/src/components/terminal/scrubber.tsx +1 -1
  61. package/src/components/terminal/todos.ts +1 -1
  62. package/src/components/ui/Splitter.tsx +1 -1
  63. package/src/index.ts +11 -1
  64. package/src/lib/context-note.ts +3 -3
  65. package/src/lib/file-link.ts +70 -0
  66. package/src/lib/format.ts +2 -2
  67. package/src/styles/scoped.entry.css +2 -2
  68. package/src/styles/terminal.css +94 -79
  69. package/src/styles/theme.css +39 -39
  70. package/build/SessionPanel-B5FfiBxE.mjs.map +0 -1
  71. package/build/status-BO9wJloK.mjs.map +0 -1
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Styled agent-control component library for WorkerDeck hosts: `SessionPanel` (status bar +
4
4
  streaming transcript + tool-call cards + permission prompts + composer with attachments and
5
- `@file` / `/command` completion, plus the panels behind it — session info, context, plan usage,
6
- MCP servers, project files), `SessionWorkspace` (a VS Code-shaped layout *around* that panel —
7
- file tree, editor tabs, Monaco — at `@workerdeck/ui/workspace`, so Monaco stays out of hosts that
5
+ `@file` / `/command` completion, plus the panels behind it - session info, context, plan usage,
6
+ MCP servers, project files), `SessionWorkspace` (a VS Code-shaped layout *around* that panel -
7
+ file tree, editor tabs, Monaco - at `@workerdeck/ui/workspace`, so Monaco stays out of hosts that
8
8
  don't want it), `SessionList`, and the underlying primitives (Button, Badge,
9
9
  Card, Select, Dialog, AlertDialog, …). Built on **Tailwind v4 + Base UI + cva**, themed by CSS
10
10
  tokens with light/dark via `<html data-theme>`.
@@ -18,7 +18,7 @@ this package is the styling opinion on top.
18
18
 
19
19
  ## Consumer wiring (Tailwind v4)
20
20
 
21
- The package ships **source styles + source classnames** — your app's Tailwind build compiles
21
+ The package ships **source styles + source classnames** - your app's Tailwind build compiles
22
22
  them. Three steps:
23
23
 
24
24
  1. Your Tailwind entry CSS:
@@ -29,7 +29,7 @@ them. Three steps:
29
29
  /* Let Tailwind see this package's classnames (node_modules is not scanned by default). */
30
30
  @source '../node_modules/@workerdeck/ui';
31
31
  /* streamdown (the markdown renderer) also styles itself with Tailwind classes, split across
32
- * chunk files — scan its whole dist dir. With npm/yarn it's hoisted to node_modules/streamdown;
32
+ * chunk files - scan its whole dist dir. With npm/yarn it's hoisted to node_modules/streamdown;
33
33
  * with pnpm it's nested under this package: */
34
34
  @source '../node_modules/@workerdeck/ui/node_modules/streamdown/dist';
35
35
  ```
@@ -67,11 +67,11 @@ const client = new WorkerDeckClient({ baseUrl: `${location.origin}/v1` })
67
67
 
68
68
  ### The workspace (optional)
69
69
 
70
- `SessionWorkspace` wraps that same panel in a three-region layout — project tree on the left,
71
- open files above, agent below — and is **strictly additive**: `SessionPanel` is untouched and
70
+ `SessionWorkspace` wraps that same panel in a three-region layout - project tree on the left,
71
+ open files above, agent below - and is **strictly additive**: `SessionPanel` is untouched and
72
72
  still complete on its own, so pick whichever fits. An app with its own file tree keeps the panel.
73
73
 
74
- It lives at its **own entry point**, and Monaco is an **optional peer dependency** — install it
74
+ It lives at its **own entry point**, and Monaco is an **optional peer dependency** - install it
75
75
  alongside `@workerdeck/ui` if you want the workspace, and skip both if you don't:
76
76
 
77
77
  ```sh
@@ -86,12 +86,12 @@ import { SessionWorkspace } from '@workerdeck/ui/workspace'
86
86
 
87
87
  It needs the gateway's host-filesystem routes (`hostFiles` in the server config); without them
88
88
  the rail is simply absent and you get the panel. Editing additionally needs `hostFiles.write`,
89
- which is a separate opt-in and defaults **off** — the editor reads `/fs/roots` and renders
89
+ which is a separate opt-in and defaults **off** - the editor reads `/fs/roots` and renders
90
90
  read-only when it's not enabled. Saves are conditional on the hash the tab read, so a save that
91
91
  collides with the agent's own edit is refused (409) and offered as a choice rather than silently
92
92
  winning.
93
93
 
94
- **If you bundle with Vite, you need one line** — the workspace uses Monaco, which reaches its
94
+ **If you bundle with Vite, you need one line** - the workspace uses Monaco, which reaches its
95
95
  web workers with `new Worker(new URL(…, import.meta.url))`. Rollup resolves that at build time,
96
96
  but Vite's dev dep-optimizer rewrites the package into `.vite/deps/` and breaks the relative URL;
97
97
  Monaco then *silently* runs worker code on the main thread:
@@ -103,19 +103,19 @@ export default defineConfig({ optimizeDeps: { exclude: ['monaco-editor'] } })
103
103
 
104
104
  Monaco is loaded through a dynamic `import()`, so it costs nothing at runtime until a file is
105
105
  opened. Our own dashboard additionally aliases away Monaco's four worker-backed language services
106
- (TypeScript/JSON/CSS/HTML) — 8.8MB of build output for IntelliSense and schema validation, with
107
- syntax highlighting unaffected — see `packages/web/vite.config.ts` if you want the same trade.
106
+ (TypeScript/JSON/CSS/HTML) - 8.8MB of build output for IntelliSense and schema validation, with
107
+ syntax highlighting unaffected - see `packages/web/vite.config.ts` if you want the same trade.
108
108
 
109
109
  None of this reaches you through the root entry. That separation is why the workspace has its own
110
110
  subpath rather than living in `@workerdeck/ui`: tree-shaking alone is not enough. Rollup does drop
111
111
  `CodeEditor` from a `SessionPanel`-only bundle, but Vite resolves Monaco's worker URLs while
112
- *transforming* the module — before tree-shaking runs — and emits ~9MB of worker assets that are
112
+ *transforming* the module - before tree-shaking runs - and emits ~9MB of worker assets that are
113
113
  never retracted. Keeping Monaco unreachable from the root entry is what actually prevents that.
114
114
 
115
115
  Every component takes `className` and carries `data-slot` attributes for targeted overrides.
116
116
 
117
117
  `SessionPanel` is self-sufficient for errors: a `protocol_error` (the gateway or CLI refusing a
118
- command — a `set_permission_mode` a provider engine can't run, say) renders as a dismissible strip
118
+ command - a `set_permission_mode` a provider engine can't run, say) renders as a dismissible strip
119
119
  inside the panel. You do **not** need to mount anything else to see them. `Toaster` is exported
120
120
  for your own `toast()` calls; the panel never depends on it, because an error channel a host can
121
121
  lose by forgetting a second mount isn't one.
@@ -124,17 +124,17 @@ lose by forgetting a second mount isn't one.
124
124
 
125
125
  - Token names are unprefixed (`--bg`, `--accent`, `--primary`, …) and `theme.css` styles
126
126
  `body`/focus rings. Embedding into an app with its own conflicting design system may need
127
- scoping — file an issue with your case.
127
+ scoping - file an issue with your case.
128
128
  - Dark mode is driven **only** by `[data-theme='dark']` on the root element (the Tailwind
129
129
  `dark:` variant is remapped to it); `prefers-color-scheme` is not consulted at CSS level.
130
130
  - The session surface centers its content at `--wd-transcript-max-width` (default `48rem`).
131
131
  Embedders in narrow docks (a VS Code bottom panel, a drawer) set it to `100%` for
132
132
  edge-to-edge content. Other geometry tokens: `--wd-status-bar-height`, `--wd-composer-padding`,
133
133
  `--wd-transcript-row-gap`. Every component also carries `data-slot` attributes for targeted
134
- overrides — e.g. `[data-slot='composer-hint']` is the "Enter to send" line, which a host
134
+ overrides - e.g. `[data-slot='composer-hint']` is the "Enter to send" line, which a host
135
135
  whose vertical space is precious can `display: none`.
136
136
  - `SessionPanel` can hand its dialog surfaces to the embedder: `panelSurface: 'external'`
137
- renders no dialogs and no `⋯` menu — every affordance that would open one calls
137
+ renders no dialogs and no `⋯` menu - every affordance that would open one calls
138
138
  `onOpenPanel(panel)` instead, and `onVitals` streams the live readings (status, context,
139
139
  rate limits, capabilities) those external surfaces need, so host chrome never has to
140
140
  attach a second time (the tool bridge asks the first attached client). The VS Code
@@ -142,13 +142,13 @@ lose by forgetting a second mount isn't one.
142
142
  - `statusSurface: 'external'` is the separate opt-out for the **status bar itself**, for a host
143
143
  whose chrome already has a status line (VS Code's window bar): the panel draws no bar and the
144
144
  readings leave through `onVitals` as before. The two flags are independent, with one coupling
145
- — the `⋯` menu lives in the bar's trailing slot, so `statusSurface: 'external'` alongside
145
+ - the `⋯` menu lives in the bar's trailing slot, so `statusSurface: 'external'` alongside
146
146
  `panelSurface: 'internal'` must pass a **function** `header` for the menu to land in.
147
147
  Whatever draws the bar owes the panel's own rule: `SessionVitals.connection` wins the status
148
148
  slot whenever it isn't `'live'`, because a session status held over a dropped socket is a
149
149
  stale reading presented as a current one.
150
150
  - `@workerdeck/ui/format` is a third entry point carrying the pure formatters (`45.2k`,
151
- `2h 10m`) with no React in the graph — for a host that renders session readings outside
151
+ `2h 10m`) with no React in the graph - for a host that renders session readings outside
152
152
  React, like an extension host drawing them into a window status bar, and wants them spelled
153
153
  exactly as the panel spells them.
154
154
 
@@ -167,47 +167,47 @@ lose by forgetting a second mount isn't one.
167
167
  become unreachable.
168
168
  - **The terminal theme (`variant: 'terminal'`) is a renderer, not a second set of branches.** It
169
169
  draws every row itself from `components/terminal/` and the shell mounts it *instead* of the
170
- components under `components/agent/`, so nothing in there asks which variant it is in — if it is
170
+ components under `components/agent/`, so nothing in there asks which variant it is in - if it is
171
171
  drawing at all, it is drawing cards.
172
172
  - **The panel mounts three terminal surfaces, and they must agree.** The transcript, the pending
173
173
  prompts and the composer each establish their own cell, because each sits in a different part of
174
174
  the flex column. `terminalMetrics` is one prop for exactly that reason: hand two of them
175
175
  different numbers and the caret lands on a different column from the text above it.
176
176
  - **`transcriptDensity` and `transcriptFont` reach `cards` only.** A terminal has one line height
177
- and is monospace by construction. Under `terminal` both are inert rather than broken — a host
177
+ and is monospace by construction. Under `terminal` both are inert rather than broken - a host
178
178
  offering them as settings should say so, or hide them (the dashboard hides them).
179
179
  - **`unseen` is catch-up mode's whole switch.** Passing a watermark draws the boundary, fades what
180
180
  was already read and offers the "N new rows since you were last here" bar; passing `undefined`
181
181
  draws none of it. The mark is frozen at mount, so it never walks down the transcript under the
182
- reader. A host offering this as a setting owns the storage — the panel has no preference.
182
+ reader. A host offering this as a setting owns the storage - the panel has no preference.
183
183
  - **`scrubber` and `stickyPrompt` reach `terminal` only**, and by construction rather than policy.
184
- Both rest on the theme's premise — one line height and one cell make a row's height computable —
184
+ Both rest on the theme's premise - one line height and one cell make a row's height computable -
185
185
  and under `cards` the flags are inert.
186
186
  - **`stickyPrompt` pins the real row, not a copy of it.** The prompt at the top of the scroller is
187
187
  the same DOM node the transcript already rendered, with its transform clamped to the scroll
188
188
  offset. A duplicate header cannot be made to line up with the rows beneath it, which is why this
189
- is worth the machinery (`rangeExtractor` to keep it mounted, a manual push-off — `position:
189
+ is worth the machinery (`rangeExtractor` to keep it mounted, a manual push-off - `position:
190
190
  sticky` does nothing on an absolutely positioned element).
191
191
  - **Terminal row heights are computed, and the cache invalidates by object identity.** The height
192
192
  calculator keys a `WeakMap<TranscriptItem, …>` per (width, cell) epoch, which works only because
193
193
  the react reducer replaces item objects on every mutation. Mutate a `TranscriptItem` in place and
194
194
  you will serve a stale height for a row that has changed. The epoch is rebuilt from a
195
- `ResizeObserver` on the **content** element, not the scroller — the panel can resize without the
195
+ `ResizeObserver` on the **content** element, not the scroller - the panel can resize without the
196
196
  wrap width moving, since the content column caps at 48rem.
197
197
  - **An item index is not a virtual row index.** `terminalBlocks` folds consecutive tool calls into
198
198
  one row and the catch-up recap splices another, so `scrollToIndex(itemIndex)` lands off by the
199
199
  fold on any transcript that has either. Go through `rowIndexForItem`.
200
200
  - **A row's rendered strings are also its height.** `height.ts` predicts each row's pixel height
201
201
  with no DOM, so anything the terminal renderer *writes* must come from the module both sides
202
- import — `tool-run.ts` for a folded run's summary line, `result-preview.ts` for a collapsed tool
202
+ import - `tool-run.ts` for a folded run's summary line, `result-preview.ts` for a collapsed tool
203
203
  result and its `… +N` label. Re-spelling either one in the renderer alone desynchronises
204
204
  `estimateSize` and the transcript grows a phantom scrollable tail. `dev/height-audit.ts` is the
205
205
  gate; it measures against real browser layout, which no jsdom test can do.
206
206
  - **`pnpm test` here covers the pure modules and nothing else.** That is the split, not an
207
207
  omission: which rows exist, what a folded run's line says, how much of a result a collapsed row
208
208
  keeps and which marks the rail paints are all string-and-array contracts with no DOM in them,
209
- and they are where this package's bugs have actually shipped. Geometry — does the rendered row
210
- come out the height the calculator predicted — is the browser audit above. A test in `test/`
209
+ and they are where this package's bugs have actually shipped. Geometry - does the rendered row
210
+ come out the height the calculator predicted - is the browser audit above. A test in `test/`
211
211
  that wants a DOM belongs in `dev/` instead. `buildClusters` and `railScale` are exported from
212
212
  `scrubber.tsx` for the test alone and are deliberately absent from `index.ts`.
213
213
  - **`affordances={false}` must leave a way to scroll.** The scrubber replaces the native scrollbar
@@ -217,7 +217,7 @@ lose by forgetting a second mount isn't one.
217
217
  - **Its two metrics must be whole pixels.** `--term-font-size` and `--term-line` are the character
218
218
  cell; a line height of `1.5 x 13px` is 19.5px and puts every other row on a half-pixel, which
219
219
  softens the text and shows a seam through the diff bands. Horizontal measures are `ch` and
220
- vertical measures are whole multiples of `--term-line` — nothing in the theme is a px constant.
220
+ vertical measures are whole multiples of `--term-line` - nothing in the theme is a px constant.
221
221
  - **`--term-bleed` is a contract, not a decoration.** A full-bleed band (a diff hunk, a user
222
222
  prompt, a hover fill) cancels it with matched negative margin and padding, so it must equal the
223
223
  scroller's own horizontal padding. `TerminalSurface` sets both; a host that pads the scroller
@@ -228,7 +228,7 @@ lose by forgetting a second mount isn't one.
228
228
  the edit has not happened yet) renders *without* a number column rather than a column of zeroes.
229
229
  - **Affordances cost no layout, which is what makes `false` a real option.** The hover fill is a
230
230
  background and the copy actions are absolutely-positioned overlays one line tall, so
231
- `affordances={false}` changes no glyph's position — it is the pure article, not a degraded mode.
231
+ `affordances={false}` changes no glyph's position - it is the pure article, not a degraded mode.
232
232
  - **Keep `monaco-editor` unreachable from `src/index.ts`.** Tree-shaking does not cover it: Vite
233
233
  resolves Monaco's worker `new URL(...)`s while *transforming* the module, before shaking, and
234
234
  emits megabytes of worker assets it never retracts. That is what the `/workspace` entry point is