@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.
- package/README.md +30 -30
- package/build/{SessionPanel-B5FfiBxE.mjs → SessionPanel-BClL7zUS.mjs} +694 -413
- package/build/SessionPanel-BClL7zUS.mjs.map +1 -0
- package/build/{SessionPanel-_lQLhWOw.d.mts → SessionPanel-BVbau2eR.d.mts} +68 -4
- package/build/format.mjs +1 -1
- package/build/index.d.mts +22 -15
- package/build/index.mjs +6 -6
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +26 -2
- package/build/{status-BO9wJloK.mjs → status-kmsBhuFG.mjs} +3 -3
- package/build/status-kmsBhuFG.mjs.map +1 -0
- package/build/workspace.d.mts +3 -2
- package/build/workspace.mjs +7 -8
- package/build/workspace.mjs.map +1 -1
- package/package.json +18 -18
- package/src/components/agent/Composer.tsx +86 -84
- package/src/components/agent/ContextDialog.tsx +1 -1
- package/src/components/agent/EntryIcon.tsx +1 -1
- package/src/components/agent/FileTree.tsx +1 -1
- package/src/components/agent/FileViewer.tsx +1 -1
- package/src/components/agent/HostFilesDialog.tsx +2 -2
- package/src/components/agent/PermissionModeSelect.tsx +2 -2
- package/src/components/agent/PermissionPrompt.tsx +2 -2
- package/src/components/agent/SessionBrowser.tsx +1 -1
- package/src/components/agent/SessionItem.tsx +4 -2
- package/src/components/agent/SessionPanel.tsx +306 -226
- package/src/components/agent/SessionWorkspace.tsx +3 -0
- package/src/components/agent/SkillsDialog.tsx +3 -3
- package/src/components/agent/StatusBar.tsx +1 -1
- package/src/components/agent/TaskList.tsx +2 -2
- package/src/components/agent/TranscriptRows.tsx +1 -1
- package/src/components/agent/UsageDialog.tsx +1 -1
- package/src/components/agent/UsageMeters.tsx +17 -4
- package/src/components/agent/composer-commands.ts +142 -0
- package/src/components/agent/use-path-links.ts +2 -7
- package/src/components/agent/use-subagent-frame.ts +2 -2
- package/src/components/agent/use-transcript-jumps.ts +2 -2
- package/src/components/prompt-area/clipboard-helpers.ts +2 -2
- package/src/components/prompt-area/cursor-helpers.ts +7 -7
- package/src/components/prompt-area/dom-helpers.ts +8 -8
- package/src/components/prompt-area/html-to-markdown.ts +2 -2
- package/src/components/prompt-area/prompt-area-engine.ts +4 -4
- package/src/components/prompt-area/prompt-area-list-ops.ts +10 -10
- package/src/components/prompt-area/prompt-area.tsx +2 -2
- package/src/components/prompt-area/trigger-presets.ts +6 -6
- package/src/components/prompt-area/types.ts +6 -6
- package/src/components/prompt-area/use-chip-editing.ts +8 -8
- package/src/components/prompt-area/use-markdown-mode.ts +3 -3
- package/src/components/prompt-area/use-prompt-area-events.ts +3 -3
- package/src/components/prompt-area/use-prompt-area-keydown.ts +3 -3
- package/src/components/prompt-area/use-prompt-area-state.ts +4 -4
- package/src/components/prompt-area/use-prompt-area.ts +5 -5
- package/src/components/prompt-area/use-trigger-search.ts +1 -1
- package/src/components/terminal/PermissionPrompt.tsx +4 -4
- package/src/components/terminal/affordances.tsx +1 -1
- package/src/components/terminal/file-link.tsx +20 -0
- package/src/components/terminal/height.ts +2 -2
- package/src/components/terminal/items.tsx +2 -2
- package/src/components/terminal/markdown.tsx +31 -5
- package/src/components/terminal/scrubber.tsx +1 -1
- package/src/components/terminal/todos.ts +1 -1
- package/src/components/ui/Splitter.tsx +1 -1
- package/src/index.ts +11 -1
- package/src/lib/context-note.ts +3 -3
- package/src/lib/file-link.ts +70 -0
- package/src/lib/format.ts +2 -2
- package/src/styles/scoped.entry.css +2 -2
- package/src/styles/terminal.css +94 -79
- package/src/styles/theme.css +39 -39
- package/build/SessionPanel-B5FfiBxE.mjs.map +0 -1
- 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
|
|
6
|
-
MCP servers, project files), `SessionWorkspace` (a VS Code-shaped layout *around* that panel
|
|
7
|
-
file tree, editor tabs, Monaco
|
|
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**
|
|
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
|
|
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
|
|
71
|
-
open files above, agent below
|
|
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**
|
|
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**
|
|
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**
|
|
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)
|
|
107
|
-
syntax highlighting unaffected
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
210
|
-
come out the height the calculator predicted
|
|
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`
|
|
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
|
|
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
|