@workerdeck/ui 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/build/{SessionPanel-B9CHoq8x.d.mts → SessionPanel-CnNYEX80.d.mts} +13 -1
- package/build/{SessionPanel-DII9MmQ8.mjs → SessionPanel-DMPhsNlW.mjs} +1015 -332
- package/build/SessionPanel-DMPhsNlW.mjs.map +1 -0
- package/build/index.d.mts +54 -3
- package/build/index.mjs +240 -17
- package/build/index.mjs.map +1 -1
- package/build/workspace.d.mts +1 -1
- package/build/workspace.mjs +1 -1
- package/package.json +8 -6
- package/src/components/agent/ProjectIcon.tsx +119 -0
- package/src/components/agent/SessionBrowser.tsx +84 -3
- package/src/components/agent/SessionPanel.tsx +25 -0
- package/src/components/agent/ToolCallCard.tsx +72 -5
- package/src/components/agent/Transcript.tsx +77 -7
- package/src/components/agent/tool-result-fetch.tsx +36 -0
- package/src/components/agent/tool-result-image.tsx +209 -0
- package/src/components/agent/transcript-rows.ts +113 -22
- package/src/components/terminal/TerminalTranscript.tsx +80 -2
- package/src/components/terminal/blocks.ts +232 -0
- package/src/components/terminal/height.ts +49 -6
- package/src/components/terminal/image-box.ts +53 -0
- package/src/components/terminal/items.tsx +115 -78
- package/src/components/terminal/result-preview.ts +20 -6
- package/src/components/terminal/scrubber.tsx +146 -25
- package/src/components/terminal/tool-run.ts +133 -0
- package/src/index.ts +2 -1
- package/src/styles/terminal.css +75 -5
- package/build/SessionPanel-DII9MmQ8.mjs.map +0 -1
package/build/index.mjs.map
CHANGED
|
@@ -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/ui/Empty.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/terminal/StatusLine.tsx","../src/components/agent/SessionList.tsx","../src/components/agent/SessionBrowser.tsx","../src/components/agent/EngineIcon.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","import type { ReactNode } from 'react'\nimport { Button } from './Button.tsx'\n\n/**\n * A view with nothing in it: icon, title, description, and at most one action.\n *\n * Centered and given room, because an empty view is the first thing a new\n * install shows and a flush-left sentence in the top corner reads like a bug\n * report. It lives here rather than in one client because every panel that can\n * be empty should be empty the same way — the extension's views and the\n * dashboard's four sidebars are the same shape of thing.\n *\n * **At most one action, and never one the view header already offers.** Creating\n * a session and adding a gateway are the `+` in the native title bar, exclusively\n * — so those empty states point at it in words rather than growing a second\n * button that does the same thing two inches lower. A button here is for the way\n * out of a state the header has no answer to: clearing a filter, widening a\n * scope.\n */\nexport function Empty({\n icon,\n title,\n description,\n action,\n onAction,\n}: {\n icon: ReactNode\n title: string\n description?: ReactNode\n action?: string\n onAction?: () => void\n}) {\n return (\n <div className='flex flex-col items-center gap-2 px-5 py-8 text-center'>\n <div className='text-fg-4 opacity-60 [&_svg]:size-7'>{icon}</div>\n <p className='text-body-sm font-medium text-fg-2'>{title}</p>\n {description ? (\n <p className='max-w-[26ch] text-balance text-label leading-relaxed text-fg-4'>\n {description}\n </p>\n ) : null}\n {action && onAction ? (\n <Button variant='outline' size='sm' className='mt-1' onClick={onAction}>\n {action}\n </Button>\n ) : null}\n </div>\n )\n}\n\n/** A key or icon named inline in an empty state's description — \"use the + above\". */\nexport function EmptyKey({ children }: { children: ReactNode }) {\n return (\n <span className='rounded border border-border px-1 font-mono text-fg-3'>{children}</span>\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 { RateLimitInfo } from '@workerdeck/protocol'\nimport type { ConnectionState, TranscriptState } from '@workerdeck/react'\nimport { formatCost, formatTokens } from '../../lib/format.ts'\nimport {\n contextSeverity,\n meterSeverity,\n modelLabel,\n statusPresentation,\n tightestWindow,\n windowLabel,\n type StatusSeverity,\n} from '../../lib/status.ts'\nimport { Ink, Row, type Tone } from './row.tsx'\n\n/**\n * The session's readings, as one line.\n *\n * The styled bar puts each of these in its own affordance — a progress ring, a\n * badge, a tooltip — which is right for a dashboard and wrong here for the usual\n * reason: a terminal has one line height, and five widgets of five different\n * heights is five different rhythms in a row that is supposed to be quiet. So\n * they are words, `·`-separated, exactly as the CLI's own status line writes\n * them.\n *\n * The readings themselves come from the same helpers the styled bar uses\n * (`lib/status.ts`), and that matters more than it looks: `statusPresentation`\n * is where the rule lives that **a dropped socket outranks the session status**,\n * because a status held over a dead socket is a stale reading being presented as\n * a live one. Re-deriving that here would be a second answer to a question that\n * already has one.\n */\n\n/** Severity → tone. The 80/95 thresholds are `lib/status.ts`'s, not new ones. */\nconst SEVERITY_TONE: Record<StatusSeverity, Tone> = {\n none: 'dim',\n warning: 'yellow',\n error: 'red',\n}\n\nexport interface TerminalStatusLineProps {\n state: TranscriptState\n /** Plan windows to read from, when they should not be the session's own — the\n * gateway's per-profile state merged over this transcript's. See\n * {@link StatusBarProps.rateLimits}. Absent = `state.rateLimits`. */\n rateLimits?: Record<string, RateLimitInfo>\n /** How the client is doing at reaching the gateway. Wins the status slot when\n * the socket is down — see above. */\n connection?: ConnectionState\n /** Opens the panel that answers each reading's own question. Omit and the\n * reading is text rather than something to press. */\n onOpenStatus?: () => void\n onOpenContext?: () => void\n onOpenUsage?: () => void\n}\n\n/** One reading. A button only when it leads somewhere — a segment that looks\n * pressable and does nothing is worse than plain text. */\nfunction Reading({\n tone,\n onPress,\n label,\n children,\n}: {\n tone?: Tone\n onPress?: () => void\n label?: string\n children: React.ReactNode\n}) {\n if (!onPress) return <Ink tone={tone}>{children}</Ink>\n return (\n <button type='button' className='term-reading' onClick={onPress} title={label}>\n <Ink tone={tone}>{children}</Ink>\n </button>\n )\n}\n\nexport function TerminalStatusLine({\n state,\n rateLimits,\n connection,\n onOpenStatus,\n onOpenContext,\n onOpenUsage,\n}: TerminalStatusLineProps) {\n const presentation = statusPresentation({ status: state.status, connection })\n const usage = state.contextUsage\n const window = tightestWindow(rateLimits ?? state.rateLimits)\n const model = modelLabel({ model: state.model, models: state.models ?? [] })\n\n const parts: React.ReactNode[] = [\n <Reading\n key='status'\n tone={SEVERITY_TONE[presentation.severity]}\n onPress={onOpenStatus}\n label='Session status'>\n {presentation.label}\n </Reading>,\n ]\n\n if (usage) {\n parts.push(\n <Reading\n key='context'\n tone={SEVERITY_TONE[contextSeverity(usage)]}\n onPress={onOpenContext}\n label='Context window'>\n {Math.round(usage.percentage)}% ctx ({formatTokens(usage.totalTokens)})\n </Reading>,\n )\n }\n\n if (window) {\n parts.push(\n <Reading\n key='usage'\n tone={SEVERITY_TONE[meterSeverity(window.info.utilization)]}\n onPress={onOpenUsage}\n label='Plan usage'>\n {Math.round(window.info.utilization ?? 0)}% {windowLabel(window.key)}\n </Reading>,\n )\n }\n\n // Cost last of the numbers: it is the reading you check, never the one you act\n // on, and nothing about a turn changes because of it.\n if (state.totalCostUsd > 0) {\n parts.push(\n <Ink key='cost' tone='faint'>\n {formatCost(state.totalCostUsd)}\n </Ink>,\n )\n }\n\n if (model) {\n parts.push(\n <Ink key='model' tone='faint'>\n {model}\n </Ink>,\n )\n }\n if (state.permissionMode && state.permissionMode !== 'default') {\n parts.push(\n <Ink key='mode' tone='yellow'>\n {state.permissionMode}\n </Ink>,\n )\n }\n\n return (\n <Row data-slot='status-line' tone='dim'>\n {parts.map((part, index) => (\n <span key={index}>\n {index > 0 ? <Ink tone='faint'> · </Ink> : null}\n {part}\n </span>\n ))}\n </Row>\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 {\n BellRing,\n CircleAlert,\n CircleSlash,\n Layers,\n Moon,\n PauseCircle,\n Pencil,\n Search,\n SearchX,\n Trash2,\n X,\n} 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 { Button } from '../ui/Button.tsx'\nimport { Empty } from '../ui/Empty.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue } from '../ui/Select.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime, friendlyModel } from '../../lib/format.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 /**\n * Whether the search + facet bar is shown. Defaults to `true` — a list that\n * *is* the screen shows its controls.\n *\n * A host with somewhere better to put the toggle (a view title bar) passes\n * `false` and owns the boolean itself, the way the VS Code extension does: the\n * key lives where the commands do. Two rules come with it, and they are why\n * this hides only the bar and nothing else — **closing the bar never clears\n * the filters**, and the subset line below it renders either way, so a list\n * filtered by a control you can't currently see still says so.\n */\n showControls?: boolean\n className?: string\n}\n\n/**\n * How a list row is drawn, in one place, so `SidebarRow` in `web` matches this\n * exactly rather than approximating it — the dashboard's other three sidebars\n * are that component, and a sessions list that hovered differently from the\n * gateways list beside it would read as a different product.\n *\n * Two rules are load-bearing:\n *\n * - **Fill means hover, and only hover.** It stays on the row whether or not\n * the row is selected, because a selected row still has to answer the\n * pointer. Selection gets the gutter instead.\n * - **`ml-0` on the selected row is not cosmetic.** It hands the accent border\n * the 4px the margin was holding, so the text does not shift sideways as a\n * row becomes the selected one. The squared left corners are what let the bar\n * sit flush against the sidebar edge.\n */\nexport function rowShapeClass(active: boolean): string {\n return cn(\n 'px-2 py-1.5 hover:bg-row-hover',\n active ? 'mr-1 ml-0 rounded-r-md border-l-4 border-l-accent' : 'mx-1 rounded-md',\n )\n}\n\nexport function SessionBrowser({\n rows,\n config,\n onConfigChange,\n scope,\n activeId,\n onSelect,\n onDelete,\n onRename,\n emptyState,\n showControls = true,\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 {/* One control per row, label left, input right — the shape VS Code uses\n for its own filter surfaces. A wrapping row of pill-shaped selects\n reflows into an unpredictable number of lines as facets appear and\n disappear; a column of labelled rows is the same height every time and\n reads at sidebar width, which is the only width this has. */}\n <div className={cn('flex flex-col gap-1.5 px-2', !showControls && 'hidden')}>\n <div className='relative'>\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 <FilterRow label='State'>\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 </FilterRow>\n {adapters.length > 1 ? (\n <FilterRow label='Engine'>\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 </FilterRow>\n ) : null}\n {gateways.length > 1 ? (\n <FilterRow label='Gateway'>\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 </FilterRow>\n ) : null}\n <FilterRow label='Group'>\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 </FilterRow>\n <FilterRow label='Sort'>\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 </FilterRow>\n </div>\n\n {subset ? (\n <div className='flex items-center gap-2 px-3 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 icon={<Layers />} title='No sessions yet' />)\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 — and\n // only the first has a button, because an action that does nothing is\n // worse than none.\n hasFacetFilter(config) ? (\n <Empty\n icon={<SearchX />}\n title='No matches'\n description='No session matches the current search and filters.'\n action='Clear filters'\n onAction={() => onConfigChange(clearFilters(config))}\n />\n ) : (\n <Empty icon={<Layers />} title='Nothing here' description='No session to show.' />\n )\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-3 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\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 [editing, setEditing] = useState(false)\n\n // What it is and what it has spent, in one line — the same set the extension\n // shows, joined the same way, so the two lists read as one product.\n const folder = info.cwd.split('/').filter(Boolean).pop() ?? info.cwd\n const details = [\n showGateway ? row.hostName : undefined,\n friendlyModel(info.model),\n folder,\n info.profile ? `@${info.profile}` : undefined,\n formatCost(info.totalCostUsd),\n ].filter(Boolean)\n\n return (\n <div\n data-slot='session-row'\n data-active={active || undefined}\n // Selection lives on the whole row, not on the two text buttons inside\n // it: the age, the unread badge and the state glyph sit outside them, and\n // a row where a third of the surface silently does nothing is a row that\n // feels broken. The buttons stay — they are what makes the row reachable\n // by keyboard — but their activation now reaches this handler by\n // bubbling, so there is one code path and no double-fire. Anything that\n // is its own action (rename, close, the name editor) stops the event.\n onClick={() => !editing && onSelect?.(row)}\n className={cn(\n 'group flex cursor-pointer flex-col gap-0.5 text-left transition-colors',\n rowShapeClass(active === true),\n )}>\n {/* Line one: what you scan the list by on the left, how it is doing on the\n right, state last. */}\n <div className='flex items-center gap-1.5'>\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 className={cn(\n 'min-w-0 flex-1 truncate text-left text-body-sm outline-none',\n active ? 'font-medium text-fg-1' : 'text-fg-2',\n )}>\n {sessionLabel(info)}\n </button>\n )}\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 <span\n title={`${row.unseen} new`}\n className='shrink-0 rounded-full bg-accent px-1.5 text-label text-accent-fg'>\n {row.unseen}\n </span>\n ) : null}\n <span className='shrink-0 text-label text-fg-4'>\n {formatRelativeTime(info.lastActivityAt ?? info.createdAt)}\n </span>\n <SessionStatusIcon row={row} />\n </div>\n\n {/* Line two: what it is, with the actions at the far right — away from the\n state icon, and away from the title you are actually reading. */}\n <div className='flex items-center gap-1 text-label text-fg-4'>\n <button\n type='button'\n tabIndex={-1}\n className='min-w-0 flex-1 truncate text-left font-mono outline-none'>\n {details.join(' · ')}\n </button>\n {onRename && !editing ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Rename session'\n className='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n setEditing(true)\n }}>\n <Pencil className='size-3 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='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n onDelete(row)\n }}>\n <Trash2 className='size-3 text-fg-3' />\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n\n/**\n * State as one glyph on the right edge — a ringing bell when it wants a human, a\n * spinner while it works, a moon when it is only sleeping. Replaces the text\n * badge: in a sidebar the word costs more room than it earns, and the states\n * that matter are the two you can recognise without reading.\n */\nexport function SessionStatusIcon({ row }: { row: SessionRow }) {\n const { info } = row\n if (info.pendingPermissionCount > 0 || info.status === 'awaiting_approval') {\n return <BellRing className='size-3.5 shrink-0 animate-pulse text-warning' />\n }\n if (info.status === 'running' || info.status === 'starting') {\n return <Spinner className='size-3.5 shrink-0 text-info' />\n }\n switch (info.status) {\n case 'failed':\n return <CircleAlert className='size-3.5 shrink-0 text-danger' />\n case 'closed':\n return <CircleSlash className='size-3.5 shrink-0 text-fg-4' />\n case 'parked':\n return <PauseCircle className='size-3.5 shrink-0 text-fg-3' />\n default:\n return <Moon className='size-3.5 shrink-0 text-fg-4' />\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. */\n/**\n * One labelled control row. The label column is fixed so every control starts\n * on the same x — the thing that makes a stack of them read as a form rather\n * than as five unrelated widgets.\n */\nfunction FilterRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className='flex items-center gap-2'>\n <span aria-hidden className='w-14 shrink-0 truncate text-label text-fg-3'>\n {label}\n </span>\n <div className='min-w-0 flex-1'>{children}</div>\n </div>\n )\n}\n\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-0 flex-1'>\n <SelectValue>\n {/* \"All\" rather than the label, which the row already carries. */}\n {value.length === 0\n ? 'All'\n : value.length === 1\n ? (options.find((o) => o.value === value[0])?.label ?? 'All')\n : `${value.length} selected`}\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='w-full min-w-0'>\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","import { cn } from '../../lib/utils.ts'\n\n/**\n * The engine's own mark, monochrome.\n *\n * Inlined rather than depended on: the React package these come from\n * (`@lobehub/icons`) pulls antd, `@lobehub/ui` and a second copy of\n * lucide-react — an absurd amount of bundle for a few glyphs in a sidebar.\n * These are paths from `@lobehub/icons-static-svg` (MIT, © LobeHub), already\n * `fill=\"currentColor\"` single-path marks, so they take the row's colour like\n * any other glyph and need no light/dark variants.\n *\n * The marks are trademarks of their owners (Anthropic, OpenAI, Google,\n * DeepSeek, Moonshot), used here only to identify which engine a session runs\n * on.\n *\n * Codex is drawn as OpenAI: the session's engine *is* OpenAI's, and OpenAI's\n * mark is the one a person recognises at 12px.\n */\nconst PATHS: Record<string, { title: string; d: string }> = {\n claude: {\n title: 'Claude',\n d: 'M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z',\n },\n codex: {\n title: 'OpenAI',\n d: 'M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z',\n },\n gemini: {\n title: 'Gemini',\n d: 'M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z',\n },\n deepseek: {\n title: 'DeepSeek',\n d: 'M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z',\n },\n moonshot: {\n title: 'Moonshot',\n d: 'M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z',\n },\n}\n\n/**\n * Which mark a session wears.\n *\n * The two first-party engines are named directly. A `provider` session is\n * whatever the host wired up, so its *model* is the only thing that says whose\n * it is — sniffed loosely on purpose (`gemini-2.5-pro`, `deepseek-chat`,\n * `kimi-k2`), and falling through to no mark rather than guessing wrong.\n */\nexport function engineMark(engine: string, model?: string): string | undefined {\n if (engine === 'claude') return 'claude'\n if (engine === 'codex') return 'codex'\n const id = model?.toLowerCase() ?? ''\n if (!id) return undefined\n if (id.includes('gemini')) return 'gemini'\n if (id.includes('deepseek')) return 'deepseek'\n if (id.includes('moonshot') || id.includes('kimi')) return 'moonshot'\n if (id.includes('claude')) return 'claude'\n if (id.startsWith('gpt') || id.startsWith('o1') || id.startsWith('o3') || id.includes('openai')) {\n return 'codex'\n }\n return undefined\n}\n\nexport function EngineIcon({\n engine,\n model,\n className,\n}: {\n engine: string\n model?: string\n className?: string\n}) {\n const mark = PATHS[engineMark(engine, model) ?? '']\n // An engine this build has never heard of still gets a slot, so the second\n // line doesn't reflow around a missing glyph.\n if (!mark) {\n return (\n <span aria-hidden title={engine} className={cn('shrink-0 text-fg-3', className)}>\n ·\n </span>\n )\n }\n return (\n <svg\n viewBox='0 0 24 24'\n fill='currentColor'\n fillRule='evenodd'\n role='img'\n aria-label={mark.title}\n className={cn('size-3 shrink-0 text-fg-3', className)}>\n <title>{mark.title}</title>\n <path d={mark.d} />\n </svg>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["AlertDialogPrimitive","SonnerToaster","Map"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/ui/Empty.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/terminal/StatusLine.tsx","../src/components/agent/SessionList.tsx","../src/components/agent/ProjectIcon.tsx","../src/components/agent/SessionBrowser.tsx","../src/components/agent/EngineIcon.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","import type { ReactNode } from 'react'\nimport { Button } from './Button.tsx'\n\n/**\n * A view with nothing in it: icon, title, description, and at most one action.\n *\n * Centered and given room, because an empty view is the first thing a new\n * install shows and a flush-left sentence in the top corner reads like a bug\n * report. It lives here rather than in one client because every panel that can\n * be empty should be empty the same way — the extension's views and the\n * dashboard's four sidebars are the same shape of thing.\n *\n * **At most one action, and never one the view header already offers.** Creating\n * a session and adding a gateway are the `+` in the native title bar, exclusively\n * — so those empty states point at it in words rather than growing a second\n * button that does the same thing two inches lower. A button here is for the way\n * out of a state the header has no answer to: clearing a filter, widening a\n * scope.\n */\nexport function Empty({\n icon,\n title,\n description,\n action,\n onAction,\n}: {\n icon: ReactNode\n title: string\n description?: ReactNode\n action?: string\n onAction?: () => void\n}) {\n return (\n <div className='flex flex-col items-center gap-2 px-5 py-8 text-center'>\n <div className='text-fg-4 opacity-60 [&_svg]:size-7'>{icon}</div>\n <p className='text-body-sm font-medium text-fg-2'>{title}</p>\n {description ? (\n <p className='max-w-[26ch] text-balance text-label leading-relaxed text-fg-4'>\n {description}\n </p>\n ) : null}\n {action && onAction ? (\n <Button variant='outline' size='sm' className='mt-1' onClick={onAction}>\n {action}\n </Button>\n ) : null}\n </div>\n )\n}\n\n/** A key or icon named inline in an empty state's description — \"use the + above\". */\nexport function EmptyKey({ children }: { children: ReactNode }) {\n return (\n <span className='rounded border border-border px-1 font-mono text-fg-3'>{children}</span>\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 { RateLimitInfo } from '@workerdeck/protocol'\nimport type { ConnectionState, TranscriptState } from '@workerdeck/react'\nimport { formatCost, formatTokens } from '../../lib/format.ts'\nimport {\n contextSeverity,\n meterSeverity,\n modelLabel,\n statusPresentation,\n tightestWindow,\n windowLabel,\n type StatusSeverity,\n} from '../../lib/status.ts'\nimport { Ink, Row, type Tone } from './row.tsx'\n\n/**\n * The session's readings, as one line.\n *\n * The styled bar puts each of these in its own affordance — a progress ring, a\n * badge, a tooltip — which is right for a dashboard and wrong here for the usual\n * reason: a terminal has one line height, and five widgets of five different\n * heights is five different rhythms in a row that is supposed to be quiet. So\n * they are words, `·`-separated, exactly as the CLI's own status line writes\n * them.\n *\n * The readings themselves come from the same helpers the styled bar uses\n * (`lib/status.ts`), and that matters more than it looks: `statusPresentation`\n * is where the rule lives that **a dropped socket outranks the session status**,\n * because a status held over a dead socket is a stale reading being presented as\n * a live one. Re-deriving that here would be a second answer to a question that\n * already has one.\n */\n\n/** Severity → tone. The 80/95 thresholds are `lib/status.ts`'s, not new ones. */\nconst SEVERITY_TONE: Record<StatusSeverity, Tone> = {\n none: 'dim',\n warning: 'yellow',\n error: 'red',\n}\n\nexport interface TerminalStatusLineProps {\n state: TranscriptState\n /** Plan windows to read from, when they should not be the session's own — the\n * gateway's per-profile state merged over this transcript's. See\n * {@link StatusBarProps.rateLimits}. Absent = `state.rateLimits`. */\n rateLimits?: Record<string, RateLimitInfo>\n /** How the client is doing at reaching the gateway. Wins the status slot when\n * the socket is down — see above. */\n connection?: ConnectionState\n /** Opens the panel that answers each reading's own question. Omit and the\n * reading is text rather than something to press. */\n onOpenStatus?: () => void\n onOpenContext?: () => void\n onOpenUsage?: () => void\n}\n\n/** One reading. A button only when it leads somewhere — a segment that looks\n * pressable and does nothing is worse than plain text. */\nfunction Reading({\n tone,\n onPress,\n label,\n children,\n}: {\n tone?: Tone\n onPress?: () => void\n label?: string\n children: React.ReactNode\n}) {\n if (!onPress) return <Ink tone={tone}>{children}</Ink>\n return (\n <button type='button' className='term-reading' onClick={onPress} title={label}>\n <Ink tone={tone}>{children}</Ink>\n </button>\n )\n}\n\nexport function TerminalStatusLine({\n state,\n rateLimits,\n connection,\n onOpenStatus,\n onOpenContext,\n onOpenUsage,\n}: TerminalStatusLineProps) {\n const presentation = statusPresentation({ status: state.status, connection })\n const usage = state.contextUsage\n const window = tightestWindow(rateLimits ?? state.rateLimits)\n const model = modelLabel({ model: state.model, models: state.models ?? [] })\n\n const parts: React.ReactNode[] = [\n <Reading\n key='status'\n tone={SEVERITY_TONE[presentation.severity]}\n onPress={onOpenStatus}\n label='Session status'>\n {presentation.label}\n </Reading>,\n ]\n\n if (usage) {\n parts.push(\n <Reading\n key='context'\n tone={SEVERITY_TONE[contextSeverity(usage)]}\n onPress={onOpenContext}\n label='Context window'>\n {Math.round(usage.percentage)}% ctx ({formatTokens(usage.totalTokens)})\n </Reading>,\n )\n }\n\n if (window) {\n parts.push(\n <Reading\n key='usage'\n tone={SEVERITY_TONE[meterSeverity(window.info.utilization)]}\n onPress={onOpenUsage}\n label='Plan usage'>\n {Math.round(window.info.utilization ?? 0)}% {windowLabel(window.key)}\n </Reading>,\n )\n }\n\n // Cost last of the numbers: it is the reading you check, never the one you act\n // on, and nothing about a turn changes because of it.\n if (state.totalCostUsd > 0) {\n parts.push(\n <Ink key='cost' tone='faint'>\n {formatCost(state.totalCostUsd)}\n </Ink>,\n )\n }\n\n if (model) {\n parts.push(\n <Ink key='model' tone='faint'>\n {model}\n </Ink>,\n )\n }\n if (state.permissionMode && state.permissionMode !== 'default') {\n parts.push(\n <Ink key='mode' tone='yellow'>\n {state.permissionMode}\n </Ink>,\n )\n }\n\n return (\n <Row data-slot='status-line' tone='dim'>\n {parts.map((part, index) => (\n <span key={index}>\n {index > 0 ? <Ink tone='faint'> · </Ink> : null}\n {part}\n </span>\n ))}\n </Row>\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 type { ProjectIcon as ProjectIconSpec } from '@workerdeck/protocol'\nimport {\n Anchor, Atom, Beaker, Bike, Binary, Blocks, Bot, Box, Brain, Briefcase, Brush, Bug,\n Building2, Cable, Camera, Car, Cat, CircuitBoard, Cloud, Code, Coffee, Cog, Compass,\n Container, Cpu, CreditCard, Database, Diamond, Dog, Feather, Film, Flame, FlaskConical,\n Folder, Gamepad2, Gauge, Gem, Ghost, Gift, GitBranch, Globe, GraduationCap, Hammer,\n Hexagon, House, Image, Key, Landmark, Laptop, Layers, Leaf, Library, Lightbulb, Lock,\n Mail, Map, MessageCircle, Mic, Monitor, Moon, Mountain, Music, Network, Newspaper,\n Notebook, Orbit, Package, Palette, PenTool, Phone, PiggyBank, Plane, Plug, Puzzle,\n Radar, Radio, Receipt, Rocket, Ruler, Satellite, Scale, Scissors, Server, Shield, Ship,\n ShoppingCart, Signal, Smartphone, Sparkles, Sprout, Square, Star, Sun, Table, Tag,\n Target, Telescope, Terminal, TestTube, TreePine, Trophy, Truck, Umbrella, Users, Wallet,\n Wand, Waves, Webhook, Wifi, Wrench, Zap,\n} from 'lucide-react'\nimport type { ComponentType } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * A project's icon — the render side of protocol's `ProjectIcon`, for a list\n * row or a group header.\n *\n * **The glyph arm is a curated set, and that is the design rather than a\n * shortcut.** The gateway validates a glyph name by *shape* only (lucide's\n * lowercase-kebab convention) and explicitly declines to grow an icon catalog,\n * so the contract already says a client must fall back on a name it does not\n * know.\n *\n * The alternative was measured rather than argued, against the VS Code\n * sidebar's own bundle: `sidebar.js` is **31 KB** with one glyph, **77 KB**\n * with the 110 below (~418 bytes each), and **927 KB** with a\n * `import * as` over lucide's ~1,600 — which is what it costs, because a\n * namespace import defeats tree-shaking by construction. (`DynamicIcon` is the\n * third option and worse in a different currency: ~1,600 chunk files shipped\n * inside the `.vsix`.) Twelve times the bundle to render a name nobody has\n * declared yet is not a trade worth making; 46 KB to cover the names people\n * actually use is.\n *\n * The set is chosen to cover what people call a project; an unlisted-but-valid\n * name draws {@link Folder}, which is what the row drew before this feature\n * existed. **Grow it freely** — each addition is ~400 bytes and the fallback\n * means a missing one is a shrug, not a hole.\n *\n * **The image arm draws nothing until its bytes arrive**, and takes them as a\n * resolved `src` rather than fetching: the wire carries only an address\n * (media type, size, content hash), and *who* fetches it differs per client —\n * a VS Code webview cannot reach a gateway at all and must be handed a data\n * URL by its extension host, while a browser can fetch and `createObjectURL`.\n * A component that fetched would be wrong on one of them. Absent `src` renders\n * nothing rather than a placeholder box: an icon is decoration beside a name\n * that is already there, and a box that becomes a picture a beat later is more\n * movement than the picture is worth.\n *\n * One thing an image icon cannot do, and a glyph can: **take the row's\n * colour.** An `<img>`-embedded SVG is its own document, so `currentColor` does\n * not reach it and its own `prefers-color-scheme` resolves against the *OS*\n * rather than the host's theme (measured: it does resolve, which is the\n * surprise — so a monochrome mark tuned for both schemes is right on a dark OS\n * in a dark editor and wrong on a light OS in a dark one). A repo that wants a\n * mark that always matches its row should declare a glyph; one that wants its\n * brand should declare the image and accept that it is a picture.\n */\nconst GLYPHS: Record<string, ComponentType<{ className?: string }>> = {\n anchor: Anchor, atom: Atom, beaker: Beaker, bike: Bike, binary: Binary, blocks: Blocks,\n bot: Bot, box: Box, brain: Brain, briefcase: Briefcase, brush: Brush, bug: Bug,\n 'building-2': Building2, cable: Cable, camera: Camera, car: Car, cat: Cat,\n 'circuit-board': CircuitBoard, cloud: Cloud, code: Code, coffee: Coffee, cog: Cog,\n compass: Compass, container: Container, cpu: Cpu, 'credit-card': CreditCard,\n database: Database, diamond: Diamond, dog: Dog, feather: Feather, film: Film,\n flame: Flame, 'flask-conical': FlaskConical, folder: Folder, 'gamepad-2': Gamepad2,\n gauge: Gauge, gem: Gem, ghost: Ghost, gift: Gift, 'git-branch': GitBranch, globe: Globe,\n 'graduation-cap': GraduationCap, hammer: Hammer, hexagon: Hexagon, house: House,\n image: Image, key: Key, landmark: Landmark, laptop: Laptop, layers: Layers, leaf: Leaf,\n library: Library, lightbulb: Lightbulb, lock: Lock, mail: Mail, map: Map,\n 'message-circle': MessageCircle, mic: Mic, monitor: Monitor, moon: Moon,\n mountain: Mountain, music: Music, network: Network, newspaper: Newspaper,\n notebook: Notebook, orbit: Orbit, package: Package, palette: Palette, 'pen-tool': PenTool,\n phone: Phone, 'piggy-bank': PiggyBank, plane: Plane, plug: Plug, puzzle: Puzzle,\n radar: Radar, radio: Radio, receipt: Receipt, rocket: Rocket, ruler: Ruler,\n satellite: Satellite, scale: Scale, scissors: Scissors, server: Server, shield: Shield,\n ship: Ship, 'shopping-cart': ShoppingCart, signal: Signal, smartphone: Smartphone,\n sparkles: Sparkles, sprout: Sprout, square: Square, star: Star, sun: Sun, table: Table,\n tag: Tag, target: Target, telescope: Telescope, terminal: Terminal, 'test-tube': TestTube,\n 'tree-pine': TreePine, trophy: Trophy, truck: Truck, umbrella: Umbrella, users: Users,\n wallet: Wallet, wand: Wand, waves: Waves, webhook: Webhook, wifi: Wifi, wrench: Wrench,\n zap: Zap,\n}\n\nexport function ProjectIcon({\n icon,\n src,\n name,\n className,\n}: {\n icon: ProjectIconSpec | undefined\n /** Resolved bytes for the `image` arm — an object URL or a data URL. The\n * caller fetches (see the note above); absent draws nothing. */\n src?: string\n /** The project's name, for the alt text. */\n name?: string\n className?: string\n}) {\n if (!icon) return null\n if (icon.type === 'image') {\n if (!src) return null\n return (\n <img\n src={src}\n alt=''\n aria-hidden\n title={name}\n // `object-contain` because a declared icon is whatever aspect the repo\n // checked in, and a squashed logo reads worse than a letterboxed one.\n className={cn('inline-block size-3 shrink-0 object-contain', className)}\n />\n )\n }\n const Glyph = GLYPHS[icon.name] ?? Folder\n return <Glyph className={cn('inline-block size-3 shrink-0', className)} />\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n BellRing,\n CircleAlert,\n CircleSlash,\n Layers,\n Moon,\n PauseCircle,\n Pencil,\n Search,\n SearchX,\n Trash2,\n X,\n} from 'lucide-react'\nimport {\n STATE_LABELS,\n STATE_ORDER,\n adaptersOf,\n clearFilters,\n filterRows,\n groupRows,\n hasFacetFilter,\n projectLabel,\n projectsOf,\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 { Button } from '../ui/Button.tsx'\nimport { Empty } from '../ui/Empty.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue } from '../ui/Select.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { ProjectIcon } from './ProjectIcon.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime, friendlyModel } from '../../lib/format.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 /**\n * Whether the search + facet bar is shown. Defaults to `true` — a list that\n * *is* the screen shows its controls.\n *\n * A host with somewhere better to put the toggle (a view title bar) passes\n * `false` and owns the boolean itself, the way the VS Code extension does: the\n * key lives where the commands do. Two rules come with it, and they are why\n * this hides only the bar and nothing else — **closing the bar never clears\n * the filters**, and the subset line below it renders either way, so a list\n * filtered by a control you can't currently see still says so.\n */\n showControls?: boolean\n /**\n * Resolved project-icon bytes by content hash — `useProjectIcons`' output.\n *\n * Passed in rather than fetched here for the reason `ProjectIcon` states: the\n * wire carries an *address*, and who can fetch it differs per client. Absent,\n * or a hash not in it yet, simply draws no picture; the project's name is\n * already there.\n */\n projectIcons?: Record<string, string>\n className?: string\n}\n\n/**\n * How a list row is drawn, in one place, so `SidebarRow` in `web` matches this\n * exactly rather than approximating it — the dashboard's other three sidebars\n * are that component, and a sessions list that hovered differently from the\n * gateways list beside it would read as a different product.\n *\n * Two rules are load-bearing:\n *\n * - **Fill means hover, and only hover.** It stays on the row whether or not\n * the row is selected, because a selected row still has to answer the\n * pointer. Selection gets the gutter instead.\n * - **`ml-0` on the selected row is not cosmetic.** It hands the accent border\n * the 4px the margin was holding, so the text does not shift sideways as a\n * row becomes the selected one. The squared left corners are what let the bar\n * sit flush against the sidebar edge.\n */\nexport function rowShapeClass(active: boolean): string {\n return cn(\n 'px-2 py-1.5 hover:bg-row-hover',\n active ? 'mr-1 ml-0 rounded-r-md border-l-4 border-l-accent' : 'mx-1 rounded-md',\n )\n}\n\nexport function SessionBrowser({\n rows,\n config,\n onConfigChange,\n scope,\n activeId,\n onSelect,\n onDelete,\n onRename,\n emptyState,\n showControls = true,\n projectIcons,\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 projects = useMemo(() => projectsOf(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 {/* One control per row, label left, input right — the shape VS Code uses\n for its own filter surfaces. A wrapping row of pill-shaped selects\n reflows into an unpredictable number of lines as facets appear and\n disappear; a column of labelled rows is the same height every time and\n reads at sidebar width, which is the only width this has. */}\n <div className={cn('flex flex-col gap-1.5 px-2', !showControls && 'hidden')}>\n <div className='relative'>\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 <FilterRow label='State'>\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 </FilterRow>\n {adapters.length > 1 ? (\n <FilterRow label='Engine'>\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 </FilterRow>\n ) : null}\n {gateways.length > 1 ? (\n <FilterRow label='Gateway'>\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 </FilterRow>\n ) : null}\n {projects.length > 1 ? (\n <FilterRow label='Project'>\n <FacetSelect\n label='Project'\n value={config.projects ?? []}\n options={projects.map((p) => ({ value: p.key, label: p.label }))}\n onChange={(next) => set({ projects: next })}\n />\n </FilterRow>\n ) : null}\n <FilterRow label='Group'>\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 ...(projects.length > 1 ? [{ value: 'project' as const, label: 'By project' }] : []),\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'By gateway' }] : []),\n ]}\n onChange={(groupBy) => set({ groupBy: groupBy as GroupBy })}\n />\n </FilterRow>\n <FilterRow label='Sort'>\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 ...(projects.length > 1 ? [{ value: 'project' as const, label: 'Project' }] : []),\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'Gateway' }] : []),\n ]}\n onChange={(sortBy) => set({ sortBy: sortBy as SortBy })}\n />\n </FilterRow>\n </div>\n\n {subset ? (\n <div className='flex items-center gap-2 px-3 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 icon={<Layers />} title='No sessions yet' />)\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 — and\n // only the first has a button, because an action that does nothing is\n // worse than none.\n hasFacetFilter(config) ? (\n <Empty\n icon={<SearchX />}\n title='No matches'\n description='No session matches the current search and filters.'\n action='Clear filters'\n onAction={() => onConfigChange(clearFilters(config))}\n />\n ) : (\n <Empty icon={<Layers />} title='Nothing here' description='No session to show.' />\n )\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-center gap-2 px-3 text-label font-medium text-fg-4'>\n {/* Only the project facet has a mark of its own, and a group\n IS one project root, so the first row is a fair source. */}\n {config.groupBy === 'project' ? (\n <ProjectIcon\n icon={group.rows[0]?.info.project?.icon}\n src={iconSrcOf(group.rows[0], projectIcons)}\n name={group.label}\n />\n ) : null}\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 showProject={config.groupBy !== 'project'}\n projectIcons={projectIcons}\n onSelect={onSelect}\n onDelete={onDelete}\n onRename={onRename}\n />\n ))}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n/** The bytes for a row's project icon, if it has an image one and the caller has\n * fetched it yet. Shared by the row and its group header so the two cannot draw\n * different pictures for one project. */\nfunction iconSrcOf(\n row: SessionRow | undefined,\n icons: Record<string, string> | undefined,\n): string | undefined {\n const icon = row?.info.project?.icon\n return icon?.type === 'image' ? icons?.[icon.hash] : undefined\n}\n\ninterface SessionRowItemProps {\n row: SessionRow\n active?: boolean\n showGateway?: boolean\n /** False when the list is already grouped by project — the header has said\n * the name, so the slot goes back to the cwd's basename, which inside a\n * project group is the one thing the header cannot say. The rule `showGateway`\n * follows one facet over. */\n showProject?: boolean\n projectIcons?: Record<string, string>\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 showProject = true,\n projectIcons,\n onSelect,\n onDelete,\n onRename,\n}: SessionRowItemProps) {\n const { info } = row\n const [editing, setEditing] = useState(false)\n\n // What it is and what it has spent, in one line — the same set the extension\n // shows, joined the same way, so the two lists read as one product.\n const folder = info.cwd.split('/').filter(Boolean).pop() ?? info.cwd\n // protocol's own spelling, so this row, the group header above it and the\n // project facet cannot disagree about what a project is called. It falls back\n // to exactly the basename this line drew before the feature existed.\n const project = showProject ? projectLabel(row) : folder\n const projectIcon = showProject ? info.project?.icon : undefined\n const details = [\n showGateway ? row.hostName : undefined,\n friendlyModel(info.model),\n project,\n info.profile ? `@${info.profile}` : undefined,\n formatCost(info.totalCostUsd),\n ].filter(Boolean)\n // Where the icon goes: immediately before the project's own name, wherever\n // that landed in the joined line. Split rather than interleaved as nodes,\n // because everything here is one truncating mono run and a flex of pieces\n // would each shrink a little and leave several half-words.\n const cut = details.indexOf(project)\n\n return (\n <div\n data-slot='session-row'\n data-active={active || undefined}\n // Selection lives on the whole row, not on the two text buttons inside\n // it: the age, the unread badge and the state glyph sit outside them, and\n // a row where a third of the surface silently does nothing is a row that\n // feels broken. The buttons stay — they are what makes the row reachable\n // by keyboard — but their activation now reaches this handler by\n // bubbling, so there is one code path and no double-fire. Anything that\n // is its own action (rename, close, the name editor) stops the event.\n onClick={() => !editing && onSelect?.(row)}\n className={cn(\n 'group flex cursor-pointer flex-col gap-0.5 text-left transition-colors',\n rowShapeClass(active === true),\n )}>\n {/* Line one: what you scan the list by on the left, how it is doing on the\n right, state last. */}\n <div className='flex items-center gap-1.5'>\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 className={cn(\n 'min-w-0 flex-1 truncate text-left text-body-sm outline-none',\n active ? 'font-medium text-fg-1' : 'text-fg-2',\n )}>\n {sessionLabel(info)}\n </button>\n )}\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 <span\n title={`${row.unseen} new`}\n className='shrink-0 rounded-full bg-accent px-1.5 text-label text-accent-fg'>\n {row.unseen}\n </span>\n ) : null}\n <span className='shrink-0 text-label text-fg-4'>\n {formatRelativeTime(info.lastActivityAt ?? info.createdAt)}\n </span>\n <SessionStatusIcon row={row} />\n </div>\n\n {/* Line two: what it is, with the actions at the far right — away from the\n state icon, and away from the title you are actually reading. */}\n <div className='flex items-center gap-1 text-label text-fg-4'>\n <button\n type='button'\n tabIndex={-1}\n className='min-w-0 flex-1 truncate text-left font-mono outline-none'>\n {cut < 0 ? (\n details.join(' · ')\n ) : (\n <>\n {details.slice(0, cut).map((part) => `${part} · `)}\n <ProjectIcon\n icon={projectIcon}\n src={iconSrcOf(row, projectIcons)}\n name={project}\n /* Nudged onto the text baseline: a 12px glyph box against an\n 11px line sits a hair proud without it. */\n className='mr-1 align-[-0.2em]'\n />\n {details.slice(cut).join(' · ')}\n </>\n )}\n </button>\n {onRename && !editing ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Rename session'\n className='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n setEditing(true)\n }}>\n <Pencil className='size-3 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='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n onDelete(row)\n }}>\n <Trash2 className='size-3 text-fg-3' />\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n\n/**\n * State as one glyph on the right edge — a ringing bell when it wants a human, a\n * spinner while it works, a moon when it is only sleeping. Replaces the text\n * badge: in a sidebar the word costs more room than it earns, and the states\n * that matter are the two you can recognise without reading.\n */\nexport function SessionStatusIcon({ row }: { row: SessionRow }) {\n const { info } = row\n if (info.pendingPermissionCount > 0 || info.status === 'awaiting_approval') {\n return <BellRing className='size-3.5 shrink-0 animate-pulse text-warning' />\n }\n if (info.status === 'running' || info.status === 'starting') {\n return <Spinner className='size-3.5 shrink-0 text-info' />\n }\n switch (info.status) {\n case 'failed':\n return <CircleAlert className='size-3.5 shrink-0 text-danger' />\n case 'closed':\n return <CircleSlash className='size-3.5 shrink-0 text-fg-4' />\n case 'parked':\n return <PauseCircle className='size-3.5 shrink-0 text-fg-3' />\n default:\n return <Moon className='size-3.5 shrink-0 text-fg-4' />\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. */\n/**\n * One labelled control row. The label column is fixed so every control starts\n * on the same x — the thing that makes a stack of them read as a form rather\n * than as five unrelated widgets.\n */\nfunction FilterRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className='flex items-center gap-2'>\n <span aria-hidden className='w-14 shrink-0 truncate text-label text-fg-3'>\n {label}\n </span>\n <div className='min-w-0 flex-1'>{children}</div>\n </div>\n )\n}\n\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-0 flex-1'>\n <SelectValue>\n {/* \"All\" rather than the label, which the row already carries. */}\n {value.length === 0\n ? 'All'\n : value.length === 1\n ? (options.find((o) => o.value === value[0])?.label ?? 'All')\n : `${value.length} selected`}\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='w-full min-w-0'>\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","import { cn } from '../../lib/utils.ts'\n\n/**\n * The engine's own mark, monochrome.\n *\n * Inlined rather than depended on: the React package these come from\n * (`@lobehub/icons`) pulls antd, `@lobehub/ui` and a second copy of\n * lucide-react — an absurd amount of bundle for a few glyphs in a sidebar.\n * These are paths from `@lobehub/icons-static-svg` (MIT, © LobeHub), already\n * `fill=\"currentColor\"` single-path marks, so they take the row's colour like\n * any other glyph and need no light/dark variants.\n *\n * The marks are trademarks of their owners (Anthropic, OpenAI, Google,\n * DeepSeek, Moonshot), used here only to identify which engine a session runs\n * on.\n *\n * Codex is drawn as OpenAI: the session's engine *is* OpenAI's, and OpenAI's\n * mark is the one a person recognises at 12px.\n */\nconst PATHS: Record<string, { title: string; d: string }> = {\n claude: {\n title: 'Claude',\n d: 'M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z',\n },\n codex: {\n title: 'OpenAI',\n d: 'M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z',\n },\n gemini: {\n title: 'Gemini',\n d: 'M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z',\n },\n deepseek: {\n title: 'DeepSeek',\n d: 'M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z',\n },\n moonshot: {\n title: 'Moonshot',\n d: 'M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z',\n },\n}\n\n/**\n * Which mark a session wears.\n *\n * The two first-party engines are named directly. A `provider` session is\n * whatever the host wired up, so its *model* is the only thing that says whose\n * it is — sniffed loosely on purpose (`gemini-2.5-pro`, `deepseek-chat`,\n * `kimi-k2`), and falling through to no mark rather than guessing wrong.\n */\nexport function engineMark(engine: string, model?: string): string | undefined {\n if (engine === 'claude') return 'claude'\n if (engine === 'codex') return 'codex'\n const id = model?.toLowerCase() ?? ''\n if (!id) return undefined\n if (id.includes('gemini')) return 'gemini'\n if (id.includes('deepseek')) return 'deepseek'\n if (id.includes('moonshot') || id.includes('kimi')) return 'moonshot'\n if (id.includes('claude')) return 'claude'\n if (id.startsWith('gpt') || id.startsWith('o1') || id.startsWith('o3') || id.includes('openai')) {\n return 'codex'\n }\n return undefined\n}\n\nexport function EngineIcon({\n engine,\n model,\n className,\n}: {\n engine: string\n model?: string\n className?: string\n}) {\n const mark = PATHS[engineMark(engine, model) ?? '']\n // An engine this build has never heard of still gets a slot, so the second\n // line doesn't reflow around a missing glyph.\n if (!mark) {\n return (\n <span aria-hidden title={engine} className={cn('shrink-0 text-fg-3', className)}>\n ·\n </span>\n )\n }\n return (\n <svg\n viewBox='0 0 24 24'\n fill='currentColor'\n fillRule='evenodd'\n role='img'\n aria-label={mark.title}\n className={cn('size-3 shrink-0 text-fg-3', className)}>\n <title>{mark.title}</title>\n <path d={mark.d} />\n </svg>\n )\n}\n"],"mappings":"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"}
|
package/build/workspace.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as SessionPanelProps } from "./SessionPanel-
|
|
1
|
+
import { r as SessionPanelProps } from "./SessionPanel-CnNYEX80.mjs";
|
|
2
2
|
import * as _$react from "react";
|
|
3
3
|
import { CSSProperties } from "react";
|
|
4
4
|
import { OpenFile, UseHostFileSearchResult, UseHostFileTreeResult } from "@workerdeck/react";
|
package/build/workspace.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Bt as Button, Ht as cn, Lt as Input, dt as Splitter, gt as Tip, pt as Spinner, t as SessionPanel, vt as TooltipProvider } from "./SessionPanel-
|
|
1
|
+
import { Bt as Button, Ht as cn, Lt as Input, dt as Splitter, gt as Tip, pt as Spinner, t as SessionPanel, vt as TooltipProvider } from "./SessionPanel-DMPhsNlW.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";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workerdeck/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Styled agent-control component library for WorkerDeck: session panel, transcript with streaming, tool-call cards, permission prompts, session list, composer. Tailwind v4 + Base UI + cva. Pairs with the headless @workerdeck/react layer.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"streamdown": "^2.5.0",
|
|
39
39
|
"tailwind-merge": "^3.6.0",
|
|
40
40
|
"use-stick-to-bottom": "^1.1.6",
|
|
41
|
-
"@workerdeck/
|
|
42
|
-
"@workerdeck/
|
|
43
|
-
"@workerdeck/react": "0.
|
|
41
|
+
"@workerdeck/protocol": "0.17.0",
|
|
42
|
+
"@workerdeck/client": "0.17.0",
|
|
43
|
+
"@workerdeck/react": "0.17.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"monaco-editor": "^0.56.0",
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
"rimraf": "^6.1.3",
|
|
67
67
|
"tailwindcss": "^4.3.1",
|
|
68
68
|
"tsdown": "^0.21.10",
|
|
69
|
-
"vite": "^6.3.0"
|
|
69
|
+
"vite": "^6.3.0",
|
|
70
|
+
"vitest": "^3.2.0"
|
|
70
71
|
},
|
|
71
72
|
"author": "Tobias Strebitzer",
|
|
72
73
|
"repository": {
|
|
@@ -92,7 +93,8 @@
|
|
|
92
93
|
"scripts": {
|
|
93
94
|
"clean": "rimraf build",
|
|
94
95
|
"build": "tsdown",
|
|
95
|
-
"typecheck": "tsgo -p tsconfig.json",
|
|
96
|
+
"typecheck": "tsgo -p tsconfig.json && tsgo -p tsconfig.test.json",
|
|
97
|
+
"test": "vitest run",
|
|
96
98
|
"dev": "vite"
|
|
97
99
|
}
|
|
98
100
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ProjectIcon as ProjectIconSpec } from '@workerdeck/protocol'
|
|
2
|
+
import {
|
|
3
|
+
Anchor, Atom, Beaker, Bike, Binary, Blocks, Bot, Box, Brain, Briefcase, Brush, Bug,
|
|
4
|
+
Building2, Cable, Camera, Car, Cat, CircuitBoard, Cloud, Code, Coffee, Cog, Compass,
|
|
5
|
+
Container, Cpu, CreditCard, Database, Diamond, Dog, Feather, Film, Flame, FlaskConical,
|
|
6
|
+
Folder, Gamepad2, Gauge, Gem, Ghost, Gift, GitBranch, Globe, GraduationCap, Hammer,
|
|
7
|
+
Hexagon, House, Image, Key, Landmark, Laptop, Layers, Leaf, Library, Lightbulb, Lock,
|
|
8
|
+
Mail, Map, MessageCircle, Mic, Monitor, Moon, Mountain, Music, Network, Newspaper,
|
|
9
|
+
Notebook, Orbit, Package, Palette, PenTool, Phone, PiggyBank, Plane, Plug, Puzzle,
|
|
10
|
+
Radar, Radio, Receipt, Rocket, Ruler, Satellite, Scale, Scissors, Server, Shield, Ship,
|
|
11
|
+
ShoppingCart, Signal, Smartphone, Sparkles, Sprout, Square, Star, Sun, Table, Tag,
|
|
12
|
+
Target, Telescope, Terminal, TestTube, TreePine, Trophy, Truck, Umbrella, Users, Wallet,
|
|
13
|
+
Wand, Waves, Webhook, Wifi, Wrench, Zap,
|
|
14
|
+
} from 'lucide-react'
|
|
15
|
+
import type { ComponentType } from 'react'
|
|
16
|
+
import { cn } from '../../lib/utils.ts'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A project's icon — the render side of protocol's `ProjectIcon`, for a list
|
|
20
|
+
* row or a group header.
|
|
21
|
+
*
|
|
22
|
+
* **The glyph arm is a curated set, and that is the design rather than a
|
|
23
|
+
* shortcut.** The gateway validates a glyph name by *shape* only (lucide's
|
|
24
|
+
* lowercase-kebab convention) and explicitly declines to grow an icon catalog,
|
|
25
|
+
* so the contract already says a client must fall back on a name it does not
|
|
26
|
+
* know.
|
|
27
|
+
*
|
|
28
|
+
* The alternative was measured rather than argued, against the VS Code
|
|
29
|
+
* sidebar's own bundle: `sidebar.js` is **31 KB** with one glyph, **77 KB**
|
|
30
|
+
* with the 110 below (~418 bytes each), and **927 KB** with a
|
|
31
|
+
* `import * as` over lucide's ~1,600 — which is what it costs, because a
|
|
32
|
+
* namespace import defeats tree-shaking by construction. (`DynamicIcon` is the
|
|
33
|
+
* third option and worse in a different currency: ~1,600 chunk files shipped
|
|
34
|
+
* inside the `.vsix`.) Twelve times the bundle to render a name nobody has
|
|
35
|
+
* declared yet is not a trade worth making; 46 KB to cover the names people
|
|
36
|
+
* actually use is.
|
|
37
|
+
*
|
|
38
|
+
* The set is chosen to cover what people call a project; an unlisted-but-valid
|
|
39
|
+
* name draws {@link Folder}, which is what the row drew before this feature
|
|
40
|
+
* existed. **Grow it freely** — each addition is ~400 bytes and the fallback
|
|
41
|
+
* means a missing one is a shrug, not a hole.
|
|
42
|
+
*
|
|
43
|
+
* **The image arm draws nothing until its bytes arrive**, and takes them as a
|
|
44
|
+
* resolved `src` rather than fetching: the wire carries only an address
|
|
45
|
+
* (media type, size, content hash), and *who* fetches it differs per client —
|
|
46
|
+
* a VS Code webview cannot reach a gateway at all and must be handed a data
|
|
47
|
+
* URL by its extension host, while a browser can fetch and `createObjectURL`.
|
|
48
|
+
* A component that fetched would be wrong on one of them. Absent `src` renders
|
|
49
|
+
* nothing rather than a placeholder box: an icon is decoration beside a name
|
|
50
|
+
* that is already there, and a box that becomes a picture a beat later is more
|
|
51
|
+
* movement than the picture is worth.
|
|
52
|
+
*
|
|
53
|
+
* One thing an image icon cannot do, and a glyph can: **take the row's
|
|
54
|
+
* colour.** An `<img>`-embedded SVG is its own document, so `currentColor` does
|
|
55
|
+
* not reach it and its own `prefers-color-scheme` resolves against the *OS*
|
|
56
|
+
* rather than the host's theme (measured: it does resolve, which is the
|
|
57
|
+
* surprise — so a monochrome mark tuned for both schemes is right on a dark OS
|
|
58
|
+
* in a dark editor and wrong on a light OS in a dark one). A repo that wants a
|
|
59
|
+
* mark that always matches its row should declare a glyph; one that wants its
|
|
60
|
+
* brand should declare the image and accept that it is a picture.
|
|
61
|
+
*/
|
|
62
|
+
const GLYPHS: Record<string, ComponentType<{ className?: string }>> = {
|
|
63
|
+
anchor: Anchor, atom: Atom, beaker: Beaker, bike: Bike, binary: Binary, blocks: Blocks,
|
|
64
|
+
bot: Bot, box: Box, brain: Brain, briefcase: Briefcase, brush: Brush, bug: Bug,
|
|
65
|
+
'building-2': Building2, cable: Cable, camera: Camera, car: Car, cat: Cat,
|
|
66
|
+
'circuit-board': CircuitBoard, cloud: Cloud, code: Code, coffee: Coffee, cog: Cog,
|
|
67
|
+
compass: Compass, container: Container, cpu: Cpu, 'credit-card': CreditCard,
|
|
68
|
+
database: Database, diamond: Diamond, dog: Dog, feather: Feather, film: Film,
|
|
69
|
+
flame: Flame, 'flask-conical': FlaskConical, folder: Folder, 'gamepad-2': Gamepad2,
|
|
70
|
+
gauge: Gauge, gem: Gem, ghost: Ghost, gift: Gift, 'git-branch': GitBranch, globe: Globe,
|
|
71
|
+
'graduation-cap': GraduationCap, hammer: Hammer, hexagon: Hexagon, house: House,
|
|
72
|
+
image: Image, key: Key, landmark: Landmark, laptop: Laptop, layers: Layers, leaf: Leaf,
|
|
73
|
+
library: Library, lightbulb: Lightbulb, lock: Lock, mail: Mail, map: Map,
|
|
74
|
+
'message-circle': MessageCircle, mic: Mic, monitor: Monitor, moon: Moon,
|
|
75
|
+
mountain: Mountain, music: Music, network: Network, newspaper: Newspaper,
|
|
76
|
+
notebook: Notebook, orbit: Orbit, package: Package, palette: Palette, 'pen-tool': PenTool,
|
|
77
|
+
phone: Phone, 'piggy-bank': PiggyBank, plane: Plane, plug: Plug, puzzle: Puzzle,
|
|
78
|
+
radar: Radar, radio: Radio, receipt: Receipt, rocket: Rocket, ruler: Ruler,
|
|
79
|
+
satellite: Satellite, scale: Scale, scissors: Scissors, server: Server, shield: Shield,
|
|
80
|
+
ship: Ship, 'shopping-cart': ShoppingCart, signal: Signal, smartphone: Smartphone,
|
|
81
|
+
sparkles: Sparkles, sprout: Sprout, square: Square, star: Star, sun: Sun, table: Table,
|
|
82
|
+
tag: Tag, target: Target, telescope: Telescope, terminal: Terminal, 'test-tube': TestTube,
|
|
83
|
+
'tree-pine': TreePine, trophy: Trophy, truck: Truck, umbrella: Umbrella, users: Users,
|
|
84
|
+
wallet: Wallet, wand: Wand, waves: Waves, webhook: Webhook, wifi: Wifi, wrench: Wrench,
|
|
85
|
+
zap: Zap,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function ProjectIcon({
|
|
89
|
+
icon,
|
|
90
|
+
src,
|
|
91
|
+
name,
|
|
92
|
+
className,
|
|
93
|
+
}: {
|
|
94
|
+
icon: ProjectIconSpec | undefined
|
|
95
|
+
/** Resolved bytes for the `image` arm — an object URL or a data URL. The
|
|
96
|
+
* caller fetches (see the note above); absent draws nothing. */
|
|
97
|
+
src?: string
|
|
98
|
+
/** The project's name, for the alt text. */
|
|
99
|
+
name?: string
|
|
100
|
+
className?: string
|
|
101
|
+
}) {
|
|
102
|
+
if (!icon) return null
|
|
103
|
+
if (icon.type === 'image') {
|
|
104
|
+
if (!src) return null
|
|
105
|
+
return (
|
|
106
|
+
<img
|
|
107
|
+
src={src}
|
|
108
|
+
alt=''
|
|
109
|
+
aria-hidden
|
|
110
|
+
title={name}
|
|
111
|
+
// `object-contain` because a declared icon is whatever aspect the repo
|
|
112
|
+
// checked in, and a squashed logo reads worse than a letterboxed one.
|
|
113
|
+
className={cn('inline-block size-3 shrink-0 object-contain', className)}
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
const Glyph = GLYPHS[icon.name] ?? Folder
|
|
118
|
+
return <Glyph className={cn('inline-block size-3 shrink-0', className)} />
|
|
119
|
+
}
|