@workerdeck/ui 0.11.0 → 0.12.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/index.mjs CHANGED
@@ -1,10 +1,11 @@
1
- import { $ as TooltipProvider, A as permissionModeMeta, B as mentionTrigger, C as TranscriptVariantProvider, Ct as cn, D as PERMISSION_MODES, E as toolIcon, F as ContextDialog, G as ProgressRing, H as PromptArea, I as Composer, J as Spinner, K as Splitter, L as skillPrompt, M as SkillsDialog, N as McpDialog, O as PermissionModeSelect, P as HostFilesDialog, Q as TooltipContent, R as commandTrigger, S as Response, St as buttonVariants, T as isMutatingTool, U as plainTextToSegments, V as usePromptAreaState, W as segmentsToPlainText, X as copyText, Y as CopyButton, Z as Tip, _ as SessionInfoDialog, _t as SelectValue, a as SessionEmptyState, at as DialogRow, b as parseUserQuestions, bt as badgeVariants, c as Message, ct as MenuContent, d as FileCard, dt as MenuTrigger, et as Dialog, f as Conversation, ft as Select, g as STATUS_META, gt as SelectTrigger, h as StatusBar, ht as SelectItemText, i as ToolCallCard, it as DialogHeader, j as ModelSelect, k as permissionModeChoices, l as MessageContent, lt as MenuItem, m as ConversationScrollButton, mt as SelectItem, n as UsageDialog, nt as DialogClose, o as Reasoning, ot as DialogTrigger, p as ConversationContent, pt as SelectContent, q as CodeBlock, r as Transcript, rt as DialogContent, s as PromptTokenText, st as Menu, t as SessionPanel, tt as DialogBody, u as Loader, ut as MenuSeparator, v as QUESTION_BEHAVIORS, vt as Input, w as useTranscriptVariant, x as PermissionPrompt, xt as Button, y as QuestionPrompt, yt as Badge, z as hashtagTrigger } from "./SessionPanel-_U8tjX29.mjs";
1
+ import { $ as Tip, A as PermissionModeSelect, B as commandTrigger, C as TranscriptDensityProvider, Ct as Button, D as isMutatingTool, E as useTranscriptVariant, F as McpDialog, G as plainTextToSegments, H as mentionTrigger, I as HostFilesDialog, J as Splitter, K as segmentsToPlainText, L as ContextDialog, M as permissionModeMeta, N as ModelSelect, O as toolIcon, P as SkillsDialog, Q as copyText, R as Composer, S as Response, St as badgeVariants, T as useTranscriptDensity, Tt as cn, U as usePromptAreaState, V as hashtagTrigger, W as PromptArea, X as Spinner, Y as CodeBlock, Z as CopyButton, _ as SessionInfoDialog, _t as SelectItemText, a as SessionEmptyState, at as DialogContent, b as parseUserQuestions, bt as Input, c as Message, ct as DialogTrigger, d as FileCard, dt as MenuItem, et as TooltipContent, f as Conversation, ft as MenuSeparator, g as STATUS_META, gt as SelectItem, h as StatusBar, ht as SelectContent, i as ToolCallCard, it as DialogClose, j as permissionModeChoices, k as PERMISSION_MODES, l as MessageContent, lt as Menu, m as ConversationScrollButton, mt as Select, n as UsageDialog, nt as Dialog, o as Reasoning, ot as DialogHeader, p as ConversationContent, pt as MenuTrigger, q as ProgressRing, r as Transcript, rt as DialogBody, s as PromptTokenText, st as DialogRow, t as SessionPanel, tt as TooltipProvider, u as Loader, ut as MenuContent, v as QUESTION_BEHAVIORS, vt as SelectTrigger, w as TranscriptVariantProvider, wt as buttonVariants, x as PermissionPrompt, xt as Badge, y as QuestionPrompt, yt as SelectValue, z as skillPrompt } from "./SessionPanel-NQ8ksCfj.mjs";
2
2
  import { a as formatDuration, c as formatRelativeTime, d as rateLimitWindowSeconds, f as toolInputPreview, i as formatCountdown, l as formatTokens, n as formatBytes, o as formatRateLimitWindow, r as formatCost, s as formatRateLimitWindowLong, t as formatAgoPrecise, u as friendlyModel } from "./format-DqR56Y8l.mjs";
3
- import { forwardRef } from "react";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
- import { Trash2 } from "lucide-react";
3
+ import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
+ import { Pencil, Search, Trash2, X } from "lucide-react";
6
6
  import { AlertDialog as AlertDialog$1 } from "@base-ui/react/alert-dialog";
7
7
  import { Toaster as Toaster$1, toast } from "sonner";
8
+ import { STATE_LABELS, STATE_ORDER, adaptersOf, clearFilters, filterRows, groupRows, hasFacetFilter, sessionLabel, subsetSummary } from "@workerdeck/protocol";
8
9
  //#region src/components/ui/Card.tsx
9
10
  function Card({ className, ...props }) {
10
11
  return /* @__PURE__ */ jsx("div", {
@@ -156,6 +157,330 @@ function SessionList({ sessions, activeId, onSelect, onDelete, emptyText = "No s
156
157
  });
157
158
  }
158
159
  //#endregion
159
- export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, AlertDialogTrigger, Badge, Button, Card, CardContent, CardHeader, CardTitle, CodeBlock, Composer, ContextDialog, Conversation, ConversationContent, ConversationScrollButton, CopyButton, Dialog, DialogBody, DialogClose, DialogContent, DialogHeader, DialogRow, DialogTrigger, FileCard, HostFilesDialog, Input, Loader, McpDialog, Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, Message, MessageContent, ModelSelect, PERMISSION_MODES, PermissionModeSelect, PermissionPrompt, ProgressRing, PromptArea, PromptTokenText, QUESTION_BEHAVIORS, QuestionPrompt, Reasoning, Response, STATUS_META, Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue, SessionEmptyState, SessionInfoDialog, SessionList, SessionListItem, SessionPanel, SkillsDialog, Spinner, Splitter, StatusBar, Textarea, Tip, Toaster, ToolCallCard, TooltipContent, TooltipProvider, Transcript, TranscriptVariantProvider, UsageDialog, badgeVariants, buttonVariants, cn, commandTrigger, copyText, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, getChipsByTrigger, hashtagTrigger, isMutatingTool, isSegmentsEmpty, mentionTrigger, parseUserQuestions, permissionModeChoices, permissionModeMeta, plainTextToSegments, rateLimitWindowSeconds, segmentsToPlainText, skillPrompt, toast, toolIcon, toolInputPreview, usePromptAreaState, useTranscriptVariant };
160
+ //#region src/components/agent/SessionBrowser.tsx
161
+ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelect, onDelete, onRename, emptyState, className }) {
162
+ const visible = useMemo(() => filterRows(rows, config, scope), [
163
+ rows,
164
+ config,
165
+ scope
166
+ ]);
167
+ const groups = useMemo(() => groupRows(visible, config), [visible, config]);
168
+ const subset = subsetSummary(config, scope, visible.length, rows.length);
169
+ const adapters = useMemo(() => adaptersOf(rows), [rows]);
170
+ const gateways = useMemo(() => {
171
+ const seen = /* @__PURE__ */ new Map();
172
+ for (const row of rows) seen.set(row.hostId, row.hostName);
173
+ return [...seen].map(([id, name]) => ({
174
+ id,
175
+ name
176
+ }));
177
+ }, [rows]);
178
+ const set = (patch) => onConfigChange({
179
+ ...config,
180
+ ...patch
181
+ });
182
+ return /* @__PURE__ */ jsxs("div", {
183
+ "data-slot": "session-browser",
184
+ className: cn("flex flex-col gap-3", className),
185
+ children: [
186
+ /* @__PURE__ */ jsxs("div", {
187
+ className: "flex flex-wrap items-center gap-2",
188
+ children: [
189
+ /* @__PURE__ */ jsxs("div", {
190
+ className: "relative min-w-48 flex-1",
191
+ children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-fg-4" }), /* @__PURE__ */ jsx(Input, {
192
+ value: config.search,
193
+ onChange: (e) => set({ search: e.target.value }),
194
+ placeholder: "Search sessions",
195
+ "aria-label": "Search sessions",
196
+ className: "pl-8"
197
+ })]
198
+ }),
199
+ /* @__PURE__ */ jsx(FacetSelect, {
200
+ label: "State",
201
+ value: config.states,
202
+ options: STATE_ORDER.map((s) => ({
203
+ value: s,
204
+ label: STATE_LABELS[s]
205
+ })),
206
+ onChange: (states) => set({ states })
207
+ }),
208
+ adapters.length > 1 ? /* @__PURE__ */ jsx(FacetSelect, {
209
+ label: "Engine",
210
+ value: config.adapters,
211
+ options: adapters.map((a) => ({
212
+ value: a,
213
+ label: a
214
+ })),
215
+ onChange: (adapters) => set({ adapters })
216
+ }) : null,
217
+ gateways.length > 1 ? /* @__PURE__ */ jsx(FacetSelect, {
218
+ label: "Gateway",
219
+ value: config.gateways,
220
+ options: gateways.map((g) => ({
221
+ value: g.id,
222
+ label: g.name
223
+ })),
224
+ onChange: (gateways) => set({ gateways })
225
+ }) : null,
226
+ /* @__PURE__ */ jsx(OneOfSelect, {
227
+ label: "Group",
228
+ value: config.groupBy,
229
+ options: [
230
+ {
231
+ value: "none",
232
+ label: "No grouping"
233
+ },
234
+ {
235
+ value: "state",
236
+ label: "By state"
237
+ },
238
+ {
239
+ value: "adapter",
240
+ label: "By engine"
241
+ },
242
+ ...gateways.length > 1 ? [{
243
+ value: "gateway",
244
+ label: "By gateway"
245
+ }] : []
246
+ ],
247
+ onChange: (groupBy) => set({ groupBy })
248
+ }),
249
+ /* @__PURE__ */ jsx(OneOfSelect, {
250
+ label: "Sort",
251
+ value: config.sortBy,
252
+ options: [
253
+ {
254
+ value: "recent",
255
+ label: "Recent"
256
+ },
257
+ {
258
+ value: "name",
259
+ label: "Name"
260
+ },
261
+ {
262
+ value: "state",
263
+ label: "State"
264
+ },
265
+ ...gateways.length > 1 ? [{
266
+ value: "gateway",
267
+ label: "Gateway"
268
+ }] : []
269
+ ],
270
+ onChange: (sortBy) => set({ sortBy })
271
+ })
272
+ ]
273
+ }),
274
+ subset ? /* @__PURE__ */ jsxs("div", {
275
+ className: "flex items-center gap-2 text-label text-fg-4",
276
+ children: [/* @__PURE__ */ jsxs("span", { children: [
277
+ subset.shown,
278
+ " of ",
279
+ subset.total,
280
+ subset.causes.length ? ` · ${subset.causes.join(" · ")}` : null
281
+ ] }), /* @__PURE__ */ jsx("button", {
282
+ type: "button",
283
+ className: "text-fg-3 underline underline-offset-2 hover:text-fg-1",
284
+ onClick: () => onConfigChange(clearFilters(config)),
285
+ children: "Show all"
286
+ })]
287
+ }) : null,
288
+ rows.length === 0 ? emptyState ?? /* @__PURE__ */ jsx(Empty, { children: "No sessions yet." }) : visible.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: hasFacetFilter(config) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
289
+ "No sessions match.",
290
+ " ",
291
+ /* @__PURE__ */ jsx("button", {
292
+ type: "button",
293
+ className: "underline underline-offset-2 hover:text-fg-1",
294
+ onClick: () => onConfigChange(clearFilters(config)),
295
+ children: "Clear filters"
296
+ })
297
+ ] }) : "No sessions here." }) : /* @__PURE__ */ jsx("div", {
298
+ className: "flex flex-col gap-4",
299
+ children: groups.map((group) => /* @__PURE__ */ jsxs("div", {
300
+ className: "flex flex-col gap-1",
301
+ children: [config.groupBy !== "none" && group.label ? /* @__PURE__ */ jsxs("div", {
302
+ className: "flex items-baseline gap-2 px-2.5 text-label font-medium text-fg-4",
303
+ children: [/* @__PURE__ */ jsx("span", {
304
+ className: "uppercase tracking-wide",
305
+ children: group.label
306
+ }), /* @__PURE__ */ jsx("span", {
307
+ className: "text-fg-4/70",
308
+ children: group.rows.length
309
+ })]
310
+ }) : null, group.rows.map((row) => /* @__PURE__ */ jsx(SessionRowItem, {
311
+ row,
312
+ active: row.info.id === activeId,
313
+ showGateway: gateways.length > 1,
314
+ onSelect,
315
+ onDelete,
316
+ onRename
317
+ }, `${row.hostId}:${row.info.id}`))]
318
+ }, group.key))
319
+ })
320
+ ]
321
+ });
322
+ }
323
+ function Empty({ children }) {
324
+ return /* @__PURE__ */ jsx("div", {
325
+ className: "px-2.5 py-6 text-center text-body-sm text-fg-4",
326
+ children
327
+ });
328
+ }
329
+ function SessionRowItem({ row, active, showGateway, onSelect, onDelete, onRename }) {
330
+ const { info } = row;
331
+ const meta = STATUS_META[info.status];
332
+ const [editing, setEditing] = useState(false);
333
+ return /* @__PURE__ */ jsxs("div", {
334
+ "data-slot": "session-row",
335
+ "data-active": active || void 0,
336
+ className: cn("group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors", active ? "border-border bg-surface" : "hover:bg-surface-hover"),
337
+ children: [
338
+ /* @__PURE__ */ jsxs("div", {
339
+ className: "min-w-0 flex-1",
340
+ children: [/* @__PURE__ */ jsxs("div", {
341
+ className: "flex items-center gap-2",
342
+ children: [
343
+ editing && onRename ? /* @__PURE__ */ jsx(NameEditor, {
344
+ initial: info.title ?? "",
345
+ onCommit: (title) => {
346
+ setEditing(false);
347
+ onRename(row, title);
348
+ },
349
+ onCancel: () => setEditing(false)
350
+ }) : /* @__PURE__ */ jsx("button", {
351
+ type: "button",
352
+ onClick: () => onSelect?.(row),
353
+ className: "min-w-0 truncate text-left text-body-sm font-medium text-fg-1 outline-none",
354
+ children: sessionLabel(info)
355
+ }),
356
+ /* @__PURE__ */ jsx(Badge, {
357
+ variant: meta.variant,
358
+ dot: true,
359
+ className: "shrink-0",
360
+ children: meta.label
361
+ }),
362
+ row.unseen > 0 ? /* @__PURE__ */ jsx(Badge, {
363
+ variant: "accent",
364
+ className: "shrink-0",
365
+ title: `${row.unseen} new`,
366
+ children: row.unseen
367
+ }) : null
368
+ ]
369
+ }), /* @__PURE__ */ jsxs("button", {
370
+ type: "button",
371
+ onClick: () => !editing && onSelect?.(row),
372
+ className: "mt-0.5 flex w-full items-center gap-2 text-left font-mono text-label text-fg-4 outline-none",
373
+ children: [
374
+ /* @__PURE__ */ jsx("span", {
375
+ className: "truncate",
376
+ children: info.cwd
377
+ }),
378
+ showGateway ? /* @__PURE__ */ jsx("span", {
379
+ className: "shrink-0",
380
+ children: row.hostName
381
+ }) : null,
382
+ info.profile ? /* @__PURE__ */ jsxs("span", {
383
+ className: "shrink-0",
384
+ children: ["@", info.profile]
385
+ }) : null,
386
+ /* @__PURE__ */ jsx("span", {
387
+ className: "shrink-0",
388
+ children: formatCost(info.totalCostUsd)
389
+ }),
390
+ /* @__PURE__ */ jsx("span", {
391
+ className: "shrink-0",
392
+ children: formatRelativeTime(info.lastActivityAt ?? info.createdAt)
393
+ })
394
+ ]
395
+ })]
396
+ }),
397
+ onRename && !editing ? /* @__PURE__ */ jsx(Button, {
398
+ variant: "ghost",
399
+ size: "icon-sm",
400
+ "aria-label": "Rename session",
401
+ className: "opacity-0 transition-opacity group-hover:opacity-100",
402
+ onClick: () => setEditing(true),
403
+ children: /* @__PURE__ */ jsx(Pencil, { className: "size-3.5 text-fg-3" })
404
+ }) : null,
405
+ onDelete ? /* @__PURE__ */ jsx(Button, {
406
+ variant: "ghost",
407
+ size: "icon-sm",
408
+ "aria-label": "Close session",
409
+ className: "opacity-0 transition-opacity group-hover:opacity-100",
410
+ onClick: () => onDelete(row),
411
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5 text-fg-3" })
412
+ }) : null
413
+ ]
414
+ });
415
+ }
416
+ /**
417
+ * Inline rename. Enter commits, Escape cancels, blur commits — but only a blur
418
+ * that is still inside this document: switching windows must not silently commit,
419
+ * nor kill the editor in the frame it opened.
420
+ */
421
+ function NameEditor({ initial, onCommit, onCancel }) {
422
+ const [value, setValue] = useState(initial);
423
+ const ref = useRef(null);
424
+ useEffect(() => {
425
+ ref.current?.select();
426
+ }, []);
427
+ return /* @__PURE__ */ jsx("input", {
428
+ ref,
429
+ value,
430
+ autoFocus: true,
431
+ "aria-label": "Session name",
432
+ onClick: (e) => e.stopPropagation(),
433
+ onChange: (e) => setValue(e.target.value),
434
+ onBlur: () => document.hasFocus() ? onCommit(value.trim()) : void 0,
435
+ onKeyDown: (e) => {
436
+ if (e.key === "Enter") onCommit(value.trim());
437
+ else if (e.key === "Escape") onCancel();
438
+ e.stopPropagation();
439
+ },
440
+ className: "-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-0.5 text-body-sm font-medium text-fg-1 outline-none"
441
+ });
442
+ }
443
+ /** A multi-select facet. Empty = no filter, which is why the trigger reads the
444
+ * facet's name rather than "All": nothing is being excluded. */
445
+ function FacetSelect({ label, value, options, onChange }) {
446
+ return /* @__PURE__ */ jsxs("div", {
447
+ className: "flex items-center gap-1",
448
+ children: [/* @__PURE__ */ jsxs(Select, {
449
+ multiple: true,
450
+ value,
451
+ onValueChange: (v) => onChange(v),
452
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
453
+ "aria-label": label,
454
+ className: "min-w-28",
455
+ children: /* @__PURE__ */ jsx(SelectValue, { children: value.length === 0 ? label : value.length === 1 ? options.find((o) => o.value === value[0])?.label ?? label : `${label} · ${value.length}` })
456
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
457
+ value: option.value,
458
+ children: /* @__PURE__ */ jsx(SelectItemText, { children: option.label })
459
+ }, option.value)) })]
460
+ }), value.length > 0 ? /* @__PURE__ */ jsx(Button, {
461
+ variant: "ghost",
462
+ size: "icon-sm",
463
+ "aria-label": `Clear ${label}`,
464
+ onClick: () => onChange([]),
465
+ children: /* @__PURE__ */ jsx(X, { className: "size-3 text-fg-4" })
466
+ }) : null]
467
+ });
468
+ }
469
+ function OneOfSelect({ label, value, options, onChange }) {
470
+ return /* @__PURE__ */ jsxs(Select, {
471
+ value,
472
+ onValueChange: (v) => onChange(v),
473
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
474
+ "aria-label": label,
475
+ className: "min-w-28",
476
+ children: /* @__PURE__ */ jsx(SelectValue, { children: options.find((o) => o.value === value)?.label ?? label })
477
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
478
+ value: option.value,
479
+ children: /* @__PURE__ */ jsx(SelectItemText, { children: option.label })
480
+ }, option.value)) })]
481
+ });
482
+ }
483
+ //#endregion
484
+ export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, AlertDialogTrigger, Badge, Button, Card, CardContent, CardHeader, CardTitle, CodeBlock, Composer, ContextDialog, Conversation, ConversationContent, ConversationScrollButton, CopyButton, Dialog, DialogBody, DialogClose, DialogContent, DialogHeader, DialogRow, DialogTrigger, FileCard, HostFilesDialog, Input, Loader, McpDialog, Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, Message, MessageContent, ModelSelect, PERMISSION_MODES, PermissionModeSelect, PermissionPrompt, ProgressRing, PromptArea, PromptTokenText, QUESTION_BEHAVIORS, QuestionPrompt, Reasoning, Response, STATUS_META, Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue, SessionBrowser, SessionEmptyState, SessionInfoDialog, SessionList, SessionListItem, SessionPanel, SkillsDialog, Spinner, Splitter, StatusBar, Textarea, Tip, Toaster, ToolCallCard, TooltipContent, TooltipProvider, Transcript, TranscriptDensityProvider, TranscriptVariantProvider, UsageDialog, badgeVariants, buttonVariants, cn, commandTrigger, copyText, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, getChipsByTrigger, hashtagTrigger, isMutatingTool, isSegmentsEmpty, mentionTrigger, parseUserQuestions, permissionModeChoices, permissionModeMeta, plainTextToSegments, rateLimitWindowSeconds, segmentsToPlainText, skillPrompt, toast, toolIcon, toolInputPreview, usePromptAreaState, useTranscriptDensity, useTranscriptVariant };
160
485
 
161
486
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["AlertDialogPrimitive","SonnerToaster"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/agent/SessionList.tsx"],"sourcesContent":["import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["AlertDialogPrimitive","SonnerToaster"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/agent/SessionList.tsx","../src/components/agent/SessionBrowser.tsx"],"sourcesContent":["import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport { Pencil, Search, Trash2, X } from 'lucide-react'\nimport {\n STATE_LABELS,\n STATE_ORDER,\n adaptersOf,\n clearFilters,\n filterRows,\n groupRows,\n hasFacetFilter,\n sessionLabel,\n subsetSummary,\n} from '@workerdeck/protocol'\nimport type {\n GroupBy,\n SessionRow,\n SessionState,\n SortBy,\n ViewConfig,\n WorkspaceScope,\n} from '@workerdeck/protocol'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue } from '../ui/Select.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\n/**\n * A sessions list with the affordances a list of thirty needs: search, facets,\n * grouping, sorting, unread counts, and one honest line about what is hidden.\n *\n * The *rules* are `@workerdeck/protocol`'s (`filterRows`/`groupRows`/\n * `subsetSummary`), not this component's — the VS Code sidebar renders the same\n * model with workbench chrome, its activity-bar badge counts the same rows this\n * would show, and iOS mirrors them in Swift. What lives here is the styled\n * rendering of that model, so a host that wants the dashboard's look gets it\n * without reimplementing the model behind it.\n *\n * `SessionList` remains beside this for the plain case (a fixed set of rows, no\n * controls); this is what you reach for when the list is the screen.\n */\n\nexport interface SessionBrowserProps {\n rows: SessionRow[]\n config: ViewConfig\n onConfigChange: (config: ViewConfig) => void\n /** The host's own folders, if it has such a notion. Absent — a dashboard, a\n * phone — makes the scope filter genuinely inert rather than empty. */\n scope?: WorkspaceScope\n activeId?: string\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n /**\n * Rename, from the row's pencil. Empty string restores the derived title. A\n * gateway edit (`PATCH /sessions/:id`), never a local override — every client\n * should see the same name. Omit to make titles read-only.\n *\n * A hover affordance rather than the extension's double-click-the-title,\n * because here a single click on the row navigates: the *first* click of a\n * double-click would have already left the page.\n */\n onRename?: (row: SessionRow, title: string) => void\n /** Rendered when nothing at all exists (as opposed to nothing matching). */\n emptyState?: React.ReactNode\n className?: string\n}\n\nexport function SessionBrowser({\n rows,\n config,\n onConfigChange,\n scope,\n activeId,\n onSelect,\n onDelete,\n onRename,\n emptyState,\n className,\n}: SessionBrowserProps) {\n const visible = useMemo(() => filterRows(rows, config, scope), [rows, config, scope])\n const groups = useMemo(() => groupRows(visible, config), [visible, config])\n const subset = subsetSummary(config, scope, visible.length, rows.length)\n // Derived, not enumerated: a new engine or a new gateway needs no change here,\n // and a facet with one possible value is not a choice worth showing.\n const adapters = useMemo(() => adaptersOf(rows), [rows])\n const gateways = useMemo(() => {\n const seen = new Map<string, string>()\n for (const row of rows) seen.set(row.hostId, row.hostName)\n return [...seen].map(([id, name]) => ({ id, name }))\n }, [rows])\n\n const set = (patch: Partial<ViewConfig>) => onConfigChange({ ...config, ...patch })\n\n return (\n <div data-slot='session-browser' className={cn('flex flex-col gap-3', className)}>\n <div className='flex flex-wrap items-center gap-2'>\n <div className='relative min-w-48 flex-1'>\n <Search className='pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-fg-4' />\n <Input\n value={config.search}\n onChange={(e) => set({ search: e.target.value })}\n placeholder='Search sessions'\n aria-label='Search sessions'\n className='pl-8'\n />\n </div>\n <FacetSelect\n label='State'\n value={config.states}\n options={STATE_ORDER.map((s) => ({ value: s, label: STATE_LABELS[s] }))}\n onChange={(states) => set({ states: states as SessionState[] })}\n />\n {adapters.length > 1 ? (\n <FacetSelect\n label='Engine'\n value={config.adapters}\n options={adapters.map((a) => ({ value: a, label: a }))}\n onChange={(adapters) => set({ adapters })}\n />\n ) : null}\n {gateways.length > 1 ? (\n <FacetSelect\n label='Gateway'\n value={config.gateways}\n options={gateways.map((g) => ({ value: g.id, label: g.name }))}\n onChange={(gateways) => set({ gateways })}\n />\n ) : null}\n <OneOfSelect\n label='Group'\n value={config.groupBy}\n options={[\n { value: 'none', label: 'No grouping' },\n { value: 'state', label: 'By state' },\n { value: 'adapter', label: 'By engine' },\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'By gateway' }] : []),\n ]}\n onChange={(groupBy) => set({ groupBy: groupBy as GroupBy })}\n />\n <OneOfSelect\n label='Sort'\n value={config.sortBy}\n options={[\n { value: 'recent', label: 'Recent' },\n { value: 'name', label: 'Name' },\n { value: 'state', label: 'State' },\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'Gateway' }] : []),\n ]}\n onChange={(sortBy) => set({ sortBy: sortBy as SortBy })}\n />\n </div>\n\n {subset ? (\n <div className='flex items-center gap-2 text-label text-fg-4'>\n <span>\n {subset.shown} of {subset.total}\n {subset.causes.length ? ` · ${subset.causes.join(' · ')}` : null}\n </span>\n <button\n type='button'\n className='text-fg-3 underline underline-offset-2 hover:text-fg-1'\n onClick={() => onConfigChange(clearFilters(config))}>\n Show all\n </button>\n </div>\n ) : null}\n\n {rows.length === 0 ? (\n (emptyState ?? <Empty>No sessions yet.</Empty>)\n ) : visible.length === 0 ? (\n // Two different dead ends, two different ways out. \"Nothing matches\" is\n // a filter someone set; anything else is the state of the world.\n <Empty>\n {hasFacetFilter(config) ? (\n <>\n No sessions match.{' '}\n <button\n type='button'\n className='underline underline-offset-2 hover:text-fg-1'\n onClick={() => onConfigChange(clearFilters(config))}>\n Clear filters\n </button>\n </>\n ) : (\n 'No sessions here.'\n )}\n </Empty>\n ) : (\n <div className='flex flex-col gap-4'>\n {groups.map((group) => (\n <div key={group.key} className='flex flex-col gap-1'>\n {config.groupBy !== 'none' && group.label ? (\n <div className='flex items-baseline gap-2 px-2.5 text-label font-medium text-fg-4'>\n <span className='uppercase tracking-wide'>{group.label}</span>\n <span className='text-fg-4/70'>{group.rows.length}</span>\n </div>\n ) : null}\n {group.rows.map((row) => (\n <SessionRowItem\n key={`${row.hostId}:${row.info.id}`}\n row={row}\n active={row.info.id === activeId}\n showGateway={gateways.length > 1}\n onSelect={onSelect}\n onDelete={onDelete}\n onRename={onRename}\n />\n ))}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nfunction Empty({ children }: { children: React.ReactNode }) {\n return <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{children}</div>\n}\n\ninterface SessionRowItemProps {\n row: SessionRow\n active?: boolean\n showGateway?: boolean\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n onRename?: (row: SessionRow, title: string) => void\n}\n\nfunction SessionRowItem({\n row,\n active,\n showGateway,\n onSelect,\n onDelete,\n onRename,\n}: SessionRowItemProps) {\n const { info } = row\n const meta = STATUS_META[info.status]\n const [editing, setEditing] = useState(false)\n\n return (\n <div\n data-slot='session-row'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <div className='min-w-0 flex-1'>\n <div className='flex items-center gap-2'>\n {/* The editor replaces the link rather than sitting inside it — an\n input nested in a button is invalid, and disabling the button to\n protect the edit disables the field with it. */}\n {editing && onRename ? (\n <NameEditor\n initial={info.title ?? ''}\n onCommit={(title) => {\n setEditing(false)\n onRename(row, title)\n }}\n onCancel={() => setEditing(false)}\n />\n ) : (\n <button\n type='button'\n onClick={() => onSelect?.(row)}\n className='min-w-0 truncate text-left text-body-sm font-medium text-fg-1 outline-none'>\n {sessionLabel(info)}\n </button>\n )}\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n {row.unseen > 0 ? (\n // Transcript rows since this session was last on screen — the same\n // unit the VS Code badge counts, because turns undercount badly.\n <Badge variant='accent' className='shrink-0' title={`${row.unseen} new`}>\n {row.unseen}\n </Badge>\n ) : null}\n </div>\n <button\n type='button'\n onClick={() => !editing && onSelect?.(row)}\n className='mt-0.5 flex w-full items-center gap-2 text-left font-mono text-label text-fg-4 outline-none'>\n <span className='truncate'>{info.cwd}</span>\n {showGateway ? <span className='shrink-0'>{row.hostName}</span> : null}\n {info.profile ? <span className='shrink-0'>@{info.profile}</span> : null}\n <span className='shrink-0'>{formatCost(info.totalCostUsd)}</span>\n <span className='shrink-0'>\n {formatRelativeTime(info.lastActivityAt ?? info.createdAt)}\n </span>\n </button>\n </div>\n {onRename && !editing ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Rename session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => setEditing(true)}>\n <Pencil className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(row)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\n/**\n * Inline rename. Enter commits, Escape cancels, blur commits — but only a blur\n * that is still inside this document: switching windows must not silently commit,\n * nor kill the editor in the frame it opened.\n */\nfunction NameEditor({\n initial,\n onCommit,\n onCancel,\n}: {\n initial: string\n onCommit: (title: string) => void\n onCancel: () => void\n}) {\n const [value, setValue] = useState(initial)\n const ref = useRef<HTMLInputElement>(null)\n useEffect(() => {\n ref.current?.select()\n }, [])\n return (\n <input\n ref={ref}\n value={value}\n autoFocus\n aria-label='Session name'\n // The row is a button; a click in here must not select the session.\n onClick={(e) => e.stopPropagation()}\n onChange={(e) => setValue(e.target.value)}\n onBlur={() => (document.hasFocus() ? onCommit(value.trim()) : undefined)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') onCommit(value.trim())\n else if (e.key === 'Escape') onCancel()\n e.stopPropagation()\n }}\n className='-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-0.5 text-body-sm font-medium text-fg-1 outline-none'\n />\n )\n}\n\n/** A multi-select facet. Empty = no filter, which is why the trigger reads the\n * facet's name rather than \"All\": nothing is being excluded. */\nfunction FacetSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string[]\n options: { value: string; label: string }[]\n onChange: (value: string[]) => void\n}) {\n return (\n <div className='flex items-center gap-1'>\n <Select multiple value={value} onValueChange={(v) => onChange(v as string[])}>\n <SelectTrigger aria-label={label} className='min-w-28'>\n <SelectValue>\n {value.length === 0\n ? label\n : value.length === 1\n ? (options.find((o) => o.value === value[0])?.label ?? label)\n : `${label} · ${value.length}`}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {value.length > 0 ? (\n <Button variant='ghost' size='icon-sm' aria-label={`Clear ${label}`} onClick={() => onChange([])}>\n <X className='size-3 text-fg-4' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nfunction OneOfSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string\n options: readonly { value: string; label: string }[]\n onChange: (value: string) => void\n}) {\n return (\n <Select value={value} onValueChange={(v) => onChange(v as string)}>\n <SelectTrigger aria-label={label} className='min-w-28'>\n <SelectValue>{options.find((o) => o.value === value)?.label ?? label}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { r as SessionPanelProps } from "./SessionPanel-CyhygZx_.mjs";
1
+ import { r as SessionPanelProps } from "./SessionPanel-Dy9lQrOV.mjs";
2
2
  import * as _$react from "react";
3
3
  import { CSSProperties } from "react";
4
4
  import { OpenFile, UseHostFileSearchResult, UseHostFileTreeResult } from "@workerdeck/react";
@@ -11,10 +11,32 @@ interface SessionWorkspaceProps {
11
11
  /** Passed straight through to {@link SessionPanel} — including the render-prop
12
12
  * form that claims the session-actions menu. */
13
13
  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
+ transcriptVariant?: SessionPanelProps['transcriptVariant'];
23
+ transcriptDensity?: SessionPanelProps['transcriptDensity'];
24
+ unseen?: SessionPanelProps['unseen'];
25
+ onVitals?: SessionPanelProps['onVitals'];
14
26
  /** Rail width in pixels on first render. */
15
27
  defaultRailWidth?: number;
16
28
  /** Start with the file rail collapsed even on a wide viewport. */
17
29
  defaultRailCollapsed?: boolean;
30
+ /**
31
+ * The rail moved. Paired with the two defaults so an embedder can persist the
32
+ * layout — the workspace deliberately does not, because *where* to keep it (a
33
+ * Memento, localStorage, a workspace file) is the embedder's call, and a
34
+ * component that picked one would be wrong in the other hosts.
35
+ */
36
+ onRailChange?: (rail: {
37
+ width: number;
38
+ collapsed: boolean;
39
+ }) => void;
18
40
  className?: string;
19
41
  }
20
42
  /**
@@ -42,8 +64,13 @@ declare function SessionWorkspace({
42
64
  client,
43
65
  sessionId,
44
66
  header,
67
+ transcriptVariant,
68
+ transcriptDensity,
69
+ unseen,
70
+ onVitals,
45
71
  defaultRailWidth,
46
72
  defaultRailCollapsed,
73
+ onRailChange,
47
74
  className
48
75
  }: SessionWorkspaceProps): _$react.JSX.Element;
49
76
  //#endregion
@@ -1,4 +1,4 @@
1
- import { $ as TooltipProvider, Ct as cn, J as Spinner, K as Splitter, Z as Tip, t as SessionPanel, vt as Input, xt as Button } from "./SessionPanel-_U8tjX29.mjs";
1
+ import { $ as Tip, Ct as Button, J as Splitter, Tt as cn, X as Spinner, bt as Input, t as SessionPanel, tt as TooltipProvider } from "./SessionPanel-NQ8ksCfj.mjs";
2
2
  import { n as formatBytes } from "./format-DqR56Y8l.mjs";
3
3
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
4
4
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -694,7 +694,7 @@ const EDITOR_MIN = 120;
694
694
  * The conditional children before it are `? :` expressions that leave a null in
695
695
  * their slot, which is exactly what keeps its index stable.
696
696
  */
697
- function SessionWorkspace({ client, sessionId, header, defaultRailWidth = 260, defaultRailCollapsed, className }) {
697
+ function SessionWorkspace({ client, sessionId, header, transcriptVariant, transcriptDensity, unseen, onVitals, defaultRailWidth = 260, defaultRailCollapsed, onRailChange, className }) {
698
698
  const { info } = useSessionInfo(client, sessionId);
699
699
  const cwd = info?.cwd;
700
700
  const tree = useHostFileTree(client, cwd);
@@ -710,6 +710,14 @@ function SessionWorkspace({ client, sessionId, header, defaultRailWidth = 260, d
710
710
  const wide = useIsWide();
711
711
  const [railCollapsed, setRailCollapsed] = useState(defaultRailCollapsed ?? false);
712
712
  const [railWidth, setRailWidth] = useState(defaultRailWidth);
713
+ const onRailChangeRef = useRef(onRailChange);
714
+ onRailChangeRef.current = onRailChange;
715
+ useEffect(() => {
716
+ onRailChangeRef.current?.({
717
+ width: railWidth,
718
+ collapsed: railCollapsed
719
+ });
720
+ }, [railWidth, railCollapsed]);
713
721
  const [editorHeight, setEditorHeight] = useState(360);
714
722
  const hasFiles = files.files.length > 0;
715
723
  const overlayRail = !wide;
@@ -800,6 +808,10 @@ function SessionWorkspace({ client, sessionId, header, defaultRailWidth = 260, d
800
808
  client,
801
809
  sessionId,
802
810
  header: hoisted,
811
+ transcriptVariant,
812
+ transcriptDensity,
813
+ unseen,
814
+ onVitals,
803
815
  className: "min-h-0 flex-1"
804
816
  })
805
817
  ]