@workerdeck/ui 1.4.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.mjs","names":[],"sources":["../src/components/agent/EditorTabs.tsx","../src/components/agent/FileTree.tsx","../src/components/agent/CodeEditor.tsx","../src/components/agent/FileViewer.tsx","../src/components/agent/use-path-links.ts","../src/components/agent/SessionWorkspace.tsx"],"sourcesContent":["import { useEffect, useRef, type MouseEvent as ReactMouseEvent } from 'react'\nimport { isDirty, type OpenFile } from '@workerdeck/react'\nimport { X } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Tip, TooltipProvider } from '../ui/Tooltip.tsx'\nimport { formatBytes } from '../../lib/format.ts'\n\nexport interface EditorTabsProps {\n files: OpenFile[]\n activePath?: string\n onActivate: (path: string) => void\n onClose: (path: string) => void\n className?: string\n}\n\nexport function EditorTabs({ files, activePath, onActivate, onClose, className }: EditorTabsProps) {\n return (\n <TooltipProvider delay={500} closeDelay={0}>\n <div\n data-slot=\"editor-tabs\"\n role=\"tablist\"\n aria-label=\"Open files\"\n className={cn('flex shrink-0 items-stretch overflow-x-auto border-b border-border bg-surface', className)}\n >\n {files.map((file) => (\n <Tab\n key={file.path}\n file={file}\n active={file.path === activePath}\n onActivate={() => onActivate(file.path)}\n onClose={() => onClose(file.path)}\n onArrow={(direction) => {\n const index = files.findIndex((f) => f.path === file.path)\n const next = files[index + direction]\n if (next) {\n onActivate(next.path)\n }\n }}\n />\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction Tab({\n file,\n active,\n onActivate,\n onClose,\n onArrow,\n}: {\n file: OpenFile\n active: boolean\n onActivate: () => void\n onClose: () => void\n onArrow: (direction: 1 | -1) => void\n}) {\n const dirty = isDirty(file)\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n if (active) {\n ref.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n }\n }, [active])\n\n const onAuxClick = (event: ReactMouseEvent) => {\n if (event.button !== 1) {\n return\n }\n event.preventDefault()\n onClose()\n }\n\n return (\n <Tip\n side=\"bottom\"\n render={\n <div\n ref={ref}\n role=\"tab\"\n tabIndex={active ? 0 : -1}\n aria-selected={active}\n onClick={onActivate}\n onAuxClick={onAuxClick}\n onKeyDown={(event) => {\n if (event.key === 'ArrowRight') {\n onArrow(1)\n } else if (event.key === 'ArrowLeft') {\n onArrow(-1)\n } else if (event.key === 'Enter' || event.key === ' ') {\n onActivate()\n } else {\n return\n }\n event.preventDefault()\n }}\n className={cn(\n 'group flex min-w-0 shrink-0 cursor-pointer items-center gap-1.5 border-r border-border px-3 py-1.5 transition-colors',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent focus-visible:ring-inset',\n active ? 'bg-bg text-fg-1' : 'text-fg-3 hover:bg-surface-hover hover:text-fg-2',\n )}\n />\n }\n content={\n <span className=\"flex flex-col gap-0.5\">\n <span className=\"font-mono break-all\">{file.path}</span>\n {file.bytes !== undefined ? <span className=\"text-fg-4\">{formatBytes(file.bytes)}</span> : null}\n </span>\n }\n >\n <span className={cn('max-w-40 truncate font-mono text-label', dirty && 'italic')}>{file.name}</span>\n {file.status === 'error' ? <span className=\"shrink-0 text-danger\">!</span> : null}\n <button\n type=\"button\"\n aria-label={dirty ? `Close ${file.name} (unsaved changes)` : `Close ${file.name}`}\n onClick={(event) => {\n event.stopPropagation()\n onClose()\n }}\n className={cn(\n 'shrink-0 rounded p-0.5 text-fg-4 transition-opacity hover:bg-surface-hover hover:text-fg-1',\n active || dirty ? 'opacity-70' : 'opacity-0 group-hover:opacity-70 focus-visible:opacity-100',\n )}\n >\n <span className={cn('block', dirty && 'group-hover:hidden')}>\n {dirty ? <span className=\"block size-3 rounded-full bg-fg-2\" /> : <X className=\"size-3\" />}\n </span>\n {dirty ? (\n <span className=\"hidden group-hover:block\">\n <X className=\"size-3\" />\n </span>\n ) : null}\n </button>\n </Tip>\n )\n}\n","import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'\nimport type { UseHostFileSearchResult, UseHostFileTreeResult } from '@workerdeck/react'\nimport type { HostFileMatch } from '@workerdeck/protocol'\nimport { ChevronRight, File, Folder, FolderOpen, PanelLeftClose, RefreshCw, Search, X } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { EntryIcon } from './EntryIcon.tsx'\n\nexport interface FileTreeProps {\n tree: UseHostFileTreeResult\n search?: UseHostFileSearchResult\n activePath?: string\n onOpenFile: (path: string) => void\n onCollapse?: () => void\n style?: CSSProperties\n className?: string\n}\n\nconst INDENT = 12\n\nexport function FileTree({ tree, search, activePath, onOpenFile, onCollapse, style, className }: FileTreeProps) {\n const [query, setQuery] = useState('')\n const [matches, setMatches] = useState<HostFileMatch[] | undefined>()\n const searching = query.trim().length > 0\n\n useEffect(() => {\n if (!search?.available) {\n return\n }\n const q = query.trim()\n if (!q) {\n setMatches(undefined)\n return\n }\n const controller = new AbortController()\n const timer = setTimeout(() => {\n void search.search(q, { limit: 60, signal: controller.signal }).then((found) => {\n if (!controller.signal.aborted) {\n setMatches(found)\n }\n })\n }, 150)\n return () => {\n controller.abort()\n clearTimeout(timer)\n }\n }, [search, query])\n\n return (\n <div data-slot=\"file-tree\" style={style} className={cn('flex min-h-0 min-w-0 flex-col bg-surface', className)}>\n <div className=\"flex items-center gap-1 px-2 pt-2\">\n {search?.available ? (\n <div className=\"relative min-w-0 flex-1\">\n <Search className=\"absolute top-1/2 left-2 size-3.5 -translate-y-1/2 text-fg-4\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search files…\"\n className=\"h-7 pr-7 pl-7 text-label\"\n spellCheck={false}\n />\n {searching ? (\n <button\n type=\"button\"\n onClick={() => setQuery('')}\n aria-label=\"Clear search\"\n className=\"absolute top-1/2 right-1.5 -translate-y-1/2 text-fg-4 transition-colors hover:text-fg-2\"\n >\n <X className=\"size-3.5\" />\n </button>\n ) : null}\n </div>\n ) : (\n <span className=\"min-w-0 flex-1 truncate px-1 text-label text-fg-4\">{tree.root}</span>\n )}\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Refresh the file tree\" onClick={() => tree.refresh()}>\n <RefreshCw className=\"size-3.5 text-fg-3\" />\n </Button>\n {onCollapse ? (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Hide project files\" onClick={onCollapse}>\n <PanelLeftClose className=\"size-3.5 text-fg-3\" />\n </Button>\n ) : null}\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto px-1 py-1\">\n {tree.error ? (\n <p className=\"px-2 py-3 text-body-sm text-danger\">{tree.error}</p>\n ) : searching ? (\n matches === undefined ? (\n <div className=\"py-6 text-center\">\n <Spinner className=\"size-4 text-fg-4\" />\n </div>\n ) : matches.length === 0 ? (\n <p className=\"px-2 py-6 text-center text-body-sm text-fg-4\">No matching files.</p>\n ) : (\n <ul>\n {matches.map((match) => (\n <li key={match.path}>\n <Row\n label={fileName(match.relative)}\n detail={directoryOf(match.relative)}\n title={match.relative}\n icon={<File className=\"size-3.5 shrink-0 text-fg-4\" />}\n active={match.path === activePath}\n onClick={() => onOpenFile(match.path)}\n />\n </li>\n ))}\n </ul>\n )\n ) : tree.loading ? (\n <div className=\"py-6 text-center\">\n <Spinner className=\"size-4 text-fg-4\" />\n </div>\n ) : tree.rows.length === 0 ? (\n <p className=\"px-2 py-6 text-center text-body-sm text-fg-4\">This project is empty.</p>\n ) : (\n <ul role=\"tree\" aria-label=\"Project files\">\n {tree.rows.map((row) => (\n <li key={row.entry.path} role=\"treeitem\" aria-expanded={row.expanded} aria-level={row.depth + 1}>\n <Row\n label={row.entry.name}\n indent={row.depth}\n active={row.entry.path === activePath}\n icon={\n row.entry.type === 'dir' ? (\n row.loading ? (\n <Spinner className=\"size-3.5 shrink-0 text-fg-4\" />\n ) : row.expanded ? (\n <FolderOpen className=\"size-3.5 shrink-0 text-accent\" />\n ) : (\n <Folder className=\"size-3.5 shrink-0 text-accent\" />\n )\n ) : (\n <EntryIcon type={row.entry.type} />\n )\n }\n chevron={\n row.entry.type === 'dir' ? (\n <ChevronRight className={cn('size-3 shrink-0 text-fg-4 transition-transform', row.expanded && 'rotate-90')} />\n ) : undefined\n }\n onClick={() => (row.entry.type === 'dir' ? tree.toggle(row.entry.path) : onOpenFile(row.entry.path))}\n />\n {row.truncated ? (\n <p className=\"truncate py-0.5 text-label text-fg-4\" style={{ paddingLeft: (row.depth + 1) * INDENT + 22 }}>\n More entries than the server will return — use search.\n </p>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n )\n}\n\nfunction Row({\n label,\n detail,\n title,\n icon,\n chevron,\n indent = 0,\n active,\n onClick,\n}: {\n label: string\n detail?: string\n title?: string\n icon: ReactNode\n chevron?: ReactNode\n indent?: number\n active?: boolean\n onClick: () => void\n}) {\n const ref = useRef<HTMLButtonElement>(null)\n useEffect(() => {\n if (active) {\n ref.current?.scrollIntoView({ block: 'nearest' })\n }\n }, [active])\n\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClick}\n title={title ?? label}\n style={{ paddingLeft: indent * INDENT + 4 }}\n className={cn(\n 'flex w-full items-center gap-1 rounded py-1 pr-2 text-left transition-colors',\n active ? 'bg-surface-hover text-fg-1' : 'text-fg-2 hover:bg-surface-hover',\n )}\n >\n <span className=\"flex size-3 shrink-0 items-center justify-center\">{chevron}</span>\n {icon}\n <span className=\"shrink-0 truncate text-label\">{label}</span>\n {detail ? <span className=\"min-w-0 flex-1 truncate text-label text-fg-4\">{detail}</span> : null}\n </button>\n )\n}\n\nfunction fileName(relative: string): string {\n return relative.slice(relative.lastIndexOf('/') + 1)\n}\n\nfunction directoryOf(relative: string): string | undefined {\n const cut = relative.lastIndexOf('/')\n return cut === -1 ? undefined : relative.slice(0, cut)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type * as Monaco from 'monaco-editor'\nimport { cn } from '../../lib/utils.ts'\nimport { Spinner } from '../ui/Spinner.tsx'\n\nexport interface CodeEditorProps {\n path: string\n value: string\n onChange?: (value: string) => void\n onSave?: () => void\n readOnly?: boolean\n className?: string\n}\n\nexport function CodeEditor({ path, value, onChange, onSave, readOnly, className }: CodeEditorProps) {\n const host = useRef<HTMLDivElement>(null)\n const editor = useRef<Monaco.editor.IStandaloneCodeEditor | null>(null)\n const monaco = useRef<typeof Monaco | null>(null)\n const [ready, setReady] = useState(false)\n const theme = useDocumentTheme()\n\n const handlers = useRef({ onChange, onSave })\n handlers.current = { onChange, onSave }\n\n useEffect(() => {\n let disposed = false\n void loadMonaco().then((api) => {\n if (disposed || !host.current) {\n return\n }\n monaco.current = api\n const instance = api.editor.create(host.current, {\n value,\n language: languageOf(path),\n readOnly,\n automaticLayout: true,\n theme: monacoTheme(document.documentElement.getAttribute('data-theme')),\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n fontSize: 12,\n lineHeight: 20,\n fontFamily: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace',\n renderLineHighlight: 'line',\n smoothScrolling: true,\n padding: { top: 8, bottom: 8 },\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n wordBasedSuggestions: 'off',\n quickSuggestions: false,\n })\n editor.current = instance\n instance.onDidChangeModelContent(() => {\n handlers.current.onChange?.(instance.getValue())\n })\n instance.addCommand(api.KeyMod.CtrlCmd | api.KeyCode.KeyS, () => {\n handlers.current.onSave?.()\n })\n setReady(true)\n })\n return () => {\n disposed = true\n // The editor, never its models: a model is keyed by path and outlives this mount, which is what restores a reopened tab's undo stack.\n editor.current?.dispose()\n editor.current = null\n setReady(false)\n }\n }, [])\n\n useEffect(() => {\n const api = monaco.current\n const instance = editor.current\n if (!api || !instance || !ready) {\n return\n }\n const uri = api.Uri.parse(`workerdeck://host${path}`)\n const model = api.editor.getModel(uri) ?? api.editor.createModel(value, languageOf(path), uri)\n if (instance.getModel() !== model) {\n instance.setModel(model)\n }\n }, [path, ready, value])\n\n useEffect(() => {\n const instance = editor.current\n if (!instance || !ready) {\n return\n }\n const model = instance.getModel()\n if (!model || model.getValue() === value) {\n return\n }\n model.pushEditOperations([], [{ range: model.getFullModelRange(), text: value }], () => null)\n }, [value, ready])\n\n useEffect(() => {\n if (ready) {\n editor.current?.updateOptions({ readOnly })\n }\n }, [readOnly, ready])\n\n useEffect(() => {\n if (ready) {\n monaco.current?.editor.setTheme(monacoTheme(theme))\n }\n }, [theme, ready])\n\n return (\n <div className={cn('relative min-h-0 min-w-0 flex-1', className)}>\n <div ref={host} className=\"absolute inset-0\" />\n {!ready ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg\">\n <Spinner className=\"size-4 text-fg-4\" />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction useDocumentTheme(): string | null {\n const [theme, setTheme] = useState<string | null>(() =>\n typeof document === 'undefined' ? null : document.documentElement.getAttribute('data-theme'),\n )\n useEffect(() => {\n const root = document.documentElement\n const sync = () => setTheme(root.getAttribute('data-theme'))\n sync()\n const observer = new MutationObserver(sync)\n observer.observe(root, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [])\n return theme\n}\n\nfunction monacoTheme(theme: string | null): string {\n return theme === 'light' ? 'wd-light' : 'wd-dark'\n}\n\nlet monacoPromise: Promise<typeof Monaco> | undefined\nfunction loadMonaco(): Promise<typeof Monaco> {\n monacoPromise ??= (async () => {\n const api = await import('monaco-editor')\n api.editor.defineTheme('wd-dark', {\n base: 'vs-dark',\n inherit: true,\n rules: [],\n colors: { 'editor.background': '#00000000' },\n })\n api.editor.defineTheme('wd-light', {\n base: 'vs',\n inherit: true,\n rules: [],\n colors: { 'editor.background': '#00000000' },\n })\n return api\n })()\n return monacoPromise\n}\n\nfunction languageOf(path: string): string {\n const name = path.slice(path.lastIndexOf('/') + 1)\n const byName = FILENAME_LANGUAGES[name.toLowerCase()]\n if (byName) {\n return byName\n }\n const extension = name.slice(name.lastIndexOf('.') + 1).toLowerCase()\n return EXTENSION_LANGUAGES[extension] ?? 'plaintext'\n}\n\nconst FILENAME_LANGUAGES: Record<string, string> = {\n dockerfile: 'dockerfile',\n makefile: 'plaintext',\n '.gitignore': 'plaintext',\n '.env': 'shell',\n}\n\nconst EXTENSION_LANGUAGES: Record<string, string> = {\n ts: 'typescript',\n tsx: 'typescript',\n mts: 'typescript',\n cts: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n mjs: 'javascript',\n cjs: 'javascript',\n json: 'json',\n jsonc: 'json',\n md: 'markdown',\n mdx: 'markdown',\n css: 'css',\n scss: 'scss',\n less: 'less',\n html: 'html',\n htm: 'html',\n xml: 'xml',\n svg: 'xml',\n yml: 'yaml',\n yaml: 'yaml',\n toml: 'plaintext',\n sh: 'shell',\n bash: 'shell',\n zsh: 'shell',\n fish: 'shell',\n py: 'python',\n rb: 'ruby',\n rs: 'rust',\n go: 'go',\n swift: 'swift',\n java: 'java',\n kt: 'kotlin',\n c: 'c',\n h: 'c',\n cpp: 'cpp',\n cc: 'cpp',\n hpp: 'cpp',\n cs: 'csharp',\n php: 'php',\n sql: 'sql',\n graphql: 'graphql',\n gql: 'graphql',\n lua: 'lua',\n r: 'r',\n pl: 'perl',\n dart: 'dart',\n scala: 'scala',\n ini: 'ini',\n cfg: 'ini',\n conf: 'ini',\n txt: 'plaintext',\n log: 'plaintext',\n}\n","import type { ReactNode } from 'react'\nimport type { OpenFile } from '@workerdeck/react'\nimport { currentText, isDirty } from '@workerdeck/react'\nimport { FileWarning, TriangleAlert } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { CodeEditor } from './CodeEditor.tsx'\n\nexport interface FileViewerProps {\n file: OpenFile | undefined\n canWrite?: boolean\n onChange?: (path: string, content: string) => void\n onSave?: (path: string) => void\n onRevert?: (path: string) => void\n onReload?: (path: string) => void\n onOverwrite?: (path: string) => void\n onDismissConflict?: (path: string) => void\n className?: string\n}\n\nexport function FileViewer({\n file,\n canWrite,\n onChange,\n onSave,\n onRevert,\n onReload,\n onOverwrite,\n onDismissConflict,\n className,\n}: FileViewerProps) {\n if (!file) {\n return null\n }\n\n return (\n <div data-slot=\"file-viewer\" className={cn('flex min-h-0 min-w-0 flex-1 flex-col bg-bg', className)}>\n {file.conflict ? (\n <div className=\"flex shrink-0 flex-wrap items-center gap-2 border-b border-warning/40 bg-warning-bg px-3 py-2\">\n <TriangleAlert className=\"size-3.5 shrink-0 text-warning\" />\n <span className=\"min-w-0 flex-1 text-body-sm text-warning\">This file changed on disk since you opened it.</span>\n <Button variant=\"outline\" size=\"xs\" onClick={() => onReload?.(file.path)}>\n Use the version on disk\n </Button>\n <Button variant=\"outline\" size=\"xs\" onClick={() => onOverwrite?.(file.path)}>\n Keep mine\n </Button>\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onDismissConflict?.(file.path)}>\n Dismiss\n </Button>\n </div>\n ) : file.saveError ? (\n <div className=\"flex shrink-0 items-center gap-2 border-b border-danger/40 bg-danger-bg px-3 py-2\">\n <TriangleAlert className=\"size-3.5 shrink-0 text-danger\" />\n <span className=\"min-w-0 flex-1 text-body-sm text-danger\">{file.saveError}</span>\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onDismissConflict?.(file.path)}>\n Dismiss\n </Button>\n </div>\n ) : null}\n\n {file.status === 'loading' ? (\n <Centred>\n <Spinner className=\"size-4 text-fg-4\" />\n </Centred>\n ) : file.status === 'error' ? (\n <Centred>\n <TriangleAlert className=\"size-4 text-danger\" />\n <p className=\"text-body-sm text-danger\">{file.error}</p>\n </Centred>\n ) : file.status === 'binary' ? (\n <Centred>\n <FileWarning className=\"size-4 text-fg-4\" />\n <p className=\"text-body-sm text-fg-4\">This file isn’t text.</p>\n <p className=\"text-label text-fg-4\">It can’t be edited here without corrupting it.</p>\n </Centred>\n ) : (\n <CodeEditor\n path={file.path}\n value={currentText(file)}\n readOnly={!canWrite}\n onChange={onChange ? (content) => onChange(file.path, content) : undefined}\n onSave={onSave ? () => onSave(file.path) : undefined}\n />\n )}\n\n {file.status === 'ready' && (file.saving || isDirty(file) || !canWrite) ? (\n <div className=\"flex shrink-0 items-center gap-2 border-t border-border px-3 py-1\">\n {file.saving ? (\n <>\n <Spinner className=\"size-3 text-fg-4\" />\n <span className=\"text-label text-fg-4\">Saving…</span>\n </>\n ) : !canWrite ? (\n <span className=\"text-label text-fg-4\">Read-only — this gateway doesn’t allow writes.</span>\n ) : (\n <>\n <span className=\"text-label text-fg-3\">Unsaved changes</span>\n <span className=\"flex-1\" />\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onRevert?.(file.path)}>\n Revert\n </Button>\n <Button variant=\"outline\" size=\"xs\" onClick={() => onSave?.(file.path)}>\n Save\n <span className=\"ml-1 text-fg-4\">⌘S</span>\n </Button>\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction Centred({ children }: { children: ReactNode }) {\n return <div className=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-2 p-6\">{children}</div>\n}\n","import { useEffect, type RefObject } from 'react'\n\nexport type PathHit = { path: string; line?: number }\n\nconst LINKISH = 'wd-path-link'\n\nconst PATH_RE = /^(\\/[^\\s:'\"`()[\\]{}]+|[\\w.@~-]+(?:\\/[\\w.@~-]+)+)(?::(\\d+))?$/\nconst FILE_RE = /^([\\w.@-]+\\.[A-Za-z0-9]{1,10})(?::(\\d+))?$/\n\nconst COVERAGE = 0.6\n\nexport function matchPath(text: string, inCode: boolean): PathHit | undefined {\n const trimmed = text.trim()\n if (!trimmed || trimmed.endsWith('/')) {\n return undefined\n }\n const hit = PATH_RE.exec(trimmed) ?? (inCode ? FILE_RE.exec(trimmed) : null)\n if (!hit) {\n return undefined\n }\n return { path: hit[1]!, line: hit[2] ? Number(hit[2]) : undefined }\n}\n\nfunction hitFor(element: HTMLElement | undefined): PathHit | undefined {\n if (!element) {\n return undefined\n }\n const inCode = element.closest('code') !== null\n const text = element.textContent ?? ''\n const hit = matchPath(text, inCode)\n if (!hit) {\n return undefined\n }\n if (!inCode && hit.path.length < text.trim().length * COVERAGE) {\n return undefined\n }\n return hit\n}\n\nexport function usePathLinks({\n container,\n onOpen,\n enabled = true,\n ignore,\n}: {\n container: RefObject<HTMLElement | null>\n onOpen: (hit: PathHit) => void\n enabled?: boolean\n ignore?: string\n}) {\n useEffect(() => {\n const root = container.current\n if (!root || !enabled) {\n return\n }\n const excluded = (element: HTMLElement | undefined) =>\n ignore !== undefined && element?.closest(ignore) !== null && element !== undefined\n\n let marked: HTMLElement | undefined\n const unmark = () => {\n marked?.classList.remove(LINKISH)\n marked = undefined\n }\n\n const onClick = (event: MouseEvent) => {\n if (!event.metaKey && !event.ctrlKey) {\n return\n }\n const target = event.target instanceof HTMLElement ? event.target : undefined\n if (excluded(target)) {\n return\n }\n const hit = hitFor(target)\n if (!hit) {\n return\n }\n event.preventDefault()\n event.stopPropagation()\n unmark()\n onOpen(hit)\n }\n\n const onMove = (event: MouseEvent) => {\n if (!event.metaKey && !event.ctrlKey) {\n return unmark()\n }\n const element = event.target instanceof HTMLElement ? event.target : undefined\n if (element === marked) {\n return\n }\n unmark()\n if (!element || excluded(element) || !hitFor(element)) {\n return\n }\n marked = element\n element.classList.add(LINKISH)\n }\n\n const onKey = (event: KeyboardEvent) => {\n if (!event.metaKey && !event.ctrlKey) {\n unmark()\n }\n }\n\n root.addEventListener('click', onClick, true)\n root.addEventListener('mousemove', onMove)\n document.addEventListener('keydown', onKey)\n document.addEventListener('keyup', onKey)\n window.addEventListener('blur', unmark)\n return () => {\n unmark()\n root.removeEventListener('click', onClick, true)\n root.removeEventListener('mousemove', onMove)\n document.removeEventListener('keydown', onKey)\n document.removeEventListener('keyup', onKey)\n window.removeEventListener('blur', unmark)\n }\n }, [container, onOpen, enabled, ignore])\n}\n\nexport function resolveAgainstCwd(path: string, cwd: string | undefined): string {\n if (path.startsWith('/')) {\n return path\n }\n if (!cwd) {\n return path\n }\n return `${cwd.replace(/\\/$/, '')}/${path.replace(/^\\.\\//, '')}`\n}\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from 'react'\nimport { createPortal } from 'react-dom'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { useHostFileRoots, useHostFileSearch, useHostFileTree, useOpenFiles, useSessionInfo, isDirty } from '@workerdeck/react'\nimport { PanelLeftOpen } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Splitter } from '../ui/Splitter.tsx'\nimport { EditorTabs } from './EditorTabs.tsx'\nimport { FileTree } from './FileTree.tsx'\nimport { FileViewer } from './FileViewer.tsx'\nimport { SessionPanel, type SessionPanelProps } from './SessionPanel.tsx'\nimport { resolveAgainstCwd, usePathLinks } from './use-path-links.ts'\n\nexport interface SessionWorkspaceProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n header?: SessionPanelProps['header']\n transcriptVariant?: SessionPanelProps['transcriptVariant']\n transcriptDensity?: SessionPanelProps['transcriptDensity']\n transcriptFont?: SessionPanelProps['transcriptFont']\n midTurnSend?: SessionPanelProps['midTurnSend']\n scrubber?: SessionPanelProps['scrubber']\n bookmarks?: SessionPanelProps['bookmarks']\n onToggleBookmark?: SessionPanelProps['onToggleBookmark']\n stickyPrompt?: SessionPanelProps['stickyPrompt']\n openSubagent?: SessionPanelProps['openSubagent']\n reveal?: SessionPanelProps['reveal']\n onSubagentChange?: SessionPanelProps['onSubagentChange']\n statusPlacement?: SessionPanelProps['statusPlacement']\n controlsSurface?: SessionPanelProps['controlsSurface']\n fontSize?: SessionPanelProps['fontSize']\n onLinkClick?: SessionPanelProps['onLinkClick']\n unseen?: SessionPanelProps['unseen']\n readOnly?: SessionPanelProps['readOnly']\n onVitals?: SessionPanelProps['onVitals']\n defaultRailWidth?: number\n defaultRailCollapsed?: boolean\n onRailChange?: (rail: { width: number; collapsed: boolean }) => void\n className?: string\n}\n\nconst RAIL_MIN = 180\nconst RAIL_MAX = 520\nconst AGENT_MIN = 220\nconst EDITOR_MIN = 120\n\nexport function SessionWorkspace({\n client,\n sessionId,\n header,\n transcriptVariant,\n transcriptDensity,\n transcriptFont,\n midTurnSend,\n scrubber,\n bookmarks,\n onToggleBookmark,\n stickyPrompt,\n openSubagent,\n reveal,\n onSubagentChange,\n statusPlacement,\n controlsSurface,\n fontSize,\n onLinkClick,\n unseen,\n readOnly,\n onVitals,\n defaultRailWidth = 260,\n defaultRailCollapsed,\n onRailChange,\n className,\n}: SessionWorkspaceProps) {\n const { info } = useSessionInfo(client, sessionId)\n const cwd = info?.cwd\n\n const tree = useHostFileTree(client, cwd)\n const search = useHostFileSearch(client, cwd)\n const files = useOpenFiles(client)\n const { canWrite } = useHostFileRoots(client)\n\n useEffect(() => {\n if (!files.hasUnsaved) {\n return\n }\n const warn = (event: BeforeUnloadEvent) => event.preventDefault()\n window.addEventListener('beforeunload', warn)\n return () => window.removeEventListener('beforeunload', warn)\n }, [files.hasUnsaved])\n\n const wide = useIsWide()\n const [railCollapsed, setRailCollapsed] = useState(defaultRailCollapsed ?? false)\n const [railWidth, setRailWidth] = useState(defaultRailWidth)\n const onRailChangeRef = useRef(onRailChange)\n onRailChangeRef.current = onRailChange\n useEffect(() => {\n onRailChangeRef.current?.({ width: railWidth, collapsed: railCollapsed })\n }, [railWidth, railCollapsed])\n const [editorHeight, setEditorHeight] = useState(360)\n\n const hasFiles = files.files.length > 0\n\n const overlayRail = !wide\n const railOpen = tree.available && !railCollapsed\n useEffect(() => {\n if (overlayRail) {\n setRailCollapsed(true)\n }\n }, [overlayRail])\n\n const closeTab = useCallback(\n (path: string) => {\n const file = files.files.find((f) => f.path === path)\n if (file && isDirty(file)) {\n const name = file.name\n if (!window.confirm(`${name} has unsaved changes. Close it and lose them?`)) {\n return\n }\n }\n files.close(path)\n },\n [files],\n )\n\n const column = useRef<HTMLDivElement>(null)\n const columnHeight = useElementHeight(column)\n const openPath = useCallback(({ path }: { path: string }) => files.open(resolveAgainstCwd(path, cwd)), [files.open, cwd])\n usePathLinks({\n container: column,\n onOpen: openPath,\n enabled: tree.available,\n ignore: '.monaco-editor',\n })\n const editorMax = Math.max(EDITOR_MIN, columnHeight - AGENT_MIN)\n\n const [topBar, setTopBar] = useState<HTMLDivElement | null>(null)\n const hoisted: SessionPanelProps['header'] =\n header === undefined || topBar === null\n ? undefined\n : typeof header === 'function'\n ? (slots) => createPortal(header(slots), topBar)\n : createPortal(header, topBar)\n\n return (\n <div data-slot=\"session-workspace\" className={cn('flex h-full min-h-0 w-full flex-col overflow-hidden bg-bg', className)}>\n {header !== undefined ? <div ref={setTopBar} className=\"shrink-0\" /> : null}\n <div className=\"relative flex min-h-0 flex-1\">\n {railOpen ? (\n <FileTree\n tree={tree}\n search={search}\n activePath={files.activePath}\n onOpenFile={files.open}\n onCollapse={() => setRailCollapsed(true)}\n style={{ width: overlayRail ? Math.min(railWidth, 320) : railWidth }}\n className={cn('shrink-0 border-r border-border', overlayRail && 'absolute inset-y-0 left-0 z-20 shadow-lg')}\n />\n ) : tree.available ? (\n <div className=\"flex w-8 shrink-0 flex-col items-center border-r border-border bg-surface pt-1.5\">\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Show project files\" onClick={() => setRailCollapsed(false)}>\n <PanelLeftOpen className=\"size-4 text-fg-3\" />\n </Button>\n </div>\n ) : null}\n {railOpen && !overlayRail ? (\n <Splitter\n orientation=\"vertical\"\n value={railWidth}\n onValueChange={setRailWidth}\n min={RAIL_MIN}\n max={RAIL_MAX}\n defaultValue={defaultRailWidth}\n aria-label=\"Resize the file tree\"\n />\n ) : null}\n\n <div ref={column} className=\"flex min-h-0 min-w-0 flex-1 flex-col\">\n {hasFiles ? (\n <div\n className=\"flex min-h-0 shrink-0 flex-col overflow-hidden\"\n style={{ height: Math.min(editorHeight, editorMax || editorHeight) }}\n >\n <EditorTabs files={files.files} activePath={files.activePath} onActivate={files.activate} onClose={closeTab} />\n <FileViewer\n file={files.active}\n canWrite={canWrite}\n onChange={files.edit}\n onSave={(path) => void files.save(path)}\n onRevert={files.revert}\n onReload={files.reload}\n onOverwrite={(path) => void files.overwrite(path)}\n onDismissConflict={files.dismissConflict}\n />\n </div>\n ) : null}\n {hasFiles ? (\n <Splitter\n orientation=\"horizontal\"\n value={Math.min(editorHeight, editorMax || editorHeight)}\n onValueChange={setEditorHeight}\n min={EDITOR_MIN}\n max={editorMax}\n aria-label=\"Resize the open file\"\n />\n ) : null}\n <SessionPanel\n client={client}\n sessionId={sessionId}\n header={hoisted}\n transcriptVariant={transcriptVariant}\n transcriptDensity={transcriptDensity}\n transcriptFont={transcriptFont}\n midTurnSend={midTurnSend}\n scrubber={scrubber}\n bookmarks={bookmarks}\n onToggleBookmark={onToggleBookmark}\n stickyPrompt={stickyPrompt}\n openSubagent={openSubagent}\n reveal={reveal}\n onSubagentChange={onSubagentChange}\n controlsSurface={controlsSurface}\n fontSize={fontSize}\n onLinkClick={onLinkClick}\n statusPlacement={statusPlacement}\n unseen={unseen}\n readOnly={readOnly}\n onVitals={onVitals}\n className=\"min-h-0 flex-1\"\n />\n </div>\n\n {overlayRail && railOpen ? (\n <button\n type=\"button\"\n aria-label=\"Close the file tree\"\n onClick={() => setRailCollapsed(true)}\n className=\"absolute inset-0 z-10 bg-black/30\"\n />\n ) : null}\n </div>\n </div>\n )\n}\n\nfunction useElementHeight(ref: RefObject<HTMLElement | null>): number {\n const [height, setHeight] = useState(0)\n useLayoutEffect(() => {\n const element = ref.current\n if (!element) {\n return\n }\n const observer = new ResizeObserver(([entry]) => {\n if (entry) {\n setHeight(entry.contentRect.height)\n }\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [ref])\n return height\n}\n\nfunction useIsWide(): boolean {\n const [wide, setWide] = useState(true)\n useEffect(() => {\n const query = window.matchMedia('(min-width: 768px)')\n const sync = () => setWide(query.matches)\n sync()\n query.addEventListener('change', sync)\n return () => query.removeEventListener('change', sync)\n }, [])\n return wide\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspace.mjs","names":[],"sources":["../src/components/agent/EditorTabs.tsx","../src/components/agent/FileTree.tsx","../src/components/agent/CodeEditor.tsx","../src/components/agent/FileViewer.tsx","../src/components/agent/use-path-links.ts","../src/components/agent/SessionWorkspace.tsx"],"sourcesContent":["import { useEffect, useRef, type MouseEvent as ReactMouseEvent } from 'react'\nimport { isDirty, type OpenFile } from '@workerdeck/react'\nimport { X } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Tip, TooltipProvider } from '../ui/Tooltip.tsx'\nimport { formatBytes } from '../../lib/format.ts'\n\nexport interface EditorTabsProps {\n files: OpenFile[]\n activePath?: string\n onActivate: (path: string) => void\n onClose: (path: string) => void\n className?: string\n}\n\nexport function EditorTabs({ files, activePath, onActivate, onClose, className }: EditorTabsProps) {\n return (\n <TooltipProvider delay={500} closeDelay={0}>\n <div\n data-slot=\"editor-tabs\"\n role=\"tablist\"\n aria-label=\"Open files\"\n className={cn('flex shrink-0 items-stretch overflow-x-auto border-b border-border bg-surface', className)}\n >\n {files.map((file) => (\n <Tab\n key={file.path}\n file={file}\n active={file.path === activePath}\n onActivate={() => onActivate(file.path)}\n onClose={() => onClose(file.path)}\n onArrow={(direction) => {\n const index = files.findIndex((f) => f.path === file.path)\n const next = files[index + direction]\n if (next) {\n onActivate(next.path)\n }\n }}\n />\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction Tab({\n file,\n active,\n onActivate,\n onClose,\n onArrow,\n}: {\n file: OpenFile\n active: boolean\n onActivate: () => void\n onClose: () => void\n onArrow: (direction: 1 | -1) => void\n}) {\n const dirty = isDirty(file)\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n if (active) {\n ref.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n }\n }, [active])\n\n const onAuxClick = (event: ReactMouseEvent) => {\n if (event.button !== 1) {\n return\n }\n event.preventDefault()\n onClose()\n }\n\n return (\n <Tip\n side=\"bottom\"\n render={\n <div\n ref={ref}\n role=\"tab\"\n tabIndex={active ? 0 : -1}\n aria-selected={active}\n onClick={onActivate}\n onAuxClick={onAuxClick}\n onKeyDown={(event) => {\n if (event.key === 'ArrowRight') {\n onArrow(1)\n } else if (event.key === 'ArrowLeft') {\n onArrow(-1)\n } else if (event.key === 'Enter' || event.key === ' ') {\n onActivate()\n } else {\n return\n }\n event.preventDefault()\n }}\n className={cn(\n 'group flex min-w-0 shrink-0 cursor-pointer items-center gap-1.5 border-r border-border px-3 py-1.5 transition-colors',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent focus-visible:ring-inset',\n active ? 'bg-bg text-fg-1' : 'text-fg-3 hover:bg-surface-hover hover:text-fg-2',\n )}\n />\n }\n content={\n <span className=\"flex flex-col gap-0.5\">\n <span className=\"font-mono break-all\">{file.path}</span>\n {file.bytes !== undefined ? <span className=\"text-fg-4\">{formatBytes(file.bytes)}</span> : null}\n </span>\n }\n >\n <span className={cn('max-w-40 truncate font-mono text-label', dirty && 'italic')}>{file.name}</span>\n {file.status === 'error' ? <span className=\"shrink-0 text-danger\">!</span> : null}\n <button\n type=\"button\"\n aria-label={dirty ? `Close ${file.name} (unsaved changes)` : `Close ${file.name}`}\n onClick={(event) => {\n event.stopPropagation()\n onClose()\n }}\n className={cn(\n 'shrink-0 rounded p-0.5 text-fg-4 transition-opacity hover:bg-surface-hover hover:text-fg-1',\n active || dirty ? 'opacity-70' : 'opacity-0 group-hover:opacity-70 focus-visible:opacity-100',\n )}\n >\n <span className={cn('block', dirty && 'group-hover:hidden')}>\n {dirty ? <span className=\"block size-3 rounded-full bg-fg-2\" /> : <X className=\"size-3\" />}\n </span>\n {dirty ? (\n <span className=\"hidden group-hover:block\">\n <X className=\"size-3\" />\n </span>\n ) : null}\n </button>\n </Tip>\n )\n}\n","import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'\nimport type { UseHostFileSearchResult, UseHostFileTreeResult } from '@workerdeck/react'\nimport type { HostFileMatch } from '@workerdeck/protocol'\nimport { ChevronRight, File, Folder, FolderOpen, PanelLeftClose, RefreshCw, Search, X } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { EntryIcon } from './EntryIcon.tsx'\n\nexport interface FileTreeProps {\n tree: UseHostFileTreeResult\n search?: UseHostFileSearchResult\n activePath?: string\n onOpenFile: (path: string) => void\n onCollapse?: () => void\n style?: CSSProperties\n className?: string\n}\n\nconst INDENT = 12\n\nexport function FileTree({ tree, search, activePath, onOpenFile, onCollapse, style, className }: FileTreeProps) {\n const [query, setQuery] = useState('')\n const [matches, setMatches] = useState<HostFileMatch[] | undefined>()\n const searching = query.trim().length > 0\n\n useEffect(() => {\n if (!search?.available) {\n return\n }\n const q = query.trim()\n if (!q) {\n setMatches(undefined)\n return\n }\n const controller = new AbortController()\n const timer = setTimeout(() => {\n void search.search(q, { limit: 60, signal: controller.signal }).then((found) => {\n if (!controller.signal.aborted) {\n setMatches(found)\n }\n })\n }, 150)\n return () => {\n controller.abort()\n clearTimeout(timer)\n }\n }, [search, query])\n\n return (\n <div data-slot=\"file-tree\" style={style} className={cn('flex min-h-0 min-w-0 flex-col bg-surface', className)}>\n <div className=\"flex items-center gap-1 px-2 pt-2\">\n {search?.available ? (\n <div className=\"relative min-w-0 flex-1\">\n <Search className=\"absolute top-1/2 left-2 size-3.5 -translate-y-1/2 text-fg-4\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search files…\"\n className=\"h-7 pr-7 pl-7 text-label\"\n spellCheck={false}\n />\n {searching ? (\n <button\n type=\"button\"\n onClick={() => setQuery('')}\n aria-label=\"Clear search\"\n className=\"absolute top-1/2 right-1.5 -translate-y-1/2 text-fg-4 transition-colors hover:text-fg-2\"\n >\n <X className=\"size-3.5\" />\n </button>\n ) : null}\n </div>\n ) : (\n <span className=\"min-w-0 flex-1 truncate px-1 text-label text-fg-4\">{tree.root}</span>\n )}\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Refresh the file tree\" onClick={() => tree.refresh()}>\n <RefreshCw className=\"size-3.5 text-fg-3\" />\n </Button>\n {onCollapse ? (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Hide project files\" onClick={onCollapse}>\n <PanelLeftClose className=\"size-3.5 text-fg-3\" />\n </Button>\n ) : null}\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto px-1 py-1\">\n {tree.error ? (\n <p className=\"px-2 py-3 text-body-sm text-danger\">{tree.error}</p>\n ) : searching ? (\n matches === undefined ? (\n <div className=\"py-6 text-center\">\n <Spinner className=\"size-4 text-fg-4\" />\n </div>\n ) : matches.length === 0 ? (\n <p className=\"px-2 py-6 text-center text-body-sm text-fg-4\">No matching files.</p>\n ) : (\n <ul>\n {matches.map((match) => (\n <li key={match.path}>\n <Row\n label={fileName(match.relative)}\n detail={directoryOf(match.relative)}\n title={match.relative}\n icon={<File className=\"size-3.5 shrink-0 text-fg-4\" />}\n active={match.path === activePath}\n onClick={() => onOpenFile(match.path)}\n />\n </li>\n ))}\n </ul>\n )\n ) : tree.loading ? (\n <div className=\"py-6 text-center\">\n <Spinner className=\"size-4 text-fg-4\" />\n </div>\n ) : tree.rows.length === 0 ? (\n <p className=\"px-2 py-6 text-center text-body-sm text-fg-4\">This project is empty.</p>\n ) : (\n <ul role=\"tree\" aria-label=\"Project files\">\n {tree.rows.map((row) => (\n <li key={row.entry.path} role=\"treeitem\" aria-expanded={row.expanded} aria-level={row.depth + 1}>\n <Row\n label={row.entry.name}\n indent={row.depth}\n active={row.entry.path === activePath}\n icon={\n row.entry.type === 'dir' ? (\n row.loading ? (\n <Spinner className=\"size-3.5 shrink-0 text-fg-4\" />\n ) : row.expanded ? (\n <FolderOpen className=\"size-3.5 shrink-0 text-accent\" />\n ) : (\n <Folder className=\"size-3.5 shrink-0 text-accent\" />\n )\n ) : (\n <EntryIcon type={row.entry.type} />\n )\n }\n chevron={\n row.entry.type === 'dir' ? (\n <ChevronRight className={cn('size-3 shrink-0 text-fg-4 transition-transform', row.expanded && 'rotate-90')} />\n ) : undefined\n }\n onClick={() => (row.entry.type === 'dir' ? tree.toggle(row.entry.path) : onOpenFile(row.entry.path))}\n />\n {row.truncated ? (\n <p className=\"truncate py-0.5 text-label text-fg-4\" style={{ paddingLeft: (row.depth + 1) * INDENT + 22 }}>\n More entries than the server will return — use search.\n </p>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n )\n}\n\nfunction Row({\n label,\n detail,\n title,\n icon,\n chevron,\n indent = 0,\n active,\n onClick,\n}: {\n label: string\n detail?: string\n title?: string\n icon: ReactNode\n chevron?: ReactNode\n indent?: number\n active?: boolean\n onClick: () => void\n}) {\n const ref = useRef<HTMLButtonElement>(null)\n useEffect(() => {\n if (active) {\n ref.current?.scrollIntoView({ block: 'nearest' })\n }\n }, [active])\n\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClick}\n title={title ?? label}\n style={{ paddingLeft: indent * INDENT + 4 }}\n className={cn(\n 'flex w-full items-center gap-1 rounded py-1 pr-2 text-left transition-colors',\n active ? 'bg-surface-hover text-fg-1' : 'text-fg-2 hover:bg-surface-hover',\n )}\n >\n <span className=\"flex size-3 shrink-0 items-center justify-center\">{chevron}</span>\n {icon}\n <span className=\"shrink-0 truncate text-label\">{label}</span>\n {detail ? <span className=\"min-w-0 flex-1 truncate text-label text-fg-4\">{detail}</span> : null}\n </button>\n )\n}\n\nfunction fileName(relative: string): string {\n return relative.slice(relative.lastIndexOf('/') + 1)\n}\n\nfunction directoryOf(relative: string): string | undefined {\n const cut = relative.lastIndexOf('/')\n return cut === -1 ? undefined : relative.slice(0, cut)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type * as Monaco from 'monaco-editor'\nimport { cn } from '../../lib/utils.ts'\nimport { Spinner } from '../ui/Spinner.tsx'\n\nexport interface CodeEditorProps {\n path: string\n value: string\n onChange?: (value: string) => void\n onSave?: () => void\n readOnly?: boolean\n className?: string\n}\n\nexport function CodeEditor({ path, value, onChange, onSave, readOnly, className }: CodeEditorProps) {\n const host = useRef<HTMLDivElement>(null)\n const editor = useRef<Monaco.editor.IStandaloneCodeEditor | null>(null)\n const monaco = useRef<typeof Monaco | null>(null)\n const [ready, setReady] = useState(false)\n const theme = useDocumentTheme()\n\n const handlers = useRef({ onChange, onSave })\n handlers.current = { onChange, onSave }\n\n useEffect(() => {\n let disposed = false\n void loadMonaco().then((api) => {\n if (disposed || !host.current) {\n return\n }\n monaco.current = api\n const instance = api.editor.create(host.current, {\n value,\n language: languageOf(path),\n readOnly,\n automaticLayout: true,\n theme: monacoTheme(document.documentElement.getAttribute('data-theme')),\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n fontSize: 12,\n lineHeight: 20,\n fontFamily: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace',\n renderLineHighlight: 'line',\n smoothScrolling: true,\n padding: { top: 8, bottom: 8 },\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n wordBasedSuggestions: 'off',\n quickSuggestions: false,\n })\n editor.current = instance\n instance.onDidChangeModelContent(() => {\n handlers.current.onChange?.(instance.getValue())\n })\n instance.addCommand(api.KeyMod.CtrlCmd | api.KeyCode.KeyS, () => {\n handlers.current.onSave?.()\n })\n setReady(true)\n })\n return () => {\n disposed = true\n // The editor, never its models: a model is keyed by path and outlives this mount, which is what restores a reopened tab's undo stack.\n editor.current?.dispose()\n editor.current = null\n setReady(false)\n }\n }, [])\n\n useEffect(() => {\n const api = monaco.current\n const instance = editor.current\n if (!api || !instance || !ready) {\n return\n }\n const uri = api.Uri.parse(`workerdeck://host${path}`)\n const model = api.editor.getModel(uri) ?? api.editor.createModel(value, languageOf(path), uri)\n if (instance.getModel() !== model) {\n instance.setModel(model)\n }\n }, [path, ready, value])\n\n useEffect(() => {\n const instance = editor.current\n if (!instance || !ready) {\n return\n }\n const model = instance.getModel()\n if (!model || model.getValue() === value) {\n return\n }\n model.pushEditOperations([], [{ range: model.getFullModelRange(), text: value }], () => null)\n }, [value, ready])\n\n useEffect(() => {\n if (ready) {\n editor.current?.updateOptions({ readOnly })\n }\n }, [readOnly, ready])\n\n useEffect(() => {\n if (ready) {\n monaco.current?.editor.setTheme(monacoTheme(theme))\n }\n }, [theme, ready])\n\n return (\n <div className={cn('relative min-h-0 min-w-0 flex-1', className)}>\n <div ref={host} className=\"absolute inset-0\" />\n {!ready ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg\">\n <Spinner className=\"size-4 text-fg-4\" />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction useDocumentTheme(): string | null {\n const [theme, setTheme] = useState<string | null>(() =>\n typeof document === 'undefined' ? null : document.documentElement.getAttribute('data-theme'),\n )\n useEffect(() => {\n const root = document.documentElement\n const sync = () => setTheme(root.getAttribute('data-theme'))\n sync()\n const observer = new MutationObserver(sync)\n observer.observe(root, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [])\n return theme\n}\n\nfunction monacoTheme(theme: string | null): string {\n return theme === 'light' ? 'wd-light' : 'wd-dark'\n}\n\nlet monacoPromise: Promise<typeof Monaco> | undefined\nfunction loadMonaco(): Promise<typeof Monaco> {\n monacoPromise ??= (async () => {\n const api = await import('monaco-editor')\n api.editor.defineTheme('wd-dark', {\n base: 'vs-dark',\n inherit: true,\n rules: [],\n colors: { 'editor.background': '#00000000' },\n })\n api.editor.defineTheme('wd-light', {\n base: 'vs',\n inherit: true,\n rules: [],\n colors: { 'editor.background': '#00000000' },\n })\n return api\n })()\n return monacoPromise\n}\n\nfunction languageOf(path: string): string {\n const name = path.slice(path.lastIndexOf('/') + 1)\n const byName = FILENAME_LANGUAGES[name.toLowerCase()]\n if (byName) {\n return byName\n }\n const extension = name.slice(name.lastIndexOf('.') + 1).toLowerCase()\n return EXTENSION_LANGUAGES[extension] ?? 'plaintext'\n}\n\nconst FILENAME_LANGUAGES: Record<string, string> = {\n dockerfile: 'dockerfile',\n makefile: 'plaintext',\n '.gitignore': 'plaintext',\n '.env': 'shell',\n}\n\nconst EXTENSION_LANGUAGES: Record<string, string> = {\n ts: 'typescript',\n tsx: 'typescript',\n mts: 'typescript',\n cts: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n mjs: 'javascript',\n cjs: 'javascript',\n json: 'json',\n jsonc: 'json',\n md: 'markdown',\n mdx: 'markdown',\n css: 'css',\n scss: 'scss',\n less: 'less',\n html: 'html',\n htm: 'html',\n xml: 'xml',\n svg: 'xml',\n yml: 'yaml',\n yaml: 'yaml',\n toml: 'plaintext',\n sh: 'shell',\n bash: 'shell',\n zsh: 'shell',\n fish: 'shell',\n py: 'python',\n rb: 'ruby',\n rs: 'rust',\n go: 'go',\n swift: 'swift',\n java: 'java',\n kt: 'kotlin',\n c: 'c',\n h: 'c',\n cpp: 'cpp',\n cc: 'cpp',\n hpp: 'cpp',\n cs: 'csharp',\n php: 'php',\n sql: 'sql',\n graphql: 'graphql',\n gql: 'graphql',\n lua: 'lua',\n r: 'r',\n pl: 'perl',\n dart: 'dart',\n scala: 'scala',\n ini: 'ini',\n cfg: 'ini',\n conf: 'ini',\n txt: 'plaintext',\n log: 'plaintext',\n}\n","import type { ReactNode } from 'react'\nimport type { OpenFile } from '@workerdeck/react'\nimport { currentText, isDirty } from '@workerdeck/react'\nimport { FileWarning, TriangleAlert } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { CodeEditor } from './CodeEditor.tsx'\n\nexport interface FileViewerProps {\n file: OpenFile | undefined\n canWrite?: boolean\n onChange?: (path: string, content: string) => void\n onSave?: (path: string) => void\n onRevert?: (path: string) => void\n onReload?: (path: string) => void\n onOverwrite?: (path: string) => void\n onDismissConflict?: (path: string) => void\n className?: string\n}\n\nexport function FileViewer({\n file,\n canWrite,\n onChange,\n onSave,\n onRevert,\n onReload,\n onOverwrite,\n onDismissConflict,\n className,\n}: FileViewerProps) {\n if (!file) {\n return null\n }\n\n return (\n <div data-slot=\"file-viewer\" className={cn('flex min-h-0 min-w-0 flex-1 flex-col bg-bg', className)}>\n {file.conflict ? (\n <div className=\"flex shrink-0 flex-wrap items-center gap-2 border-b border-warning/40 bg-warning-bg px-3 py-2\">\n <TriangleAlert className=\"size-3.5 shrink-0 text-warning\" />\n <span className=\"min-w-0 flex-1 text-body-sm text-warning\">This file changed on disk since you opened it.</span>\n <Button variant=\"outline\" size=\"xs\" onClick={() => onReload?.(file.path)}>\n Use the version on disk\n </Button>\n <Button variant=\"outline\" size=\"xs\" onClick={() => onOverwrite?.(file.path)}>\n Keep mine\n </Button>\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onDismissConflict?.(file.path)}>\n Dismiss\n </Button>\n </div>\n ) : file.saveError ? (\n <div className=\"flex shrink-0 items-center gap-2 border-b border-danger/40 bg-danger-bg px-3 py-2\">\n <TriangleAlert className=\"size-3.5 shrink-0 text-danger\" />\n <span className=\"min-w-0 flex-1 text-body-sm text-danger\">{file.saveError}</span>\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onDismissConflict?.(file.path)}>\n Dismiss\n </Button>\n </div>\n ) : null}\n\n {file.status === 'loading' ? (\n <Centred>\n <Spinner className=\"size-4 text-fg-4\" />\n </Centred>\n ) : file.status === 'error' ? (\n <Centred>\n <TriangleAlert className=\"size-4 text-danger\" />\n <p className=\"text-body-sm text-danger\">{file.error}</p>\n </Centred>\n ) : file.status === 'binary' ? (\n <Centred>\n <FileWarning className=\"size-4 text-fg-4\" />\n <p className=\"text-body-sm text-fg-4\">This file isn’t text.</p>\n <p className=\"text-label text-fg-4\">It can’t be edited here without corrupting it.</p>\n </Centred>\n ) : (\n <CodeEditor\n path={file.path}\n value={currentText(file)}\n readOnly={!canWrite}\n onChange={onChange ? (content) => onChange(file.path, content) : undefined}\n onSave={onSave ? () => onSave(file.path) : undefined}\n />\n )}\n\n {file.status === 'ready' && (file.saving || isDirty(file) || !canWrite) ? (\n <div className=\"flex shrink-0 items-center gap-2 border-t border-border px-3 py-1\">\n {file.saving ? (\n <>\n <Spinner className=\"size-3 text-fg-4\" />\n <span className=\"text-label text-fg-4\">Saving…</span>\n </>\n ) : !canWrite ? (\n <span className=\"text-label text-fg-4\">Read-only — this gateway doesn’t allow writes.</span>\n ) : (\n <>\n <span className=\"text-label text-fg-3\">Unsaved changes</span>\n <span className=\"flex-1\" />\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onRevert?.(file.path)}>\n Revert\n </Button>\n <Button variant=\"outline\" size=\"xs\" onClick={() => onSave?.(file.path)}>\n Save\n <span className=\"ml-1 text-fg-4\">⌘S</span>\n </Button>\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction Centred({ children }: { children: ReactNode }) {\n return <div className=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-2 p-6\">{children}</div>\n}\n","import { useEffect, type RefObject } from 'react'\n\nexport type PathHit = { path: string; line?: number }\n\nconst LINKISH = 'wd-path-link'\n\nconst PATH_RE = /^(\\/[^\\s:'\"`()[\\]{}]+|[\\w.@~-]+(?:\\/[\\w.@~-]+)+)(?::(\\d+))?$/\nconst FILE_RE = /^([\\w.@-]+\\.[A-Za-z0-9]{1,10})(?::(\\d+))?$/\n\nconst COVERAGE = 0.6\n\nexport function matchPath(text: string, inCode: boolean): PathHit | undefined {\n const trimmed = text.trim()\n if (!trimmed || trimmed.endsWith('/')) {\n return undefined\n }\n const hit = PATH_RE.exec(trimmed) ?? (inCode ? FILE_RE.exec(trimmed) : null)\n if (!hit) {\n return undefined\n }\n return { path: hit[1]!, line: hit[2] ? Number(hit[2]) : undefined }\n}\n\nfunction hitFor(element: HTMLElement | undefined): PathHit | undefined {\n if (!element) {\n return undefined\n }\n const inCode = element.closest('code') !== null\n const text = element.textContent ?? ''\n const hit = matchPath(text, inCode)\n if (!hit) {\n return undefined\n }\n if (!inCode && hit.path.length < text.trim().length * COVERAGE) {\n return undefined\n }\n return hit\n}\n\nexport function usePathLinks({\n container,\n onOpen,\n enabled = true,\n ignore,\n}: {\n container: RefObject<HTMLElement | null>\n onOpen: (hit: PathHit) => void\n enabled?: boolean\n ignore?: string\n}) {\n useEffect(() => {\n const root = container.current\n if (!root || !enabled) {\n return\n }\n const excluded = (element: HTMLElement | undefined) =>\n ignore !== undefined && element?.closest(ignore) !== null && element !== undefined\n\n let marked: HTMLElement | undefined\n const unmark = () => {\n marked?.classList.remove(LINKISH)\n marked = undefined\n }\n\n const onClick = (event: MouseEvent) => {\n if (!event.metaKey && !event.ctrlKey) {\n return\n }\n const target = event.target instanceof HTMLElement ? event.target : undefined\n if (excluded(target)) {\n return\n }\n const hit = hitFor(target)\n if (!hit) {\n return\n }\n event.preventDefault()\n event.stopPropagation()\n unmark()\n onOpen(hit)\n }\n\n const onMove = (event: MouseEvent) => {\n if (!event.metaKey && !event.ctrlKey) {\n return unmark()\n }\n const element = event.target instanceof HTMLElement ? event.target : undefined\n if (element === marked) {\n return\n }\n unmark()\n if (!element || excluded(element) || !hitFor(element)) {\n return\n }\n marked = element\n element.classList.add(LINKISH)\n }\n\n const onKey = (event: KeyboardEvent) => {\n if (!event.metaKey && !event.ctrlKey) {\n unmark()\n }\n }\n\n root.addEventListener('click', onClick, true)\n root.addEventListener('mousemove', onMove)\n document.addEventListener('keydown', onKey)\n document.addEventListener('keyup', onKey)\n window.addEventListener('blur', unmark)\n return () => {\n unmark()\n root.removeEventListener('click', onClick, true)\n root.removeEventListener('mousemove', onMove)\n document.removeEventListener('keydown', onKey)\n document.removeEventListener('keyup', onKey)\n window.removeEventListener('blur', unmark)\n }\n }, [container, onOpen, enabled, ignore])\n}\n\nexport function resolveAgainstCwd(path: string, cwd: string | undefined): string {\n if (path.startsWith('/')) {\n return path\n }\n if (!cwd) {\n return path\n }\n return `${cwd.replace(/\\/$/, '')}/${path.replace(/^\\.\\//, '')}`\n}\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from 'react'\nimport { createPortal } from 'react-dom'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { useHostFileRoots, useHostFileSearch, useHostFileTree, useOpenFiles, useSessionInfo, isDirty } from '@workerdeck/react'\nimport { PanelLeftOpen } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Splitter } from '../ui/Splitter.tsx'\nimport { EditorTabs } from './EditorTabs.tsx'\nimport { FileTree } from './FileTree.tsx'\nimport { FileViewer } from './FileViewer.tsx'\nimport { SessionPanel, type SessionPanelProps } from './SessionPanel.tsx'\nimport { resolveAgainstCwd, usePathLinks } from './use-path-links.ts'\n\nexport interface SessionWorkspaceProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n header?: SessionPanelProps['header']\n transcriptVariant?: SessionPanelProps['transcriptVariant']\n transcriptDensity?: SessionPanelProps['transcriptDensity']\n transcriptFont?: SessionPanelProps['transcriptFont']\n scrubber?: SessionPanelProps['scrubber']\n bookmarks?: SessionPanelProps['bookmarks']\n onToggleBookmark?: SessionPanelProps['onToggleBookmark']\n stickyPrompt?: SessionPanelProps['stickyPrompt']\n openSubagent?: SessionPanelProps['openSubagent']\n reveal?: SessionPanelProps['reveal']\n subagents?: SessionPanelProps['subagents']\n onSubagentChange?: SessionPanelProps['onSubagentChange']\n statusPlacement?: SessionPanelProps['statusPlacement']\n controlsSurface?: SessionPanelProps['controlsSurface']\n fontSize?: SessionPanelProps['fontSize']\n onLinkClick?: SessionPanelProps['onLinkClick']\n unseen?: SessionPanelProps['unseen']\n readOnly?: SessionPanelProps['readOnly']\n onVitals?: SessionPanelProps['onVitals']\n defaultRailWidth?: number\n defaultRailCollapsed?: boolean\n onRailChange?: (rail: { width: number; collapsed: boolean }) => void\n className?: string\n}\n\nconst RAIL_MIN = 180\nconst RAIL_MAX = 520\nconst AGENT_MIN = 220\nconst EDITOR_MIN = 120\n\nexport function SessionWorkspace({\n client,\n sessionId,\n header,\n transcriptVariant,\n transcriptDensity,\n transcriptFont,\n scrubber,\n bookmarks,\n onToggleBookmark,\n stickyPrompt,\n openSubagent,\n reveal,\n subagents,\n onSubagentChange,\n statusPlacement,\n controlsSurface,\n fontSize,\n onLinkClick,\n unseen,\n readOnly,\n onVitals,\n defaultRailWidth = 260,\n defaultRailCollapsed,\n onRailChange,\n className,\n}: SessionWorkspaceProps) {\n const { info } = useSessionInfo(client, sessionId)\n const cwd = info?.cwd\n\n const tree = useHostFileTree(client, cwd)\n const search = useHostFileSearch(client, cwd)\n const files = useOpenFiles(client)\n const { canWrite } = useHostFileRoots(client)\n\n useEffect(() => {\n if (!files.hasUnsaved) {\n return\n }\n const warn = (event: BeforeUnloadEvent) => event.preventDefault()\n window.addEventListener('beforeunload', warn)\n return () => window.removeEventListener('beforeunload', warn)\n }, [files.hasUnsaved])\n\n const wide = useIsWide()\n const [railCollapsed, setRailCollapsed] = useState(defaultRailCollapsed ?? false)\n const [railWidth, setRailWidth] = useState(defaultRailWidth)\n const onRailChangeRef = useRef(onRailChange)\n onRailChangeRef.current = onRailChange\n useEffect(() => {\n onRailChangeRef.current?.({ width: railWidth, collapsed: railCollapsed })\n }, [railWidth, railCollapsed])\n const [editorHeight, setEditorHeight] = useState(360)\n\n const hasFiles = files.files.length > 0\n\n const overlayRail = !wide\n const railOpen = tree.available && !railCollapsed\n useEffect(() => {\n if (overlayRail) {\n setRailCollapsed(true)\n }\n }, [overlayRail])\n\n const closeTab = useCallback(\n (path: string) => {\n const file = files.files.find((f) => f.path === path)\n if (file && isDirty(file)) {\n const name = file.name\n if (!window.confirm(`${name} has unsaved changes. Close it and lose them?`)) {\n return\n }\n }\n files.close(path)\n },\n [files],\n )\n\n const column = useRef<HTMLDivElement>(null)\n const columnHeight = useElementHeight(column)\n const openPath = useCallback(({ path }: { path: string }) => files.open(resolveAgainstCwd(path, cwd)), [files.open, cwd])\n usePathLinks({\n container: column,\n onOpen: openPath,\n enabled: tree.available,\n ignore: '.monaco-editor',\n })\n const editorMax = Math.max(EDITOR_MIN, columnHeight - AGENT_MIN)\n\n const [topBar, setTopBar] = useState<HTMLDivElement | null>(null)\n const hoisted: SessionPanelProps['header'] =\n header === undefined || topBar === null\n ? undefined\n : typeof header === 'function'\n ? (slots) => createPortal(header(slots), topBar)\n : createPortal(header, topBar)\n\n return (\n <div data-slot=\"session-workspace\" className={cn('flex h-full min-h-0 w-full flex-col overflow-hidden bg-bg', className)}>\n {header !== undefined ? <div ref={setTopBar} className=\"shrink-0\" /> : null}\n <div className=\"relative flex min-h-0 flex-1\">\n {railOpen ? (\n <FileTree\n tree={tree}\n search={search}\n activePath={files.activePath}\n onOpenFile={files.open}\n onCollapse={() => setRailCollapsed(true)}\n style={{ width: overlayRail ? Math.min(railWidth, 320) : railWidth }}\n className={cn('shrink-0 border-r border-border', overlayRail && 'absolute inset-y-0 left-0 z-20 shadow-lg')}\n />\n ) : tree.available ? (\n <div className=\"flex w-8 shrink-0 flex-col items-center border-r border-border bg-surface pt-1.5\">\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Show project files\" onClick={() => setRailCollapsed(false)}>\n <PanelLeftOpen className=\"size-4 text-fg-3\" />\n </Button>\n </div>\n ) : null}\n {railOpen && !overlayRail ? (\n <Splitter\n orientation=\"vertical\"\n value={railWidth}\n onValueChange={setRailWidth}\n min={RAIL_MIN}\n max={RAIL_MAX}\n defaultValue={defaultRailWidth}\n aria-label=\"Resize the file tree\"\n />\n ) : null}\n\n <div ref={column} className=\"flex min-h-0 min-w-0 flex-1 flex-col\">\n {hasFiles ? (\n <div\n className=\"flex min-h-0 shrink-0 flex-col overflow-hidden\"\n style={{ height: Math.min(editorHeight, editorMax || editorHeight) }}\n >\n <EditorTabs files={files.files} activePath={files.activePath} onActivate={files.activate} onClose={closeTab} />\n <FileViewer\n file={files.active}\n canWrite={canWrite}\n onChange={files.edit}\n onSave={(path) => void files.save(path)}\n onRevert={files.revert}\n onReload={files.reload}\n onOverwrite={(path) => void files.overwrite(path)}\n onDismissConflict={files.dismissConflict}\n />\n </div>\n ) : null}\n {hasFiles ? (\n <Splitter\n orientation=\"horizontal\"\n value={Math.min(editorHeight, editorMax || editorHeight)}\n onValueChange={setEditorHeight}\n min={EDITOR_MIN}\n max={editorMax}\n aria-label=\"Resize the open file\"\n />\n ) : null}\n <SessionPanel\n client={client}\n sessionId={sessionId}\n header={hoisted}\n transcriptVariant={transcriptVariant}\n transcriptDensity={transcriptDensity}\n transcriptFont={transcriptFont}\n scrubber={scrubber}\n bookmarks={bookmarks}\n onToggleBookmark={onToggleBookmark}\n stickyPrompt={stickyPrompt}\n openSubagent={openSubagent}\n reveal={reveal}\n subagents={subagents}\n onSubagentChange={onSubagentChange}\n controlsSurface={controlsSurface}\n fontSize={fontSize}\n onLinkClick={onLinkClick}\n statusPlacement={statusPlacement}\n unseen={unseen}\n readOnly={readOnly}\n onVitals={onVitals}\n className=\"min-h-0 flex-1\"\n />\n </div>\n\n {overlayRail && railOpen ? (\n <button\n type=\"button\"\n aria-label=\"Close the file tree\"\n onClick={() => setRailCollapsed(true)}\n className=\"absolute inset-0 z-10 bg-black/30\"\n />\n ) : null}\n </div>\n </div>\n )\n}\n\nfunction useElementHeight(ref: RefObject<HTMLElement | null>): number {\n const [height, setHeight] = useState(0)\n useLayoutEffect(() => {\n const element = ref.current\n if (!element) {\n return\n }\n const observer = new ResizeObserver(([entry]) => {\n if (entry) {\n setHeight(entry.contentRect.height)\n }\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [ref])\n return height\n}\n\nfunction useIsWide(): boolean {\n const [wide, setWide] = useState(true)\n useEffect(() => {\n const query = window.matchMedia('(min-width: 768px)')\n const sync = () => setWide(query.matches)\n sync()\n query.addEventListener('change', sync)\n return () => query.removeEventListener('change', sync)\n }, [])\n return wide\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workerdeck/ui",
3
- "version": "1.4.0",
3
+ "version": "2.1.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",
@@ -39,9 +39,9 @@
39
39
  "streamdown": "^2.6.0",
40
40
  "tailwind-merge": "^3.6.0",
41
41
  "use-stick-to-bottom": "^1.1.6",
42
- "@workerdeck/client": "1.4.0",
43
- "@workerdeck/protocol": "1.4.0",
44
- "@workerdeck/react": "1.4.0"
42
+ "@workerdeck/protocol": "2.1.0",
43
+ "@workerdeck/react": "2.1.0",
44
+ "@workerdeck/client": "2.1.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "monaco-editor": "^0.56.0",
@@ -35,7 +35,6 @@ export interface SessionBrowserProps {
35
35
  onRename?: (row: SessionRow, title: string) => void
36
36
  onClearContext?: (row: SessionRow) => void
37
37
  onSelectSubagent?: (row: SessionRow, toolUseId: string) => void
38
- onRevealStep?: (row: SessionRow, toolUseId: string) => void
39
38
  emptyState?: React.ReactNode
40
39
  showControls?: boolean
41
40
  projectIcons?: Record<string, string>
@@ -58,7 +57,6 @@ export function SessionBrowser({
58
57
  onRename,
59
58
  onClearContext,
60
59
  onSelectSubagent,
61
- onRevealStep,
62
60
  emptyState,
63
61
  showControls = true,
64
62
  projectIcons,
@@ -217,7 +215,6 @@ export function SessionBrowser({
217
215
  onRename={onRename}
218
216
  onClearContext={onClearContext}
219
217
  onSelectSubagent={onSelectSubagent}
220
- onRevealStep={onRevealStep}
221
218
  />
222
219
  ))}
223
220
  </div>
@@ -245,7 +242,6 @@ interface SessionRowItemProps {
245
242
  onRename?: (row: SessionRow, title: string) => void
246
243
  onClearContext?: (row: SessionRow) => void
247
244
  onSelectSubagent?: (row: SessionRow, toolUseId: string) => void
248
- onRevealStep?: (row: SessionRow, toolUseId: string) => void
249
245
  }
250
246
 
251
247
  function SessionRowItem({
@@ -260,7 +256,6 @@ function SessionRowItem({
260
256
  onRename,
261
257
  onClearContext,
262
258
  onSelectSubagent,
263
- onRevealStep,
264
259
  }: SessionRowItemProps) {
265
260
  const { info } = row
266
261
  const [editing, setEditing] = useState(false)
@@ -275,7 +270,6 @@ function SessionRowItem({
275
270
  projectIcons={projectIcons}
276
271
  onSelect={() => onSelect?.(row)}
277
272
  onSelectSubagent={onSelectSubagent ? (id) => onSelectSubagent(row, id) : undefined}
278
- onRevealStep={onRevealStep ? (id) => onRevealStep(row, id) : undefined}
279
273
  onRename={onRename ? (title) => onRename(row, title) : undefined}
280
274
  renameOn="external"
281
275
  editing={editing}
@@ -22,7 +22,6 @@ export interface SessionItemProps {
22
22
  onExpandedChange?: (expanded: boolean) => void
23
23
  onSelect?: () => void
24
24
  onSelectSubagent?: (toolUseId: string) => void
25
- onRevealStep?: (toolUseId: string) => void
26
25
  onRename?: (title: string) => void
27
26
  renameOn?: 'doubleClick' | 'external'
28
27
  editing?: boolean
@@ -42,7 +41,6 @@ export function SessionItem({
42
41
  onExpandedChange,
43
42
  onSelect,
44
43
  onSelectSubagent,
45
- onRevealStep,
46
44
  onRename,
47
45
  renameOn = 'doubleClick',
48
46
  editing,
@@ -95,13 +93,8 @@ export function SessionItem({
95
93
  for (const extra of extras) {
96
94
  parts.push(<span key={extra}>{extra}</span>)
97
95
  }
98
- const steps = sessionSteps(info, (toolUseId, kind) => {
99
- if (kind === 'agent') {
100
- return onSelectSubagent ? onSelectSubagent(toolUseId) : onSelect?.()
101
- }
102
- return onRevealStep ? onRevealStep(toolUseId) : onSelect?.()
103
- })
104
- const holdsOpenAgent = steps.some((s) => s.kind === 'agent' && s.key === activeStepKey)
96
+ const steps = sessionSteps(info, (toolUseId) => (onSelectSubagent ? onSelectSubagent(toolUseId) : onSelect?.()))
97
+ const holdsOpenAgent = steps.some((s) => s.key === activeStepKey)
105
98
 
106
99
  return (
107
100
  <div
@@ -4,11 +4,14 @@ import {
4
4
  PROTOCOL_VERSION,
5
5
  mergeUsage,
6
6
  orderUsageWindows,
7
+ sessionTasks,
7
8
  usageInfos,
8
9
  type ModelOption,
9
10
  type PermissionMode,
10
11
  type RateLimitInfo,
12
+ type SessionTask,
11
13
  type SkillInfo,
14
+ type SubagentInfo,
12
15
  } from '@workerdeck/protocol'
13
16
  import {
14
17
  useAttachments,
@@ -29,6 +32,7 @@ import { Button } from '../ui/Button.tsx'
29
32
  import { Menu, MenuContent, MenuItem, MenuTrigger } from '../ui/Menu.tsx'
30
33
  import { Composer, skillPrompt, type ComposerHandle } from './Composer.tsx'
31
34
  import { ContextDialog } from './ContextDialog.tsx'
35
+ import { TasksDialog } from './TasksDialog.tsx'
32
36
  import { HostFilesDialog } from './HostFilesDialog.tsx'
33
37
  import { McpDialog } from './McpDialog.tsx'
34
38
  import { SkillsDialog } from './SkillsDialog.tsx'
@@ -46,7 +50,6 @@ import { BookmarkProvider, type BookmarkHandle, type TerminalAffordances } from
46
50
  import { SessionInfoDialog } from './SessionInfoDialog.tsx'
47
51
  import { StatusBar } from './StatusBar.tsx'
48
52
  import { Transcript } from './Transcript.tsx'
49
- import { HeldSendsBar, useHeldSends } from './held-sends.tsx'
50
53
  import { ToolResultFetchProvider } from './tool-result-fetch.tsx'
51
54
  import { ToolTitleProvider } from './tool-titles.tsx'
52
55
  import { ToolResultImageProvider, useToolResultImages } from './tool-result-image.tsx'
@@ -107,12 +110,11 @@ export interface SessionPanelProps {
107
110
  reveal?: { toolUseId: string; nonce: number }
108
111
  openSubagent?: { toolUseId: string; nonce: number }
109
112
  onSubagentChange?: (toolUseId: string | undefined) => void
113
+ // The attach frame's `state.session` never refreshes, so a host that polls the sessions list passes the live records here.
114
+ subagents?: SubagentInfo[]
110
115
  stickyPrompt?: boolean
111
116
  transcriptDensity?: TranscriptDensity
112
117
  transcriptFont?: TranscriptFont
113
- // 'fold' sends a mid-turn message straight through, and the engine folds it into the running
114
- // turn (catch-up mode). 'hold' keeps it here until the turn ends.
115
- midTurnSend?: 'fold' | 'hold'
116
118
  controlsSurface?: 'internal' | 'external' | 'status'
117
119
  onControls?: (controls: SessionControls | undefined) => void
118
120
  focusComposerOnClick?: boolean
@@ -151,7 +153,7 @@ const INTERACTIVE = [
151
153
  '[role="tab"]',
152
154
  ].join(',')
153
155
 
154
- export type SessionSurfacePanel = 'info' | 'context' | 'usage' | 'mcp' | 'files' | 'skills'
156
+ export type SessionSurfacePanel = 'info' | 'context' | 'usage' | 'mcp' | 'files' | 'skills' | 'tasks'
155
157
  type Panel = SessionSurfacePanel
156
158
 
157
159
  export type SessionVitals = {
@@ -166,6 +168,7 @@ export type SessionVitals = {
166
168
  skills: SkillInfo[] | undefined
167
169
  cwd: TranscriptState['cwd']
168
170
  contextUsage: TranscriptState['contextUsage']
171
+ tasks: SessionTask[]
169
172
  rateLimits: TranscriptState['rateLimits']
170
173
  // When the newest window in `rateLimits` was reported, as event time — not receive time. External chrome (the
171
174
  // VS Code status bar, iOS) cannot otherwise tell a live reading from one a days-old session just replayed.
@@ -187,13 +190,13 @@ export function SessionPanel({
187
190
  transcriptVariant = 'cards',
188
191
  transcriptDensity = 'comfortable',
189
192
  transcriptFont = 'sans',
190
- midTurnSend = 'fold',
191
193
  affordances,
192
194
  terminalMetrics,
193
195
  scrubber = false,
194
196
  bookmarks,
195
197
  onToggleBookmark,
196
198
  reveal,
199
+ subagents,
197
200
  openSubagent,
198
201
  onSubagentChange,
199
202
  stickyPrompt = false,
@@ -219,6 +222,8 @@ export function SessionPanel({
219
222
  const controlsExternal = controlsSurface === 'external' || controlsInStatus
220
223
  const [protocolError, setProtocolError] = useState<string | undefined>(undefined)
221
224
  const [panel, setPanel] = useState<Panel | undefined>()
225
+ const [showCompletedTasks, setShowCompletedTasks] = useState(false)
226
+ const [taskReveal, setTaskReveal] = useState<{ toolUseId: string; nonce: number } | undefined>()
222
227
  const {
223
228
  state,
224
229
  connection,
@@ -302,6 +307,11 @@ export function SessionPanel({
302
307
  return stamps.length > 0 ? Math.max(...stamps) : undefined
303
308
  }, [usage])
304
309
 
310
+ const tasks = useMemo(
311
+ () => sessionTasks({ checklist: state.checklist, subagents: subagents ?? state.session?.subagents }),
312
+ [state.checklist, subagents, state.session?.subagents],
313
+ )
314
+
305
315
  const onVitalsRef = useRef(onVitals)
306
316
  onVitalsRef.current = onVitals
307
317
  const vitalsModel = effectiveModel ?? state.model
@@ -322,6 +332,7 @@ export function SessionPanel({
322
332
  skills: state.skills,
323
333
  cwd: state.cwd,
324
334
  contextUsage: state.contextUsage,
335
+ tasks,
325
336
  rateLimits,
326
337
  rateLimitsUpdatedAt: usageUpdatedAt,
327
338
  itemCount: state.items.length,
@@ -339,6 +350,7 @@ export function SessionPanel({
339
350
  state.skills,
340
351
  state.cwd,
341
352
  state.contextUsage,
353
+ tasks,
342
354
  rateLimits,
343
355
  usageUpdatedAt,
344
356
  state.items.length,
@@ -399,8 +411,6 @@ export function SessionPanel({
399
411
  return [{ name: 'model', description: 'Switch the model for this session', argumentHint: '<model>' }, ...state.commands]
400
412
  }, [state.commands])
401
413
 
402
- const heldSends = useHeldSends({ hold: midTurnSend === 'hold', busy: busy && !ended, send })
403
-
404
414
  const handleSend = (text: string, attachmentIds: string[]) => {
405
415
  if (attachmentIds.length === 0) {
406
416
  const modelCommand = /^\/model\s+(\S+)$/.exec(text)
@@ -415,7 +425,7 @@ export function SessionPanel({
415
425
  }
416
426
  setCaughtUp(true)
417
427
  repinTranscript.current?.()
418
- heldSends.submit(text, attachmentIds)
428
+ send(text, attachmentIds)
419
429
  }
420
430
 
421
431
  const actionsMenu = (
@@ -497,6 +507,8 @@ export function SessionPanel({
497
507
  onOpenStatus={external && !onOpenPanel ? undefined : () => openPanel('info')}
498
508
  onOpenContext={external && !onOpenPanel ? undefined : () => openPanel('context')}
499
509
  onOpenUsage={external && !onOpenPanel ? undefined : () => openPanel('usage')}
510
+ tasks={tasks}
511
+ onOpenTasks={external && !onOpenPanel ? undefined : () => openPanel('tasks')}
500
512
  actions={headerTakesActions ? undefined : menu}
501
513
  />
502
514
  )
@@ -597,7 +609,7 @@ export function SessionPanel({
597
609
  bookmarks={bookmarks}
598
610
  replaying={replaying}
599
611
  catchUp={catchUp && newCount > 0 ? { from: catchUp.itemCount, since: catchUp.since } : undefined}
600
- reveal={returnReveal ?? reveal}
612
+ reveal={taskReveal ?? returnReveal ?? reveal}
601
613
  frame={subagentId === undefined ? undefined : { parentToolUseId: subagentId }}
602
614
  onOpenSubagent={enterSubagent}
603
615
  emptyState={emptyState}
@@ -672,7 +684,6 @@ export function SessionPanel({
672
684
  ) : null}
673
685
  {readOnly || subagentId !== undefined ? null : (
674
686
  <>
675
- <HeldSendsBar held={heldSends.held} onSendNow={heldSends.flush} />
676
687
  <Composer
677
688
  ref={composerRef}
678
689
  onSend={handleSend}
@@ -725,6 +736,18 @@ export function SessionPanel({
725
736
  open={panel === 'mcp'}
726
737
  onOpenChange={(next) => setPanel(next ? 'mcp' : undefined)}
727
738
  />
739
+ <TasksDialog
740
+ tasks={tasks}
741
+ showCompleted={showCompletedTasks}
742
+ onShowCompletedChange={setShowCompletedTasks}
743
+ onSelectTask={(task) => {
744
+ if (task.toolUseId) {
745
+ setTaskReveal({ toolUseId: task.toolUseId, nonce: Date.now() })
746
+ }
747
+ }}
748
+ open={panel === 'tasks'}
749
+ onOpenChange={(next) => setPanel(next ? 'tasks' : undefined)}
750
+ />
728
751
  <SkillsDialog
729
752
  skills={state.skills}
730
753
  open={panel === 'skills'}