@workerdeck/ui 0.23.0 → 1.1.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/build/{SessionPanel-DUt2VzXG.mjs → SessionPanel-I5QSLLb0.mjs} +2266 -3459
- package/build/SessionPanel-I5QSLLb0.mjs.map +1 -0
- package/build/SessionPanel-yNO60Tzw.d.mts +151 -0
- package/build/format-C_lpFq0l.d.mts +16 -0
- package/build/format.d.mts +8 -76
- package/build/format.mjs +2 -2
- package/build/index.d.mts +291 -1695
- package/build/index.mjs +6 -301
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +32 -23
- package/build/{status-BE-zg88x.mjs → status-Dm9INVTt.mjs} +17 -88
- package/build/status-Dm9INVTt.mjs.map +1 -0
- package/build/workspace.d.mts +8 -187
- package/build/workspace.mjs +13 -144
- package/build/workspace.mjs.map +1 -1
- package/package.json +24 -24
- package/src/components/agent/CodeEditor.tsx +31 -103
- package/src/components/agent/Composer.tsx +114 -333
- package/src/components/agent/ContextDialog.tsx +20 -41
- package/src/components/agent/ContextRing.tsx +6 -28
- package/src/components/agent/Conversation.tsx +21 -33
- package/src/components/agent/EditorTabs.tsx +39 -67
- package/src/components/agent/EngineIcon.tsx +29 -72
- package/src/components/agent/EntryIcon.tsx +16 -0
- package/src/components/agent/FileCard.tsx +10 -16
- package/src/components/agent/FileTree.tsx +52 -125
- package/src/components/agent/FileViewer.tsx +30 -60
- package/src/components/agent/HostFilesDialog.tsx +40 -69
- package/src/components/agent/Loader.tsx +3 -24
- package/src/components/agent/McpDialog.tsx +64 -137
- package/src/components/agent/Message.tsx +2 -21
- package/src/components/agent/ModelSelect.tsx +33 -62
- package/src/components/agent/PermissionModeSelect.tsx +21 -79
- package/src/components/agent/PermissionPrompt.tsx +59 -63
- package/src/components/agent/ProjectIcon.tsx +228 -93
- package/src/components/agent/PromptTokenText.tsx +10 -16
- package/src/components/agent/QuestionPrompt.tsx +43 -64
- package/src/components/agent/Reasoning.tsx +13 -16
- package/src/components/agent/Response.tsx +3 -9
- package/src/components/agent/Scrubber.tsx +31 -80
- package/src/components/agent/SessionBrowser.tsx +64 -243
- package/src/components/agent/SessionEmptyState.tsx +10 -40
- package/src/components/agent/SessionInfoDialog.tsx +46 -78
- package/src/components/agent/SessionItem.tsx +53 -320
- package/src/components/agent/SessionList.tsx +23 -39
- package/src/components/agent/SessionPanel.tsx +315 -996
- package/src/components/agent/SessionStatusIcon.tsx +14 -28
- package/src/components/agent/SessionSteps.tsx +34 -187
- package/src/components/agent/SessionWorkspace.tsx +104 -219
- package/src/components/agent/SkillsDialog.tsx +35 -80
- package/src/components/agent/StatusBar.tsx +51 -136
- package/src/components/agent/SubagentStrip.tsx +12 -77
- package/src/components/agent/ToolCallCard.tsx +94 -135
- package/src/components/agent/Transcript.tsx +103 -1288
- package/src/components/agent/TranscriptItemView.tsx +155 -0
- package/src/components/agent/TranscriptRows.tsx +418 -0
- package/src/components/agent/UsageDialog.tsx +7 -29
- package/src/components/agent/UsageMeters.tsx +18 -73
- package/src/components/agent/pulse.tsx +9 -32
- package/src/components/agent/scrubber-marks.ts +33 -103
- package/src/components/agent/status.ts +1 -5
- package/src/components/agent/tool-result-fetch.tsx +3 -25
- package/src/components/agent/tool-result-image.tsx +32 -111
- package/src/components/agent/transcript-rows.ts +41 -118
- package/src/components/agent/transcript-variant.tsx +3 -80
- package/src/components/agent/use-height-epoch.ts +10 -24
- package/src/components/agent/use-path-links.ts +45 -63
- package/src/components/agent/use-subagent-frame.ts +106 -0
- package/src/components/agent/use-transcript-jumps.ts +49 -107
- package/src/components/prompt-area/animated-placeholder.tsx +6 -5
- package/src/components/prompt-area/clipboard-helpers.ts +26 -21
- package/src/components/prompt-area/cursor-helpers.ts +79 -37
- package/src/components/prompt-area/dom-helpers.ts +97 -69
- package/src/components/prompt-area/file-strip.tsx +90 -80
- package/src/components/prompt-area/html-to-markdown.ts +67 -46
- package/src/components/prompt-area/image-strip.tsx +8 -18
- package/src/components/prompt-area/index.ts +2 -15
- package/src/components/prompt-area/prompt-area-engine.ts +100 -115
- package/src/components/prompt-area/prompt-area-list-ops.ts +66 -59
- package/src/components/prompt-area/prompt-area.tsx +31 -38
- package/src/components/prompt-area/remove-button.tsx +3 -9
- package/src/components/prompt-area/segment-helpers.ts +4 -12
- package/src/components/prompt-area/trigger-popover.tsx +15 -27
- package/src/components/prompt-area/trigger-presets.ts +0 -10
- package/src/components/prompt-area/types.ts +3 -8
- package/src/components/prompt-area/use-chip-editing.ts +305 -0
- package/src/components/prompt-area/use-markdown-mode.ts +7 -17
- package/src/components/prompt-area/use-prompt-area-events.ts +80 -86
- package/src/components/prompt-area/use-prompt-area-keydown.ts +598 -0
- package/src/components/prompt-area/use-prompt-area-state.ts +5 -23
- package/src/components/prompt-area/use-prompt-area.ts +170 -896
- package/src/components/prompt-area/use-trigger-search.ts +21 -19
- package/src/components/terminal/PermissionPrompt.tsx +56 -59
- package/src/components/terminal/QuestionPrompt.tsx +28 -79
- package/src/components/terminal/StatusLine.tsx +13 -63
- package/src/components/terminal/TerminalTranscript.tsx +44 -160
- package/src/components/terminal/affordances.tsx +68 -98
- package/src/components/terminal/blocks.ts +20 -156
- package/src/components/terminal/diff.tsx +19 -67
- package/src/components/terminal/height.ts +163 -313
- package/src/components/terminal/image-box.ts +0 -44
- package/src/components/terminal/items.tsx +150 -305
- package/src/components/terminal/markdown.tsx +36 -95
- package/src/components/terminal/press.tsx +26 -53
- package/src/components/terminal/prompt.tsx +40 -170
- package/src/components/terminal/result-preview.ts +8 -56
- package/src/components/terminal/row.tsx +11 -98
- package/src/components/terminal/scrubber.tsx +108 -401
- package/src/components/terminal/surface.tsx +6 -54
- package/src/components/terminal/todos.ts +66 -0
- package/src/components/terminal/tool-run.ts +21 -192
- package/src/components/ui/AlertDialog.tsx +8 -23
- package/src/components/ui/Badge.tsx +18 -29
- package/src/components/ui/Button.tsx +7 -15
- package/src/components/ui/Card.tsx +2 -13
- package/src/components/ui/CodeBlock.tsx +10 -37
- package/src/components/ui/CopyButton.tsx +10 -23
- package/src/components/ui/Dialog.tsx +23 -36
- package/src/components/ui/Empty.tsx +6 -29
- package/src/components/ui/Input.tsx +14 -16
- package/src/components/ui/Menu.tsx +6 -18
- package/src/components/ui/PortalScope.tsx +1 -21
- package/src/components/ui/ProgressRing.tsx +8 -19
- package/src/components/ui/Select.tsx +18 -31
- package/src/components/ui/Sonner.tsx +1 -2
- package/src/components/ui/Spinner.tsx +1 -1
- package/src/components/ui/Splitter.tsx +30 -64
- package/src/components/ui/Textarea.tsx +2 -5
- package/src/components/ui/Tooltip.tsx +3 -13
- package/src/format.ts +0 -9
- package/src/index.ts +16 -113
- package/src/lib/clipboard.ts +8 -28
- package/src/lib/css.ts +8 -0
- package/src/lib/format.ts +101 -86
- package/src/lib/plan-request.ts +16 -0
- package/src/lib/status.ts +45 -95
- package/src/lib/tool-icon.ts +39 -47
- package/src/lib/utils.ts +1 -3
- package/src/styles/terminal.css +81 -49
- package/src/styles/theme.css +96 -32
- package/src/workspace.ts +1 -24
- package/build/SessionPanel-BobynUo4.d.mts +0 -669
- package/build/SessionPanel-DUt2VzXG.mjs.map +0 -1
- package/build/format-ljc3lKpA.d.mts +0 -59
- package/build/status-BE-zg88x.mjs.map +0 -1
package/build/workspace.d.mts
CHANGED
|
@@ -1,152 +1,50 @@
|
|
|
1
|
-
import { r as SessionPanelProps } from "./SessionPanel-
|
|
2
|
-
import * as _$react from "react";
|
|
1
|
+
import { r as SessionPanelProps } from "./SessionPanel-yNO60Tzw.mjs";
|
|
3
2
|
import { CSSProperties } from "react";
|
|
4
3
|
import { OpenFile, UseHostFileSearchResult, UseHostFileTreeResult } from "@workerdeck/react";
|
|
5
4
|
import { WorkerDeckClient } from "@workerdeck/client";
|
|
6
|
-
|
|
7
5
|
//#region src/components/agent/SessionWorkspace.d.ts
|
|
8
6
|
interface SessionWorkspaceProps {
|
|
9
7
|
client: WorkerDeckClient;
|
|
10
8
|
sessionId: string | undefined;
|
|
11
|
-
/** Passed straight through to {@link SessionPanel} — including the render-prop
|
|
12
|
-
* form that claims the session-actions menu. */
|
|
13
9
|
header?: SessionPanelProps['header'];
|
|
14
|
-
/**
|
|
15
|
-
* Panel seams the workspace does not interpret, forwarded verbatim.
|
|
16
|
-
*
|
|
17
|
-
* They are listed rather than spread so the workspace stays explicit about
|
|
18
|
-
* what it passes on: the panel owns the session's one attach, and a seam that
|
|
19
|
-
* silently arrived here would be a second place to look for why a session
|
|
20
|
-
* renders the way it does.
|
|
21
|
-
*/
|
|
22
10
|
transcriptVariant?: SessionPanelProps['transcriptVariant'];
|
|
23
11
|
transcriptDensity?: SessionPanelProps['transcriptDensity'];
|
|
24
12
|
transcriptFont?: SessionPanelProps['transcriptFont'];
|
|
25
|
-
/** The overview ruler in place of the scrollbar — see `SessionPanel`. */
|
|
26
13
|
scrubber?: SessionPanelProps['scrubber'];
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
bookmarks?: SessionPanelProps['bookmarks'];
|
|
15
|
+
onToggleBookmark?: SessionPanelProps['onToggleBookmark'];
|
|
29
16
|
stickyPrompt?: SessionPanelProps['stickyPrompt'];
|
|
30
|
-
/** Take the panel body over with one sub-agent's work — see `SessionPanel`. */
|
|
31
17
|
openSubagent?: SessionPanelProps['openSubagent'];
|
|
32
|
-
/** Travel to a row in the conversation without framing anything — where a
|
|
33
|
-
* **task** press lands, as opposed to `openSubagent`'s takeover. See
|
|
34
|
-
* `SessionPanel`. */
|
|
35
18
|
reveal?: SessionPanelProps['reveal'];
|
|
36
|
-
/** The outward half of `openSubagent`: which sub-agent the panel now has
|
|
37
|
-
* framed, or `undefined` for the conversation — see `SessionPanel`. */
|
|
38
19
|
onSubagentChange?: SessionPanelProps['onSubagentChange'];
|
|
39
|
-
/** Which end of the panel the status bar sits at — see `SessionPanel`. */
|
|
40
20
|
statusPlacement?: SessionPanelProps['statusPlacement'];
|
|
41
21
|
controlsSurface?: SessionPanelProps['controlsSurface'];
|
|
42
|
-
/** Base font size in whole pixels — see `SessionPanel.fontSize`. */
|
|
43
22
|
fontSize?: SessionPanelProps['fontSize'];
|
|
44
|
-
/** Link click handler — see `SessionPanel.onLinkClick`. */
|
|
45
23
|
onLinkClick?: SessionPanelProps['onLinkClick'];
|
|
46
24
|
unseen?: SessionPanelProps['unseen'];
|
|
47
|
-
/** Viewer mode — no composer, no approval prompts. Forwarded verbatim to
|
|
48
|
-
* {@link SessionPanel}; the file tree and editor are unaffected, since reading
|
|
49
|
-
* a run's files is the point of a read-only view. */
|
|
50
25
|
readOnly?: SessionPanelProps['readOnly'];
|
|
51
26
|
onVitals?: SessionPanelProps['onVitals'];
|
|
52
|
-
/** Rail width in pixels on first render. */
|
|
53
27
|
defaultRailWidth?: number;
|
|
54
|
-
/** Start with the file rail collapsed even on a wide viewport. */
|
|
55
28
|
defaultRailCollapsed?: boolean;
|
|
56
|
-
/**
|
|
57
|
-
* The rail moved. Paired with the two defaults so an embedder can persist the
|
|
58
|
-
* layout — the workspace deliberately does not, because *where* to keep it (a
|
|
59
|
-
* Memento, localStorage, a workspace file) is the embedder's call, and a
|
|
60
|
-
* component that picked one would be wrong in the other hosts.
|
|
61
|
-
*/
|
|
62
29
|
onRailChange?: (rail: {
|
|
63
30
|
width: number;
|
|
64
31
|
collapsed: boolean;
|
|
65
32
|
}) => void;
|
|
66
33
|
className?: string;
|
|
67
34
|
}
|
|
68
|
-
|
|
69
|
-
* A VS Code-shaped workspace around a live session: file tree on the left, open
|
|
70
|
-
* files above, the agent below.
|
|
71
|
-
*
|
|
72
|
-
* **Strictly additive.** {@link SessionPanel} is untouched and still the whole
|
|
73
|
-
* session surface on its own — an embedder picks one or the other, and one that
|
|
74
|
-
* has its own file tree keeps using the panel.
|
|
75
|
-
*
|
|
76
|
-
* Two things here are load-bearing and easy to break:
|
|
77
|
-
*
|
|
78
|
-
* 1. **The editor region is absent from the layout when nothing is open**, not
|
|
79
|
-
* collapsed to zero height. A zero-height pane leaves a draggable splitter and
|
|
80
|
-
* parks the composer at an odd offset; absence is what makes the agent's
|
|
81
|
-
* "claims the full column" state actually look like the panel alone.
|
|
82
|
-
* 2. **`SessionPanel` keeps its position in the tree across that transition.** It
|
|
83
|
-
* holds the WebSocket attach and the entire transcript, so moving it between
|
|
84
|
-
* parents — or wrapping it conditionally — would remount it and drop the
|
|
85
|
-
* session's rendered history on the floor the first time someone opens a file.
|
|
86
|
-
* The conditional children before it are `? :` expressions that leave a null in
|
|
87
|
-
* their slot, which is exactly what keeps its index stable.
|
|
88
|
-
*/
|
|
89
|
-
declare function SessionWorkspace({
|
|
90
|
-
client,
|
|
91
|
-
sessionId,
|
|
92
|
-
header,
|
|
93
|
-
transcriptVariant,
|
|
94
|
-
transcriptDensity,
|
|
95
|
-
transcriptFont,
|
|
96
|
-
scrubber,
|
|
97
|
-
scrubberMarks,
|
|
98
|
-
stickyPrompt,
|
|
99
|
-
openSubagent,
|
|
100
|
-
reveal,
|
|
101
|
-
onSubagentChange,
|
|
102
|
-
statusPlacement,
|
|
103
|
-
controlsSurface,
|
|
104
|
-
fontSize,
|
|
105
|
-
onLinkClick,
|
|
106
|
-
unseen,
|
|
107
|
-
readOnly,
|
|
108
|
-
onVitals,
|
|
109
|
-
defaultRailWidth,
|
|
110
|
-
defaultRailCollapsed,
|
|
111
|
-
onRailChange,
|
|
112
|
-
className
|
|
113
|
-
}: SessionWorkspaceProps): _$react.JSX.Element;
|
|
35
|
+
declare function SessionWorkspace({ client, sessionId, header, transcriptVariant, transcriptDensity, transcriptFont, scrubber, bookmarks, onToggleBookmark, stickyPrompt, openSubagent, reveal, onSubagentChange, statusPlacement, controlsSurface, fontSize, onLinkClick, unseen, readOnly, onVitals, defaultRailWidth, defaultRailCollapsed, onRailChange, className }: SessionWorkspaceProps): import("react").JSX.Element;
|
|
114
36
|
//#endregion
|
|
115
37
|
//#region src/components/agent/FileTree.d.ts
|
|
116
38
|
interface FileTreeProps {
|
|
117
|
-
/** Tree state from `useHostFileTree` — this component holds none of its own. */
|
|
118
39
|
tree: UseHostFileTreeResult;
|
|
119
|
-
/** Optional search from `useHostFileSearch`; omit and the box is not offered. */
|
|
120
40
|
search?: UseHostFileSearchResult;
|
|
121
|
-
/** Path of the focused file, highlighted in the tree. */
|
|
122
41
|
activePath?: string;
|
|
123
42
|
onOpenFile: (path: string) => void;
|
|
124
|
-
/** Offered as a button in the header when given. */
|
|
125
43
|
onCollapse?: () => void;
|
|
126
44
|
style?: CSSProperties;
|
|
127
45
|
className?: string;
|
|
128
46
|
}
|
|
129
|
-
|
|
130
|
-
* The workspace's left rail: an expandable tree of the session's project,
|
|
131
|
-
* with a search box over the same fuzzy route `@file` completion uses.
|
|
132
|
-
*
|
|
133
|
-
* Presentational by construction — every piece of state it renders comes from
|
|
134
|
-
* the hooks in `@workerdeck/react`, and the only thing it owns is the search
|
|
135
|
-
* query, which is the text in its own input.
|
|
136
|
-
*
|
|
137
|
-
* Searching replaces the tree with matches rather than filtering it: the route
|
|
138
|
-
* answers with paths from all over the project, and threading those back into
|
|
139
|
-
* tree positions would mean expanding a dozen directories to show six results.
|
|
140
|
-
*/
|
|
141
|
-
declare function FileTree({
|
|
142
|
-
tree,
|
|
143
|
-
search,
|
|
144
|
-
activePath,
|
|
145
|
-
onOpenFile,
|
|
146
|
-
onCollapse,
|
|
147
|
-
style,
|
|
148
|
-
className
|
|
149
|
-
}: FileTreeProps): _$react.JSX.Element;
|
|
47
|
+
declare function FileTree({ tree, search, activePath, onOpenFile, onCollapse, style, className }: FileTreeProps): import("react").JSX.Element;
|
|
150
48
|
//#endregion
|
|
151
49
|
//#region src/components/agent/EditorTabs.d.ts
|
|
152
50
|
interface EditorTabsProps {
|
|
@@ -156,109 +54,32 @@ interface EditorTabsProps {
|
|
|
156
54
|
onClose: (path: string) => void;
|
|
157
55
|
className?: string;
|
|
158
56
|
}
|
|
159
|
-
|
|
160
|
-
* The open-file tab strip.
|
|
161
|
-
*
|
|
162
|
-
* Hand-rolled rather than built on `@base-ui/react`'s `Tabs`: a VS Code tab
|
|
163
|
-
* carries a close button, and a `<button>` inside a `<button>` is invalid HTML —
|
|
164
|
-
* getting the primitive to render something else costs more than the roving
|
|
165
|
-
* tabindex it would have provided. So that part is here, explicitly, along with
|
|
166
|
-
* the two affordances that actually make a tab strip feel right: middle-click to
|
|
167
|
-
* close, and the active tab scrolling itself into view.
|
|
168
|
-
*
|
|
169
|
-
* Deliberately state-free. Which files are open, which is focused and what
|
|
170
|
-
* closing does are all decided by `useOpenFiles`.
|
|
171
|
-
*/
|
|
172
|
-
declare function EditorTabs({
|
|
173
|
-
files,
|
|
174
|
-
activePath,
|
|
175
|
-
onActivate,
|
|
176
|
-
onClose,
|
|
177
|
-
className
|
|
178
|
-
}: EditorTabsProps): _$react.JSX.Element;
|
|
57
|
+
declare function EditorTabs({ files, activePath, onActivate, onClose, className }: EditorTabsProps): import("react").JSX.Element;
|
|
179
58
|
//#endregion
|
|
180
59
|
//#region src/components/agent/FileViewer.d.ts
|
|
181
60
|
interface FileViewerProps {
|
|
182
61
|
file: OpenFile | undefined;
|
|
183
|
-
/** From `/fs/roots`. False renders the editor read-only rather than letting
|
|
184
|
-
* someone type into a file this gateway will refuse to write. */
|
|
185
62
|
canWrite?: boolean;
|
|
186
63
|
onChange?: (path: string, content: string) => void;
|
|
187
64
|
onSave?: (path: string) => void;
|
|
188
|
-
/** Discard this tab's edits — local only, no re-read. */
|
|
189
65
|
onRevert?: (path: string) => void;
|
|
190
|
-
/** Take the version on disk, discarding this tab's edits. */
|
|
191
66
|
onReload?: (path: string) => void;
|
|
192
|
-
/** Take this tab's version, over whatever is on disk now. */
|
|
193
67
|
onOverwrite?: (path: string) => void;
|
|
194
68
|
onDismissConflict?: (path: string) => void;
|
|
195
69
|
className?: string;
|
|
196
70
|
}
|
|
197
|
-
|
|
198
|
-
* The focused file: Monaco, plus the states a file can be in that are not
|
|
199
|
-
* "here is some text".
|
|
200
|
-
*
|
|
201
|
-
* No path row — the tab's tooltip carries the path and the size, and a line of
|
|
202
|
-
* monospace above every file is chrome that never earns its height.
|
|
203
|
-
*/
|
|
204
|
-
declare function FileViewer({
|
|
205
|
-
file,
|
|
206
|
-
canWrite,
|
|
207
|
-
onChange,
|
|
208
|
-
onSave,
|
|
209
|
-
onRevert,
|
|
210
|
-
onReload,
|
|
211
|
-
onOverwrite,
|
|
212
|
-
onDismissConflict,
|
|
213
|
-
className
|
|
214
|
-
}: FileViewerProps): _$react.JSX.Element | null;
|
|
71
|
+
declare function FileViewer({ file, canWrite, onChange, onSave, onRevert, onReload, onOverwrite, onDismissConflict, className }: FileViewerProps): import("react").JSX.Element | null;
|
|
215
72
|
//#endregion
|
|
216
73
|
//#region src/components/agent/CodeEditor.d.ts
|
|
217
74
|
interface CodeEditorProps {
|
|
218
|
-
/** Absolute path — decides the language and identifies the model. */
|
|
219
75
|
path: string;
|
|
220
|
-
/** Text to show. Applied to the model when it differs from what is on screen,
|
|
221
|
-
* so an external reload lands without fighting the user's cursor. */
|
|
222
76
|
value: string;
|
|
223
77
|
onChange?: (value: string) => void;
|
|
224
|
-
/** Ctrl/Cmd+S. Wired inside Monaco because the editor swallows keydown. */
|
|
225
78
|
onSave?: () => void;
|
|
226
79
|
readOnly?: boolean;
|
|
227
80
|
className?: string;
|
|
228
81
|
}
|
|
229
|
-
|
|
230
|
-
* Monaco — VS Code's own editor — behind a small React surface.
|
|
231
|
-
*
|
|
232
|
-
* Two deliberate choices, both about `@workerdeck/ui` being a **published
|
|
233
|
-
* library** rather than an app:
|
|
234
|
-
*
|
|
235
|
-
* 1. **Loaded on demand.** The `import()` is inside an effect, so Monaco is a
|
|
236
|
-
* separate chunk that arrives when someone first opens a file. A dashboard
|
|
237
|
-
* that never opens one never pays for it, and Monaco's ~90 language grammars
|
|
238
|
-
* are themselves lazy (each `registerLanguage` carries an `import()` loader),
|
|
239
|
-
* so opening a `.ts` file fetches the TypeScript grammar and nothing else.
|
|
240
|
-
* 2. **No `MonacoEnvironment` is configured here, and none is needed.** Workers
|
|
241
|
-
* in a library become every embedder's bootstrapping problem, and
|
|
242
|
-
* `packages/web` ships prebuilt static files at a domain root, which is
|
|
243
|
-
* exactly where hardcoded worker URLs break. The editor is configured so it
|
|
244
|
-
* never asks for one: `wordBasedSuggestions` and `quickSuggestions` off,
|
|
245
|
-
* no diff editor. A host that wants the worker-backed language services
|
|
246
|
-
* (TypeScript IntelliSense, JSON schema validation) sets `MonacoEnvironment`
|
|
247
|
-
* itself before the first file is opened — Monaco is a singleton and nothing
|
|
248
|
-
* here fights that.
|
|
249
|
-
*
|
|
250
|
-
* One model per path, kept across tab switches, so undo history and view state
|
|
251
|
-
* survive clicking away and back — which is most of what makes tabs feel like
|
|
252
|
-
* tabs rather than like re-opening a file.
|
|
253
|
-
*/
|
|
254
|
-
declare function CodeEditor({
|
|
255
|
-
path,
|
|
256
|
-
value,
|
|
257
|
-
onChange,
|
|
258
|
-
onSave,
|
|
259
|
-
readOnly,
|
|
260
|
-
className
|
|
261
|
-
}: CodeEditorProps): _$react.JSX.Element;
|
|
82
|
+
declare function CodeEditor({ path, value, onChange, onSave, readOnly, className }: CodeEditorProps): import("react").JSX.Element;
|
|
262
83
|
//#endregion
|
|
263
84
|
export { CodeEditor, type CodeEditorProps, EditorTabs, type EditorTabsProps, FileTree, type FileTreeProps, FileViewer, type FileViewerProps, SessionWorkspace, type SessionWorkspaceProps };
|
|
264
85
|
//# sourceMappingURL=workspace.d.mts.map
|
package/build/workspace.mjs
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Bt as Input, Gt as cn, R as EntryIcon, Ut as Button, bt as TooltipProvider, ht as Spinner, pt as Splitter, t as SessionPanel, vt as Tip } from "./SessionPanel-I5QSLLb0.mjs";
|
|
2
|
+
import { f as formatBytes } from "./status-Dm9INVTt.mjs";
|
|
3
3
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { ChevronRight, File, FileWarning, Folder, FolderOpen,
|
|
5
|
+
import { ChevronRight, File, FileWarning, Folder, FolderOpen, PanelLeftClose, PanelLeftOpen, RefreshCw, Search, TriangleAlert, X } from "lucide-react";
|
|
6
6
|
import { currentText, isDirty, useHostFileRoots, useHostFileSearch, useHostFileTree, useOpenFiles, useSessionInfo } from "@workerdeck/react";
|
|
7
7
|
import { createPortal } from "react-dom";
|
|
8
8
|
//#region src/components/agent/EditorTabs.tsx
|
|
9
|
-
/**
|
|
10
|
-
* The open-file tab strip.
|
|
11
|
-
*
|
|
12
|
-
* Hand-rolled rather than built on `@base-ui/react`'s `Tabs`: a VS Code tab
|
|
13
|
-
* carries a close button, and a `<button>` inside a `<button>` is invalid HTML —
|
|
14
|
-
* getting the primitive to render something else costs more than the roving
|
|
15
|
-
* tabindex it would have provided. So that part is here, explicitly, along with
|
|
16
|
-
* the two affordances that actually make a tab strip feel right: middle-click to
|
|
17
|
-
* close, and the active tab scrolling itself into view.
|
|
18
|
-
*
|
|
19
|
-
* Deliberately state-free. Which files are open, which is focused and what
|
|
20
|
-
* closing does are all decided by `useOpenFiles`.
|
|
21
|
-
*/
|
|
22
9
|
function EditorTabs({ files, activePath, onActivate, onClose, className }) {
|
|
23
10
|
return /* @__PURE__ */ jsx(TooltipProvider, {
|
|
24
11
|
delay: 500,
|
|
@@ -113,21 +100,7 @@ function Tab({ file, active, onActivate, onClose, onArrow }) {
|
|
|
113
100
|
}
|
|
114
101
|
//#endregion
|
|
115
102
|
//#region src/components/agent/FileTree.tsx
|
|
116
|
-
/** How far one level of nesting indents, in pixels. Inline rather than a Tailwind
|
|
117
|
-
* class because depth is a number at runtime and `pl-${n}` is not a class. */
|
|
118
103
|
const INDENT = 12;
|
|
119
|
-
/**
|
|
120
|
-
* The workspace's left rail: an expandable tree of the session's project,
|
|
121
|
-
* with a search box over the same fuzzy route `@file` completion uses.
|
|
122
|
-
*
|
|
123
|
-
* Presentational by construction — every piece of state it renders comes from
|
|
124
|
-
* the hooks in `@workerdeck/react`, and the only thing it owns is the search
|
|
125
|
-
* query, which is the text in its own input.
|
|
126
|
-
*
|
|
127
|
-
* Searching replaces the tree with matches rather than filtering it: the route
|
|
128
|
-
* answers with paths from all over the project, and threading those back into
|
|
129
|
-
* tree positions would mean expanding a dozen directories to show six results.
|
|
130
|
-
*/
|
|
131
104
|
function FileTree({ tree, search, activePath, onOpenFile, onCollapse, style, className }) {
|
|
132
105
|
const [query, setQuery] = useState("");
|
|
133
106
|
const [matches, setMatches] = useState();
|
|
@@ -246,8 +219,6 @@ function FileTree({ tree, search, activePath, onOpenFile, onCollapse, style, cla
|
|
|
246
219
|
})]
|
|
247
220
|
});
|
|
248
221
|
}
|
|
249
|
-
/** One clickable line. A directory and a file differ only in what the click does
|
|
250
|
-
* and whether there is a chevron — visually they are the same row. */
|
|
251
222
|
function Row({ label, detail, title, icon, chevron, indent = 0, active, onClick }) {
|
|
252
223
|
const ref = useRef(null);
|
|
253
224
|
useEffect(() => {
|
|
@@ -277,48 +248,15 @@ function Row({ label, detail, title, icon, chevron, indent = 0, active, onClick
|
|
|
277
248
|
]
|
|
278
249
|
});
|
|
279
250
|
}
|
|
280
|
-
/** Last segment of a relative match. */
|
|
281
251
|
function fileName(relative) {
|
|
282
252
|
return relative.slice(relative.lastIndexOf("/") + 1);
|
|
283
253
|
}
|
|
284
|
-
/** Everything before it, or `undefined` for a file at the search root. */
|
|
285
254
|
function directoryOf(relative) {
|
|
286
255
|
const cut = relative.lastIndexOf("/");
|
|
287
256
|
return cut === -1 ? void 0 : relative.slice(0, cut);
|
|
288
257
|
}
|
|
289
|
-
/** A symlink is reported as itself and never silently resolved — following it is
|
|
290
|
-
* the next request's problem, and that request is refused if it escapes the roots. */
|
|
291
|
-
function EntryIcon({ type }) {
|
|
292
|
-
if (type === "symlink") return /* @__PURE__ */ jsx(Link2, { className: "size-3.5 shrink-0 text-fg-4" });
|
|
293
|
-
return /* @__PURE__ */ jsx(File, { className: "size-3.5 shrink-0 text-fg-4" });
|
|
294
|
-
}
|
|
295
258
|
//#endregion
|
|
296
259
|
//#region src/components/agent/CodeEditor.tsx
|
|
297
|
-
/**
|
|
298
|
-
* Monaco — VS Code's own editor — behind a small React surface.
|
|
299
|
-
*
|
|
300
|
-
* Two deliberate choices, both about `@workerdeck/ui` being a **published
|
|
301
|
-
* library** rather than an app:
|
|
302
|
-
*
|
|
303
|
-
* 1. **Loaded on demand.** The `import()` is inside an effect, so Monaco is a
|
|
304
|
-
* separate chunk that arrives when someone first opens a file. A dashboard
|
|
305
|
-
* that never opens one never pays for it, and Monaco's ~90 language grammars
|
|
306
|
-
* are themselves lazy (each `registerLanguage` carries an `import()` loader),
|
|
307
|
-
* so opening a `.ts` file fetches the TypeScript grammar and nothing else.
|
|
308
|
-
* 2. **No `MonacoEnvironment` is configured here, and none is needed.** Workers
|
|
309
|
-
* in a library become every embedder's bootstrapping problem, and
|
|
310
|
-
* `packages/web` ships prebuilt static files at a domain root, which is
|
|
311
|
-
* exactly where hardcoded worker URLs break. The editor is configured so it
|
|
312
|
-
* never asks for one: `wordBasedSuggestions` and `quickSuggestions` off,
|
|
313
|
-
* no diff editor. A host that wants the worker-backed language services
|
|
314
|
-
* (TypeScript IntelliSense, JSON schema validation) sets `MonacoEnvironment`
|
|
315
|
-
* itself before the first file is opened — Monaco is a singleton and nothing
|
|
316
|
-
* here fights that.
|
|
317
|
-
*
|
|
318
|
-
* One model per path, kept across tab switches, so undo history and view state
|
|
319
|
-
* survive clicking away and back — which is most of what makes tabs feel like
|
|
320
|
-
* tabs rather than like re-opening a file.
|
|
321
|
-
*/
|
|
322
260
|
function CodeEditor({ path, value, onChange, onSave, readOnly, className }) {
|
|
323
261
|
const host = useRef(null);
|
|
324
262
|
const editor = useRef(null);
|
|
@@ -417,12 +355,6 @@ function CodeEditor({ path, value, onChange, onSave, readOnly, className }) {
|
|
|
417
355
|
}) : null]
|
|
418
356
|
});
|
|
419
357
|
}
|
|
420
|
-
/**
|
|
421
|
-
* Follow the `data-theme` the design tokens already swap on, so the editor is
|
|
422
|
-
* never the one light rectangle in a dark app (or the reverse). An attribute
|
|
423
|
-
* observer rather than a media query: the app has a manual toggle, and the
|
|
424
|
-
* attribute is what that toggle writes.
|
|
425
|
-
*/
|
|
426
358
|
function useDocumentTheme() {
|
|
427
359
|
const [theme, setTheme] = useState(() => typeof document === "undefined" ? null : document.documentElement.getAttribute("data-theme"));
|
|
428
360
|
useEffect(() => {
|
|
@@ -438,17 +370,9 @@ function useDocumentTheme() {
|
|
|
438
370
|
}, []);
|
|
439
371
|
return theme;
|
|
440
372
|
}
|
|
441
|
-
/** An unset attribute means the host never opted into the token themes, in which
|
|
442
|
-
* case dark matches this package's default surface. */
|
|
443
373
|
function monacoTheme(theme) {
|
|
444
374
|
return theme === "light" ? "wd-light" : "wd-dark";
|
|
445
375
|
}
|
|
446
|
-
/**
|
|
447
|
-
* Load Monaco once, register the themes, and hand back the API.
|
|
448
|
-
*
|
|
449
|
-
* Cached as a promise rather than a value so that several editors mounting in
|
|
450
|
-
* the same frame share one load instead of racing three of them.
|
|
451
|
-
*/
|
|
452
376
|
let monacoPromise;
|
|
453
377
|
function loadMonaco() {
|
|
454
378
|
monacoPromise ??= (async () => {
|
|
@@ -469,28 +393,19 @@ function loadMonaco() {
|
|
|
469
393
|
})();
|
|
470
394
|
return monacoPromise;
|
|
471
395
|
}
|
|
472
|
-
/**
|
|
473
|
-
* Monaco's language id for a path.
|
|
474
|
-
*
|
|
475
|
-
* By extension, with a short table for the files that have none — Monaco's own
|
|
476
|
-
* registry is keyed on extensions it knows, and an unmatched file falls through
|
|
477
|
-
* to `plaintext`, which is the honest answer rather than a guess.
|
|
478
|
-
*/
|
|
479
396
|
function languageOf(path) {
|
|
480
397
|
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
481
398
|
const byName = FILENAME_LANGUAGES[name.toLowerCase()];
|
|
482
399
|
if (byName) return byName;
|
|
483
|
-
|
|
400
|
+
const extension = name.slice(name.lastIndexOf(".") + 1).toLowerCase();
|
|
401
|
+
return EXTENSION_LANGUAGES[extension] ?? "plaintext";
|
|
484
402
|
}
|
|
485
|
-
/** Files whose type is their whole name. */
|
|
486
403
|
const FILENAME_LANGUAGES = {
|
|
487
404
|
dockerfile: "dockerfile",
|
|
488
405
|
makefile: "plaintext",
|
|
489
406
|
".gitignore": "plaintext",
|
|
490
407
|
".env": "shell"
|
|
491
408
|
};
|
|
492
|
-
/** Extension → Monaco language id. Only where the id differs from the extension
|
|
493
|
-
* or the extension is ambiguous; everything else Monaco resolves itself. */
|
|
494
409
|
const EXTENSION_LANGUAGES = {
|
|
495
410
|
ts: "typescript",
|
|
496
411
|
tsx: "typescript",
|
|
@@ -548,13 +463,6 @@ const EXTENSION_LANGUAGES = {
|
|
|
548
463
|
};
|
|
549
464
|
//#endregion
|
|
550
465
|
//#region src/components/agent/FileViewer.tsx
|
|
551
|
-
/**
|
|
552
|
-
* The focused file: Monaco, plus the states a file can be in that are not
|
|
553
|
-
* "here is some text".
|
|
554
|
-
*
|
|
555
|
-
* No path row — the tab's tooltip carries the path and the size, and a line of
|
|
556
|
-
* monospace above every file is chrome that never earns its height.
|
|
557
|
-
*/
|
|
558
466
|
function FileViewer({ file, canWrite, onChange, onSave, onRevert, onReload, onOverwrite, onDismissConflict, className }) {
|
|
559
467
|
if (!file) return null;
|
|
560
468
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -666,32 +574,27 @@ function Centred({ children }) {
|
|
|
666
574
|
}
|
|
667
575
|
//#endregion
|
|
668
576
|
//#region src/components/agent/use-path-links.ts
|
|
669
|
-
/** Marks the element under the pointer while the modifier is held. */
|
|
670
577
|
const LINKISH = "wd-path-link";
|
|
671
|
-
/** Absolute, or relative with at least one separator. `:12` is a line number. */
|
|
672
578
|
const PATH_RE = /^(\/[^\s:'"`()[\]{}]+|[\w.@~-]+(?:\/[\w.@~-]+)+)(?::(\d+))?$/;
|
|
673
|
-
/** A bare filename — only trusted inside a code element. */
|
|
674
579
|
const FILE_RE = /^([\w.@-]+\.[A-Za-z0-9]{1,10})(?::(\d+))?$/;
|
|
675
|
-
/** How much of an element's text the match must cover, outside code. */
|
|
676
580
|
const COVERAGE = .6;
|
|
677
581
|
function matchPath(text, inCode) {
|
|
678
582
|
const trimmed = text.trim();
|
|
679
|
-
if (!trimmed || trimmed.endsWith("/")) return
|
|
583
|
+
if (!trimmed || trimmed.endsWith("/")) return;
|
|
680
584
|
const hit = PATH_RE.exec(trimmed) ?? (inCode ? FILE_RE.exec(trimmed) : null);
|
|
681
|
-
if (!hit) return
|
|
585
|
+
if (!hit) return;
|
|
682
586
|
return {
|
|
683
587
|
path: hit[1],
|
|
684
588
|
line: hit[2] ? Number(hit[2]) : void 0
|
|
685
589
|
};
|
|
686
590
|
}
|
|
687
|
-
/** The path a click on this element means, if any. */
|
|
688
591
|
function hitFor(element) {
|
|
689
|
-
if (!element) return
|
|
592
|
+
if (!element) return;
|
|
690
593
|
const inCode = element.closest("code") !== null;
|
|
691
594
|
const text = element.textContent ?? "";
|
|
692
595
|
const hit = matchPath(text, inCode);
|
|
693
|
-
if (!hit) return
|
|
694
|
-
if (!inCode && hit.path.length < text.trim().length * COVERAGE) return
|
|
596
|
+
if (!hit) return;
|
|
597
|
+
if (!inCode && hit.path.length < text.trim().length * COVERAGE) return;
|
|
695
598
|
return hit;
|
|
696
599
|
}
|
|
697
600
|
function usePathLinks({ container, onOpen, enabled = true, ignore }) {
|
|
@@ -747,12 +650,6 @@ function usePathLinks({ container, onOpen, enabled = true, ignore }) {
|
|
|
747
650
|
ignore
|
|
748
651
|
]);
|
|
749
652
|
}
|
|
750
|
-
/**
|
|
751
|
-
* A transcript path against the session's cwd.
|
|
752
|
-
*
|
|
753
|
-
* Absolute wins outright. Everything else is relative to where the agent is
|
|
754
|
-
* working, which is the only root that makes `worker.mjs` mean anything.
|
|
755
|
-
*/
|
|
756
653
|
function resolveAgainstCwd(path, cwd) {
|
|
757
654
|
if (path.startsWith("/")) return path;
|
|
758
655
|
if (!cwd) return path;
|
|
@@ -762,33 +659,9 @@ function resolveAgainstCwd(path, cwd) {
|
|
|
762
659
|
//#region src/components/agent/SessionWorkspace.tsx
|
|
763
660
|
const RAIL_MIN = 180;
|
|
764
661
|
const RAIL_MAX = 520;
|
|
765
|
-
/** How little of the agent column the editor may leave. Below this the composer
|
|
766
|
-
* and a line of transcript stop fitting together, which is the point at which the
|
|
767
|
-
* agent has stopped being usable rather than merely small. */
|
|
768
662
|
const AGENT_MIN = 220;
|
|
769
663
|
const EDITOR_MIN = 120;
|
|
770
|
-
|
|
771
|
-
* A VS Code-shaped workspace around a live session: file tree on the left, open
|
|
772
|
-
* files above, the agent below.
|
|
773
|
-
*
|
|
774
|
-
* **Strictly additive.** {@link SessionPanel} is untouched and still the whole
|
|
775
|
-
* session surface on its own — an embedder picks one or the other, and one that
|
|
776
|
-
* has its own file tree keeps using the panel.
|
|
777
|
-
*
|
|
778
|
-
* Two things here are load-bearing and easy to break:
|
|
779
|
-
*
|
|
780
|
-
* 1. **The editor region is absent from the layout when nothing is open**, not
|
|
781
|
-
* collapsed to zero height. A zero-height pane leaves a draggable splitter and
|
|
782
|
-
* parks the composer at an odd offset; absence is what makes the agent's
|
|
783
|
-
* "claims the full column" state actually look like the panel alone.
|
|
784
|
-
* 2. **`SessionPanel` keeps its position in the tree across that transition.** It
|
|
785
|
-
* holds the WebSocket attach and the entire transcript, so moving it between
|
|
786
|
-
* parents — or wrapping it conditionally — would remount it and drop the
|
|
787
|
-
* session's rendered history on the floor the first time someone opens a file.
|
|
788
|
-
* The conditional children before it are `? :` expressions that leave a null in
|
|
789
|
-
* their slot, which is exactly what keeps its index stable.
|
|
790
|
-
*/
|
|
791
|
-
function SessionWorkspace({ client, sessionId, header, transcriptVariant, transcriptDensity, transcriptFont, scrubber, scrubberMarks, stickyPrompt, openSubagent, reveal, onSubagentChange, statusPlacement, controlsSurface, fontSize, onLinkClick, unseen, readOnly, onVitals, defaultRailWidth = 260, defaultRailCollapsed, onRailChange, className }) {
|
|
664
|
+
function SessionWorkspace({ client, sessionId, header, transcriptVariant, transcriptDensity, transcriptFont, scrubber, bookmarks, onToggleBookmark, stickyPrompt, openSubagent, reveal, onSubagentChange, statusPlacement, controlsSurface, fontSize, onLinkClick, unseen, readOnly, onVitals, defaultRailWidth = 260, defaultRailCollapsed, onRailChange, className }) {
|
|
792
665
|
const { info } = useSessionInfo(client, sessionId);
|
|
793
666
|
const cwd = info?.cwd;
|
|
794
667
|
const tree = useHostFileTree(client, cwd);
|
|
@@ -913,7 +786,8 @@ function SessionWorkspace({ client, sessionId, header, transcriptVariant, transc
|
|
|
913
786
|
transcriptDensity,
|
|
914
787
|
transcriptFont,
|
|
915
788
|
scrubber,
|
|
916
|
-
|
|
789
|
+
bookmarks,
|
|
790
|
+
onToggleBookmark,
|
|
917
791
|
stickyPrompt,
|
|
918
792
|
openSubagent,
|
|
919
793
|
reveal,
|
|
@@ -939,9 +813,6 @@ function SessionWorkspace({ client, sessionId, header, transcriptVariant, transc
|
|
|
939
813
|
})]
|
|
940
814
|
});
|
|
941
815
|
}
|
|
942
|
-
/** Live height of an element, for a splitter that needs to know how much room it
|
|
943
|
-
* is dividing. Zero until the first observation, which callers treat as
|
|
944
|
-
* "unmeasured" rather than as a real bound. */
|
|
945
816
|
function useElementHeight(ref) {
|
|
946
817
|
const [height, setHeight] = useState(0);
|
|
947
818
|
useLayoutEffect(() => {
|
|
@@ -955,8 +826,6 @@ function useElementHeight(ref) {
|
|
|
955
826
|
}, [ref]);
|
|
956
827
|
return height;
|
|
957
828
|
}
|
|
958
|
-
/** Whether there is room for a rail beside the content. Presentation only — the
|
|
959
|
-
* workspace's actual state lives in the hooks from `@workerdeck/react`. */
|
|
960
829
|
function useIsWide() {
|
|
961
830
|
const [wide, setWide] = useState(true);
|
|
962
831
|
useEffect(() => {
|