@workerdeck/ui 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/{SessionPanel-Dg4xf-ql.mjs → SessionPanel-BClL7zUS.mjs} +649 -368
- package/build/SessionPanel-BClL7zUS.mjs.map +1 -0
- package/build/{SessionPanel-_lQLhWOw.d.mts → SessionPanel-BVbau2eR.d.mts} +68 -4
- package/build/index.d.mts +11 -4
- package/build/index.mjs +3 -3
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +24 -0
- package/build/workspace.d.mts +3 -2
- package/build/workspace.mjs +4 -5
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/Composer.tsx +84 -82
- package/src/components/agent/SessionItem.tsx +3 -1
- package/src/components/agent/SessionPanel.tsx +304 -224
- package/src/components/agent/SessionWorkspace.tsx +3 -0
- package/src/components/agent/UsageMeters.tsx +17 -4
- package/src/components/agent/composer-commands.ts +142 -0
- package/src/components/agent/use-path-links.ts +2 -7
- package/src/components/terminal/file-link.tsx +20 -0
- package/src/components/terminal/markdown.tsx +31 -5
- package/src/index.ts +11 -1
- package/src/lib/file-link.ts +70 -0
- package/src/styles/terminal.css +15 -0
- package/build/SessionPanel-Dg4xf-ql.mjs.map +0 -1
package/build/workspace.mjs.map
CHANGED
|
@@ -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 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":";;;;;;;;AAeA,SAAgB,WAAW,EAAE,OAAO,YAAY,YAAY,SAAS,aAA8B;CACjG,OACE,oBAAC,iBAAD;EAAiB,OAAO;EAAK,YAAY;EACvC,UAAA,oBAAC,OAAD;GACE,aAAU;GACV,MAAK;GACL,cAAW;GACX,WAAW,GAAG,iFAAiF,SAAS;GAEvG,UAAA,MAAM,KAAK,SACV,oBAAC,KAAD;IAEQ;IACN,QAAQ,KAAK,SAAS;IACtB,kBAAkB,WAAW,KAAK,IAAI;IACtC,eAAe,QAAQ,KAAK,IAAI;IAChC,UAAU,cAAc;KAEtB,MAAM,OAAO,MADC,MAAM,WAAW,MAAM,EAAE,SAAS,KAAK,IAC9B,IAAI;KAC3B,IAAI,MACF,WAAW,KAAK,IAAI;IAExB;GACD,GAZM,KAAK,IAYX,CACF;EACE,CAAA;CACU,CAAA;AAErB;AAEA,SAAS,IAAI,EACX,MACA,QACA,YACA,SACA,WAOC;CACD,MAAM,QAAQ,QAAQ,IAAI;CAC1B,MAAM,MAAM,OAAuB,IAAI;CACvC,gBAAgB;EACd,IAAI,QACF,IAAI,SAAS,eAAe;GAAE,OAAO;GAAW,QAAQ;EAAU,CAAC;CAEvE,GAAG,CAAC,MAAM,CAAC;CAEX,MAAM,cAAc,UAA2B;EAC7C,IAAI,MAAM,WAAW,GACnB;EAEF,MAAM,eAAe;EACrB,QAAQ;CACV;CAEA,OACE,qBAAC,KAAD;EACE,MAAK;EACL,QACE,oBAAC,OAAD;GACO;GACL,MAAK;GACL,UAAU,SAAS,IAAI;GACvB,iBAAe;GACf,SAAS;GACG;GACZ,YAAY,UAAU;IACpB,IAAI,MAAM,QAAQ,cAChB,QAAQ,CAAC;SACJ,IAAI,MAAM,QAAQ,aACvB,QAAQ,EAAE;SACL,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAChD,WAAW;SAEX;IAEF,MAAM,eAAe;GACvB;GACA,WAAW,GACT,wHACA,sGACA,SAAS,oBAAoB,kDAC/B;EACD,CAAA;EAEH,SACE,qBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,oBAAC,QAAD;IAAM,WAAU;IAAuB,UAAA,KAAK;GAAW,CAAA,GACtD,KAAK,UAAU,KAAA,IAAY,oBAAC,QAAD;IAAM,WAAU;IAAa,UAAA,YAAY,KAAK,KAAK;GAAQ,CAAA,IAAI,IACvF;;EAjCV,UAAA;GAoCE,oBAAC,QAAD;IAAM,WAAW,GAAG,0CAA0C,SAAS,QAAQ;IAAI,UAAA,KAAK;GAAW,CAAA;GAClG,KAAK,WAAW,UAAU,oBAAC,QAAD;IAAM,WAAU;IAAuB,UAAA;GAAO,CAAA,IAAI;GAC7E,qBAAC,UAAD;IACE,MAAK;IACL,cAAY,QAAQ,SAAS,KAAK,KAAK,sBAAsB,SAAS,KAAK;IAC3E,UAAU,UAAU;KAClB,MAAM,gBAAgB;KACtB,QAAQ;IACV;IACA,WAAW,GACT,8FACA,UAAU,QAAQ,eAAe,4DACnC;IAVF,UAAA,CAYE,oBAAC,QAAD;KAAM,WAAW,GAAG,SAAS,SAAS,oBAAoB;KACvD,UAAA,QAAQ,oBAAC,QAAD,EAAM,WAAU,oCAAqC,CAAA,IAAI,oBAAC,GAAD,EAAG,WAAU,SAAU,CAAA;IACrF,CAAA,GACL,QACC,oBAAC,QAAD;KAAM,WAAU;KACd,UAAA,oBAAC,GAAD,EAAG,WAAU,SAAU,CAAA;IACnB,CAAA,IACJ,IACE;;EACL;;AAET;;;ACpHA,MAAM,SAAS;AAEf,SAAgB,SAAS,EAAE,MAAM,QAAQ,YAAY,YAAY,YAAY,OAAO,aAA4B;CAC9G,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CACrC,MAAM,CAAC,SAAS,cAAc,SAAsC;CACpE,MAAM,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS;CAExC,gBAAgB;EACd,IAAI,CAAC,QAAQ,WACX;EAEF,MAAM,IAAI,MAAM,KAAK;EACrB,IAAI,CAAC,GAAG;GACN,WAAW,KAAA,CAAS;GACpB;EACF;EACA,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,QAAQ,iBAAiB;GAC7B,OAAY,OAAO,GAAG;IAAE,OAAO;IAAI,QAAQ,WAAW;GAAO,CAAC,CAAC,CAAC,MAAM,UAAU;IAC9E,IAAI,CAAC,WAAW,OAAO,SACrB,WAAW,KAAK;GAEpB,CAAC;EACH,GAAG,GAAG;EACN,aAAa;GACX,WAAW,MAAM;GACjB,aAAa,KAAK;EACpB;CACF,GAAG,CAAC,QAAQ,KAAK,CAAC;CAElB,OACE,qBAAC,OAAD;EAAK,aAAU;EAAmB;EAAO,WAAW,GAAG,4CAA4C,SAAS;EAA5G,UAAA,CACE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACG,QAAQ,YACP,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACE,oBAAC,QAAD,EAAQ,WAAU,8DAA+D,CAAA;MACjF,oBAAC,OAAD;OACE,OAAO;OACP,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;OACxC,aAAY;OACZ,WAAU;OACV,YAAY;MACb,CAAA;MACA,YACC,oBAAC,UAAD;OACE,MAAK;OACL,eAAe,SAAS,EAAE;OAC1B,cAAW;OACX,WAAU;OAEV,UAAA,oBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MACnB,CAAA,IACN;KACD;IAEL,CAAA,IAAA,oBAAC,QAAD;KAAM,WAAU;KAAqD,UAAA,KAAK;IAAW,CAAA;IAEvF,oBAAC,QAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAU,cAAW;KAAwB,eAAe,KAAK,QAAQ;KACpG,UAAA,oBAAC,WAAD,EAAW,WAAU,qBAAsB,CAAA;IACrC,CAAA;IACP,aACC,oBAAC,QAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAU,cAAW;KAAqB,SAAS;KAC9E,UAAA,oBAAC,gBAAD,EAAgB,WAAU,qBAAsB,CAAA;IAC1C,CAAA,IACN;GACD;EAEL,CAAA,GAAA,oBAAC,OAAD;GAAK,WAAU;GACZ,UAAA,KAAK,QACJ,oBAAC,KAAD;IAAG,WAAU;IAAsC,UAAA,KAAK;GAAS,CAAA,IAC/D,YACF,YAAY,KAAA,IACV,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,SAAD,EAAS,WAAU,mBAAoB,CAAA;GACpC,CAAA,IACH,QAAQ,WAAW,IACrB,oBAAC,KAAD;IAAG,WAAU;IAA+C,UAAA;GAAqB,CAAA,IAEjF,oBAAC,MAAD,EAAA,UACG,QAAQ,KAAK,UACZ,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;IACE,OAAO,SAAS,MAAM,QAAQ;IAC9B,QAAQ,YAAY,MAAM,QAAQ;IAClC,OAAO,MAAM;IACb,MAAM,oBAAC,MAAD,EAAM,WAAU,8BAA+B,CAAA;IACrD,QAAQ,MAAM,SAAS;IACvB,eAAe,WAAW,MAAM,IAAI;GACrC,CAAA,EACC,GATK,MAAM,IASX,CACL,EACC,CAAA,IAEJ,KAAK,UACP,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,SAAD,EAAS,WAAU,mBAAoB,CAAA;GACpC,CAAA,IACH,KAAK,KAAK,WAAW,IACvB,oBAAC,KAAD;IAAG,WAAU;IAA+C,UAAA;GAAyB,CAAA,IAErF,oBAAC,MAAD;IAAI,MAAK;IAAO,cAAW;IACxB,UAAA,KAAK,KAAK,KAAK,QACd,qBAAC,MAAD;KAAyB,MAAK;KAAW,iBAAe,IAAI;KAAU,cAAY,IAAI,QAAQ;KAA9F,UAAA,CACE,oBAAC,KAAD;MACE,OAAO,IAAI,MAAM;MACjB,QAAQ,IAAI;MACZ,QAAQ,IAAI,MAAM,SAAS;MAC3B,MACE,IAAI,MAAM,SAAS,QACjB,IAAI,UACF,oBAAC,SAAD,EAAS,WAAU,8BAA+B,CAAA,IAChD,IAAI,WACN,oBAAC,YAAD,EAAY,WAAU,gCAAiC,CAAA,IAEvD,oBAAC,QAAD,EAAQ,WAAU,gCAAiC,CAAA,IAGrD,oBAAC,WAAD,EAAW,MAAM,IAAI,MAAM,KAAO,CAAA;MAGtC,SACE,IAAI,MAAM,SAAS,QACjB,oBAAC,cAAD,EAAc,WAAW,GAAG,kDAAkD,IAAI,YAAY,WAAW,EAAI,CAAA,IAC3G,KAAA;MAEN,eAAgB,IAAI,MAAM,SAAS,QAAQ,KAAK,OAAO,IAAI,MAAM,IAAI,IAAI,WAAW,IAAI,MAAM,IAAI;KACnG,CAAA,GACA,IAAI,YACH,oBAAC,KAAD;MAAG,WAAU;MAAuC,OAAO,EAAE,cAAc,IAAI,QAAQ,KAAK,SAAS,GAAG;MAAG,UAAA;KAExG,CAAA,IACD,IACF;IA9BK,GAAA,IAAI,MAAM,IA8Bf,CACL;GACC,CAAA;EAEH,CAAA,CACF;;AAET;AAEA,SAAS,IAAI,EACX,OACA,QACA,OACA,MACA,SACA,SAAS,GACT,QACA,WAUC;CACD,MAAM,MAAM,OAA0B,IAAI;CAC1C,gBAAgB;EACd,IAAI,QACF,IAAI,SAAS,eAAe,EAAE,OAAO,UAAU,CAAC;CAEpD,GAAG,CAAC,MAAM,CAAC;CAEX,OACE,qBAAC,UAAD;EACO;EACL,MAAK;EACI;EACT,OAAO,SAAS;EAChB,OAAO,EAAE,aAAa,SAAS,SAAS,EAAE;EAC1C,WAAW,GACT,gFACA,SAAS,+BAA+B,kCAC1C;EATF,UAAA;GAWE,oBAAC,QAAD;IAAM,WAAU;IAAoD,UAAA;GAAc,CAAA;GACjF;GACD,oBAAC,QAAD;IAAM,WAAU;IAAgC,UAAA;GAAY,CAAA;GAC3D,SAAS,oBAAC,QAAD;IAAM,WAAU;IAAgD,UAAA;GAAa,CAAA,IAAI;EACrF;;AAEZ;AAEA,SAAS,SAAS,UAA0B;CAC1C,OAAO,SAAS,MAAM,SAAS,YAAY,GAAG,IAAI,CAAC;AACrD;AAEA,SAAS,YAAY,UAAsC;CACzD,MAAM,MAAM,SAAS,YAAY,GAAG;CACpC,OAAO,QAAQ,KAAK,KAAA,IAAY,SAAS,MAAM,GAAG,GAAG;AACvD;;;ACxMA,SAAgB,WAAW,EAAE,MAAM,OAAO,UAAU,QAAQ,UAAU,aAA8B;CAClG,MAAM,OAAO,OAAuB,IAAI;CACxC,MAAM,SAAS,OAAmD,IAAI;CACtE,MAAM,SAAS,OAA6B,IAAI;CAChD,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CACxC,MAAM,QAAQ,iBAAiB;CAE/B,MAAM,WAAW,OAAO;EAAE;EAAU;CAAO,CAAC;CAC5C,SAAS,UAAU;EAAE;EAAU;CAAO;CAEtC,gBAAgB;EACd,IAAI,WAAW;EACf,WAAgB,CAAC,CAAC,MAAM,QAAQ;GAC9B,IAAI,YAAY,CAAC,KAAK,SACpB;GAEF,OAAO,UAAU;GACjB,MAAM,WAAW,IAAI,OAAO,OAAO,KAAK,SAAS;IAC/C;IACA,UAAU,WAAW,IAAI;IACzB;IACA,iBAAiB;IACjB,OAAO,YAAY,SAAS,gBAAgB,aAAa,YAAY,CAAC;IACtE,SAAS,EAAE,SAAS,MAAM;IAC1B,sBAAsB;IACtB,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,qBAAqB;IACrB,iBAAiB;IACjB,SAAS;KAAE,KAAK;KAAG,QAAQ;IAAE;IAC7B,WAAW;KAAE,uBAAuB;KAAI,yBAAyB;IAAG;IACpE,sBAAsB;IACtB,kBAAkB;GACpB,CAAC;GACD,OAAO,UAAU;GACjB,SAAS,8BAA8B;IACrC,SAAS,QAAQ,WAAW,SAAS,SAAS,CAAC;GACjD,CAAC;GACD,SAAS,WAAW,IAAI,OAAO,UAAU,IAAI,QAAQ,YAAY;IAC/D,SAAS,QAAQ,SAAS;GAC5B,CAAC;GACD,SAAS,IAAI;EACf,CAAC;EACD,aAAa;GACX,WAAW;GAEX,OAAO,SAAS,QAAQ;GACxB,OAAO,UAAU;GACjB,SAAS,KAAK;EAChB;CACF,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,MAAM,MAAM,OAAO;EACnB,MAAM,WAAW,OAAO;EACxB,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,OACxB;EAEF,MAAM,MAAM,IAAI,IAAI,MAAM,oBAAoB,MAAM;EACpD,MAAM,QAAQ,IAAI,OAAO,SAAS,GAAG,KAAK,IAAI,OAAO,YAAY,OAAO,WAAW,IAAI,GAAG,GAAG;EAC7F,IAAI,SAAS,SAAS,MAAM,OAC1B,SAAS,SAAS,KAAK;CAE3B,GAAG;EAAC;EAAM;EAAO;CAAK,CAAC;CAEvB,gBAAgB;EACd,MAAM,WAAW,OAAO;EACxB,IAAI,CAAC,YAAY,CAAC,OAChB;EAEF,MAAM,QAAQ,SAAS,SAAS;EAChC,IAAI,CAAC,SAAS,MAAM,SAAS,MAAM,OACjC;EAEF,MAAM,mBAAmB,CAAC,GAAG,CAAC;GAAE,OAAO,MAAM,kBAAkB;GAAG,MAAM;EAAM,CAAC,SAAS,IAAI;CAC9F,GAAG,CAAC,OAAO,KAAK,CAAC;CAEjB,gBAAgB;EACd,IAAI,OACF,OAAO,SAAS,cAAc,EAAE,SAAS,CAAC;CAE9C,GAAG,CAAC,UAAU,KAAK,CAAC;CAEpB,gBAAgB;EACd,IAAI,OACF,OAAO,SAAS,OAAO,SAAS,YAAY,KAAK,CAAC;CAEtD,GAAG,CAAC,OAAO,KAAK,CAAC;CAEjB,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,mCAAmC,SAAS;EAA/D,UAAA,CACE,oBAAC,OAAD;GAAK,KAAK;GAAM,WAAU;EAAoB,CAAA,GAC7C,CAAC,QACA,oBAAC,OAAD;GAAK,WAAU;GACb,UAAA,oBAAC,SAAD,EAAS,WAAU,mBAAoB,CAAA;EACpC,CAAA,IACH,IACD;;AAET;AAEA,SAAS,mBAAkC;CACzC,MAAM,CAAC,OAAO,YAAY,eACxB,OAAO,aAAa,cAAc,OAAO,SAAS,gBAAgB,aAAa,YAAY,CAC7F;CACA,gBAAgB;EACd,MAAM,OAAO,SAAS;EACtB,MAAM,aAAa,SAAS,KAAK,aAAa,YAAY,CAAC;EAC3D,KAAK;EACL,MAAM,WAAW,IAAI,iBAAiB,IAAI;EAC1C,SAAS,QAAQ,MAAM;GAAE,YAAY;GAAM,iBAAiB,CAAC,YAAY;EAAE,CAAC;EAC5E,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,CAAC;CACL,OAAO;AACT;AAEA,SAAS,YAAY,OAA8B;CACjD,OAAO,UAAU,UAAU,aAAa;AAC1C;AAEA,IAAI;AACJ,SAAS,aAAqC;CAC5C,mBAAmB,YAAY;EAC7B,MAAM,MAAM,MAAM,OAAO;EACzB,IAAI,OAAO,YAAY,WAAW;GAChC,MAAM;GACN,SAAS;GACT,OAAO,CAAC;GACR,QAAQ,EAAE,qBAAqB,YAAY;EAC7C,CAAC;EACD,IAAI,OAAO,YAAY,YAAY;GACjC,MAAM;GACN,SAAS;GACT,OAAO,CAAC;GACR,QAAQ,EAAE,qBAAqB,YAAY;EAC7C,CAAC;EACD,OAAO;CACT,EAAA,CAAG;CACH,OAAO;AACT;AAEA,SAAS,WAAW,MAAsB;CACxC,MAAM,OAAO,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC;CACjD,MAAM,SAAS,mBAAmB,KAAK,YAAY;CACnD,IAAI,QACF,OAAO;CAET,MAAM,YAAY,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY;CACpE,OAAO,oBAAoB,cAAc;AAC3C;AAEA,MAAM,qBAA6C;CACjD,YAAY;CACZ,UAAU;CACV,cAAc;CACd,QAAQ;AACV;AAEA,MAAM,sBAA8C;CAClD,IAAI;CACJ,KAAK;CACL,KAAK;CACL,KAAK;CACL,IAAI;CACJ,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,OAAO;CACP,IAAI;CACJ,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAM;CACN,IAAI;CACJ,MAAM;CACN,KAAK;CACL,MAAM;CACN,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACP,MAAM;CACN,IAAI;CACJ,GAAG;CACH,GAAG;CACH,KAAK;CACL,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,KAAK;CACL,KAAK;CACL,SAAS;CACT,KAAK;CACL,KAAK;CACL,GAAG;CACH,IAAI;CACJ,MAAM;CACN,OAAO;CACP,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;AACP;;;AC9MA,SAAgB,WAAW,EACzB,MACA,UACA,UACA,QACA,UACA,UACA,aACA,mBACA,aACkB;CAClB,IAAI,CAAC,MACH,OAAO;CAGT,OACE,qBAAC,OAAD;EAAK,aAAU;EAAc,WAAW,GAAG,8CAA8C,SAAS;EAAlG,UAAA;GACG,KAAK,WACJ,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,eAAD,EAAe,WAAU,iCAAkC,CAAA;KAC3D,oBAAC,QAAD;MAAM,WAAU;MAA2C,UAAA;KAAoD,CAAA;KAC/G,oBAAC,QAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAe,WAAW,KAAK,IAAI;MAAG,UAAA;KAElE,CAAA;KACR,oBAAC,QAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAe,cAAc,KAAK,IAAI;MAAG,UAAA;KAErE,CAAA;KACR,oBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,oBAAoB,KAAK,IAAI;MAAG,UAAA;KAEzE,CAAA;IACL;GACH,CAAA,IAAA,KAAK,YACP,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,eAAD,EAAe,WAAU,gCAAiC,CAAA;KAC1D,oBAAC,QAAD;MAAM,WAAU;MAA2C,UAAA,KAAK;KAAgB,CAAA;KAChF,oBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,oBAAoB,KAAK,IAAI;MAAG,UAAA;KAEzE,CAAA;IACL;GACH,CAAA,IAAA;GAEH,KAAK,WAAW,YACf,oBAAC,SAAD,EAAA,UACE,oBAAC,SAAD,EAAS,WAAU,mBAAoB,CAAA,EAChC,CAAA,IACP,KAAK,WAAW,UAClB,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,eAAD,EAAe,WAAU,qBAAsB,CAAA,GAC/C,oBAAC,KAAD;IAAG,WAAU;IAA4B,UAAA,KAAK;GAAS,CAAA,CAChD,EAAA,CAAA,IACP,KAAK,WAAW,WAClB,qBAAC,SAAD,EAAA,UAAA;IACE,oBAAC,aAAD,EAAa,WAAU,mBAAoB,CAAA;IAC3C,oBAAC,KAAD;KAAG,WAAU;KAAyB,UAAA;IAAwB,CAAA;IAC9D,oBAAC,KAAD;KAAG,WAAU;KAAuB,UAAA;IAAiD,CAAA;GAC9E,EAAA,CAAA,IAET,oBAAC,YAAD;IACE,MAAM,KAAK;IACX,OAAO,YAAY,IAAI;IACvB,UAAU,CAAC;IACX,UAAU,YAAY,YAAY,SAAS,KAAK,MAAM,OAAO,IAAI,KAAA;IACjE,QAAQ,eAAe,OAAO,KAAK,IAAI,IAAI,KAAA;GAC5C,CAAA;GAGF,KAAK,WAAW,YAAY,KAAK,UAAU,QAAQ,IAAI,KAAK,CAAC,YAC5D,oBAAC,OAAD;IAAK,WAAU;IACZ,UAAA,KAAK,SACJ,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,SAAD,EAAS,WAAU,mBAAoB,CAAA,GACvC,oBAAC,QAAD;KAAM,WAAU;KAAuB,UAAA;IAAa,CAAA,CACpD,EAAA,CAAA,IACA,CAAC,WACH,oBAAC,QAAD;KAAM,WAAU;KAAuB,UAAA;IAAoD,CAAA,IAE3F,qBAAA,YAAA,EAAA,UAAA;KACE,oBAAC,QAAD;MAAM,WAAU;MAAuB,UAAA;KAAqB,CAAA;KAC5D,oBAAC,QAAD,EAAM,WAAU,SAAU,CAAA;KAC1B,oBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,WAAW,KAAK,IAAI;MAAG,UAAA;KAEhE,CAAA;KACR,qBAAC,QAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAe,SAAS,KAAK,IAAI;MAArE,UAAA,CAAwE,QAEtE,oBAAC,QAAD;OAAM,WAAU;OAAiB,UAAA;MAAQ,CAAA,CACnC;;IACR,EAAA,CAAA;GAED,CAAA,IACH;EACD;;AAET;AAEA,SAAS,QAAQ,EAAE,YAAqC;CACtD,OAAO,oBAAC,OAAD;EAAK,WAAU;EAAsE;CAAc,CAAA;AAC5G;;;ACjHA,MAAM,UAAU;AAEhB,MAAM,UAAU;AAChB,MAAM,UAAU;AAEhB,MAAM,WAAW;AAEjB,SAAgB,UAAU,MAAc,QAAsC;CAC5E,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,CAAC,WAAW,QAAQ,SAAS,GAAG,GAClC;CAEF,MAAM,MAAM,QAAQ,KAAK,OAAO,MAAM,SAAS,QAAQ,KAAK,OAAO,IAAI;CACvE,IAAI,CAAC,KACH;CAEF,OAAO;EAAE,MAAM,IAAI;EAAK,MAAM,IAAI,KAAK,OAAO,IAAI,EAAE,IAAI,KAAA;CAAU;AACpE;AAEA,SAAS,OAAO,SAAuD;CACrE,IAAI,CAAC,SACH;CAEF,MAAM,SAAS,QAAQ,QAAQ,MAAM,MAAM;CAC3C,MAAM,OAAO,QAAQ,eAAe;CACpC,MAAM,MAAM,UAAU,MAAM,MAAM;CAClC,IAAI,CAAC,KACH;CAEF,IAAI,CAAC,UAAU,IAAI,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,SAAS,UACpD;CAEF,OAAO;AACT;AAEA,SAAgB,aAAa,EAC3B,WACA,QACA,UAAU,MACV,UAMC;CACD,gBAAgB;EACd,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,QAAQ,CAAC,SACZ;EAEF,MAAM,YAAY,YAChB,WAAW,KAAA,KAAa,SAAS,QAAQ,MAAM,MAAM,QAAQ,YAAY,KAAA;EAE3E,IAAI;EACJ,MAAM,eAAe;GACnB,QAAQ,UAAU,OAAO,OAAO;GAChC,SAAS,KAAA;EACX;EAEA,MAAM,WAAW,UAAsB;GACrC,IAAI,CAAC,MAAM,WAAW,CAAC,MAAM,SAC3B;GAEF,MAAM,SAAS,MAAM,kBAAkB,cAAc,MAAM,SAAS,KAAA;GACpE,IAAI,SAAS,MAAM,GACjB;GAEF,MAAM,MAAM,OAAO,MAAM;GACzB,IAAI,CAAC,KACH;GAEF,MAAM,eAAe;GACrB,MAAM,gBAAgB;GACtB,OAAO;GACP,OAAO,GAAG;EACZ;EAEA,MAAM,UAAU,UAAsB;GACpC,IAAI,CAAC,MAAM,WAAW,CAAC,MAAM,SAC3B,OAAO,OAAO;GAEhB,MAAM,UAAU,MAAM,kBAAkB,cAAc,MAAM,SAAS,KAAA;GACrE,IAAI,YAAY,QACd;GAEF,OAAO;GACP,IAAI,CAAC,WAAW,SAAS,OAAO,KAAK,CAAC,OAAO,OAAO,GAClD;GAEF,SAAS;GACT,QAAQ,UAAU,IAAI,OAAO;EAC/B;EAEA,MAAM,SAAS,UAAyB;GACtC,IAAI,CAAC,MAAM,WAAW,CAAC,MAAM,SAC3B,OAAO;EAEX;EAEA,KAAK,iBAAiB,SAAS,SAAS,IAAI;EAC5C,KAAK,iBAAiB,aAAa,MAAM;EACzC,SAAS,iBAAiB,WAAW,KAAK;EAC1C,SAAS,iBAAiB,SAAS,KAAK;EACxC,OAAO,iBAAiB,QAAQ,MAAM;EACtC,aAAa;GACX,OAAO;GACP,KAAK,oBAAoB,SAAS,SAAS,IAAI;GAC/C,KAAK,oBAAoB,aAAa,MAAM;GAC5C,SAAS,oBAAoB,WAAW,KAAK;GAC7C,SAAS,oBAAoB,SAAS,KAAK;GAC3C,OAAO,oBAAoB,QAAQ,MAAM;EAC3C;CACF,GAAG;EAAC;EAAW;EAAQ;EAAS;CAAM,CAAC;AACzC;AAEA,SAAgB,kBAAkB,MAAc,KAAiC;CAC/E,IAAI,KAAK,WAAW,GAAG,GACrB,OAAO;CAET,IAAI,CAAC,KACH,OAAO;CAET,OAAO,GAAG,IAAI,QAAQ,OAAO,EAAE,EAAE,GAAG,KAAK,QAAQ,SAAS,EAAE;AAC9D;;;ACtFA,MAAM,WAAW;AACjB,MAAM,WAAW;AACjB,MAAM,YAAY;AAClB,MAAM,aAAa;AAEnB,SAAgB,iBAAiB,EAC/B,QACA,WACA,QACA,mBACA,mBACA,gBACA,UACA,WACA,kBACA,cACA,cACA,QACA,WACA,kBACA,iBACA,iBACA,UACA,aACA,QACA,UACA,UACA,mBAAmB,KACnB,sBACA,cACA,aACwB;CACxB,MAAM,EAAE,SAAS,eAAe,QAAQ,SAAS;CACjD,MAAM,MAAM,MAAM;CAElB,MAAM,OAAO,gBAAgB,QAAQ,GAAG;CACxC,MAAM,SAAS,kBAAkB,QAAQ,GAAG;CAC5C,MAAM,QAAQ,aAAa,MAAM;CACjC,MAAM,EAAE,aAAa,iBAAiB,MAAM;CAE5C,gBAAgB;EACd,IAAI,CAAC,MAAM,YACT;EAEF,MAAM,QAAQ,UAA6B,MAAM,eAAe;EAChE,OAAO,iBAAiB,gBAAgB,IAAI;EAC5C,aAAa,OAAO,oBAAoB,gBAAgB,IAAI;CAC9D,GAAG,CAAC,MAAM,UAAU,CAAC;CAErB,MAAM,OAAO,UAAU;CACvB,MAAM,CAAC,eAAe,oBAAoB,SAAS,wBAAwB,KAAK;CAChF,MAAM,CAAC,WAAW,gBAAgB,SAAS,gBAAgB;CAC3D,MAAM,kBAAkB,OAAO,YAAY;CAC3C,gBAAgB,UAAU;CAC1B,gBAAgB;EACd,gBAAgB,UAAU;GAAE,OAAO;GAAW,WAAW;EAAc,CAAC;CAC1E,GAAG,CAAC,WAAW,aAAa,CAAC;CAC7B,MAAM,CAAC,cAAc,mBAAmB,SAAS,GAAG;CAEpD,MAAM,WAAW,MAAM,MAAM,SAAS;CAEtC,MAAM,cAAc,CAAC;CACrB,MAAM,WAAW,KAAK,aAAa,CAAC;CACpC,gBAAgB;EACd,IAAI,aACF,iBAAiB,IAAI;CAEzB,GAAG,CAAC,WAAW,CAAC;CAEhB,MAAM,WAAW,aACd,SAAiB;EAChB,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,EAAE,SAAS,IAAI;EACpD,IAAI,QAAQ,QAAQ,IAAI,GAAG;GACzB,MAAM,OAAO,KAAK;GAClB,IAAI,CAAC,OAAO,QAAQ,GAAG,KAAK,8CAA8C,GACxE;EAEJ;EACA,MAAM,MAAM,IAAI;CAClB,GACA,CAAC,KAAK,CACR;CAEA,MAAM,SAAS,OAAuB,IAAI;CAC1C,MAAM,eAAe,iBAAiB,MAAM;CAE5C,aAAa;EACX,WAAW;EACX,QAHe,aAAa,EAAE,WAA6B,MAAM,KAAK,kBAAkB,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,MAAM,GAAG,CAG7G;EACR,SAAS,KAAK;EACd,QAAQ;CACV,CAAC;CACD,MAAM,YAAY,KAAK,IAAI,YAAY,eAAe,SAAS;CAE/D,MAAM,CAAC,QAAQ,aAAa,SAAgC,IAAI;CAChE,MAAM,UACJ,WAAW,KAAA,KAAa,WAAW,OAC/B,KAAA,IACA,OAAO,WAAW,cACf,UAAU,aAAa,OAAO,KAAK,GAAG,MAAM,IAC7C,aAAa,QAAQ,MAAM;CAEnC,OACE,qBAAC,OAAD;EAAK,aAAU;EAAoB,WAAW,GAAG,6DAA6D,SAAS;EAAvH,UAAA,CACG,WAAW,KAAA,IAAY,oBAAC,OAAD;GAAK,KAAK;GAAW,WAAU;EAAY,CAAA,IAAI,MACvE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACG,WACC,oBAAC,UAAD;KACQ;KACE;KACR,YAAY,MAAM;KAClB,YAAY,MAAM;KAClB,kBAAkB,iBAAiB,IAAI;KACvC,OAAO,EAAE,OAAO,cAAc,KAAK,IAAI,WAAW,GAAG,IAAI,UAAU;KACnE,WAAW,GAAG,mCAAmC,eAAe,0CAA0C;IAC3G,CAAA,IACC,KAAK,YACP,oBAAC,OAAD;KAAK,WAAU;KACb,UAAA,oBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAU,cAAW;MAAqB,eAAe,iBAAiB,KAAK;MAC1G,UAAA,oBAAC,eAAD,EAAe,WAAU,mBAAoB,CAAA;KACvC,CAAA;IACL,CAAA,IACH;IACH,YAAY,CAAC,cACZ,oBAAC,UAAD;KACE,aAAY;KACZ,OAAO;KACP,eAAe;KACf,KAAK;KACL,KAAK;KACL,cAAc;KACd,cAAW;IACZ,CAAA,IACC;IAEJ,qBAAC,OAAD;KAAK,KAAK;KAAQ,WAAU;KAA5B,UAAA;MACG,WACC,qBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,QAAQ,KAAK,IAAI,cAAc,aAAa,YAAY,EAAE;OAFrE,UAAA,CAIE,oBAAC,YAAD;QAAY,OAAO,MAAM;QAAO,YAAY,MAAM;QAAY,YAAY,MAAM;QAAU,SAAS;OAAW,CAAA,GAC9G,oBAAC,YAAD;QACE,MAAM,MAAM;QACF;QACV,UAAU,MAAM;QAChB,SAAS,SAAS,KAAK,MAAM,KAAK,IAAI;QACtC,UAAU,MAAM;QAChB,UAAU,MAAM;QAChB,cAAc,SAAS,KAAK,MAAM,UAAU,IAAI;QAChD,mBAAmB,MAAM;OAC1B,CAAA,CACE;MACH,CAAA,IAAA;MACH,WACC,oBAAC,UAAD;OACE,aAAY;OACZ,OAAO,KAAK,IAAI,cAAc,aAAa,YAAY;OACvD,eAAe;OACf,KAAK;OACL,KAAK;OACL,cAAW;MACZ,CAAA,IACC;MACJ,oBAAC,cAAD;OACU;OACG;OACX,QAAQ;OACW;OACA;OACH;OACN;OACC;OACO;OACJ;OACA;OACN;OACG;OACO;OACD;OACP;OACG;OACI;OACT;OACE;OACA;OACV,WAAU;MACX,CAAA;KACE;;IAEJ,eAAe,WACd,oBAAC,UAAD;KACE,MAAK;KACL,cAAW;KACX,eAAe,iBAAiB,IAAI;KACpC,WAAU;IACX,CAAA,IACC;GACD;EACF,CAAA,CAAA;;AAET;AAEA,SAAS,iBAAiB,KAA4C;CACpE,MAAM,CAAC,QAAQ,aAAa,SAAS,CAAC;CACtC,sBAAsB;EACpB,MAAM,UAAU,IAAI;EACpB,IAAI,CAAC,SACH;EAEF,MAAM,WAAW,IAAI,gBAAgB,CAAC,WAAW;GAC/C,IAAI,OACF,UAAU,MAAM,YAAY,MAAM;EAEtC,CAAC;EACD,SAAS,QAAQ,OAAO;EACxB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,GAAG,CAAC;CACR,OAAO;AACT;AAEA,SAAS,YAAqB;CAC5B,MAAM,CAAC,MAAM,WAAW,SAAS,IAAI;CACrC,gBAAgB;EACd,MAAM,QAAQ,OAAO,WAAW,oBAAoB;EACpD,MAAM,aAAa,QAAQ,MAAM,OAAO;EACxC,KAAK;EACL,MAAM,iBAAiB,UAAU,IAAI;EACrC,aAAa,MAAM,oBAAoB,UAAU,IAAI;CACvD,GAAG,CAAC,CAAC;CACL,OAAO;AACT"}
|
|
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'\nimport { parseFileLink } from '../../lib/file-link.ts'\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 return parseFileLink(path, cwd)?.path ?? path\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 onOpenFile?: SessionPanelProps['onOpenFile']\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 onOpenFile,\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 onOpenFile={onOpenFile ?? files.open}\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": "2.
|
|
3
|
+
"version": "2.9.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.7.0",
|
|
41
41
|
"use-stick-to-bottom": "^1.1.6",
|
|
42
|
-
"@workerdeck/client": "2.
|
|
43
|
-
"@workerdeck/protocol": "2.
|
|
44
|
-
"@workerdeck/react": "2.
|
|
42
|
+
"@workerdeck/client": "2.9.0",
|
|
43
|
+
"@workerdeck/protocol": "2.9.0",
|
|
44
|
+
"@workerdeck/react": "2.9.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"monaco-editor": "^0.56.0",
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
} from 'react'
|
|
12
12
|
import type { SkillInfo, SlashCommandInfo } from '@workerdeck/protocol'
|
|
13
13
|
import type { StagedAttachment, UseAttachmentsResult } from '@workerdeck/react'
|
|
14
|
-
import { ArrowUp, FileText, Paperclip, RotateCw, Sparkles, Square, TriangleAlert, X } from 'lucide-react'
|
|
14
|
+
import { ArrowUp, FileText, Paperclip, RotateCw, SlidersHorizontal, Sparkles, Square, TriangleAlert, X } from 'lucide-react'
|
|
15
15
|
import { Button } from '../ui/Button.tsx'
|
|
16
16
|
import { Spinner } from '../ui/Spinner.tsx'
|
|
17
17
|
import { PromptArea } from '../prompt-area/prompt-area.tsx'
|
|
18
18
|
import { usePromptAreaState } from '../prompt-area/use-prompt-area-state.ts'
|
|
19
19
|
import { plainTextToSegments } from '../prompt-area/prompt-area-engine.ts'
|
|
20
20
|
import { commandTrigger, launchTrigger, mentionTrigger } from '../prompt-area/trigger-presets.ts'
|
|
21
|
+
import { mergeComposerRows, rankComposerRows, skillPrompt, type ClientCommand } from './composer-commands.ts'
|
|
21
22
|
import { useTranscriptVariant } from './transcript-variant.tsx'
|
|
22
23
|
import type { TerminalAffordances } from '../terminal/affordances.tsx'
|
|
23
24
|
import { PROMPT_GLYPH } from '../terminal/items.tsx'
|
|
@@ -44,6 +45,9 @@ export interface ComposerProps {
|
|
|
44
45
|
placeholder?: string
|
|
45
46
|
commands?: SlashCommandInfo[]
|
|
46
47
|
skills?: SkillInfo[]
|
|
48
|
+
// Session controls the host offers as `/` rows. Merged into the same ranked list as the engine's own
|
|
49
|
+
// commands, and suppressed name-for-name by an engine command so the real one always wins.
|
|
50
|
+
clientCommands?: ClientCommand[]
|
|
47
51
|
onSearchFiles?: (query: string, options: { signal: AbortSignal }) => Promise<ComposerFileMatch[]>
|
|
48
52
|
// Shell mode: `!` as the first character turns the composer into a host shell prompt. Omit to leave the
|
|
49
53
|
// mode off entirely - the gateway only offers it to an operator on a session whose engine reaches a host cwd.
|
|
@@ -61,24 +65,6 @@ export interface ComposerProps {
|
|
|
61
65
|
ref?: Ref<ComposerHandle>
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
function cleanName(name: string) {
|
|
65
|
-
return name.replace(/\s*\(MCP\)$/i, '')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function skillPrompt(skill: SkillInfo): string {
|
|
69
|
-
const base = skill.defaultPrompt?.trim() || `$${skill.name}`
|
|
70
|
-
return /\s$/.test(base) ? base : base + ' '
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function matchScore(query: string, haystacks: string[]): number {
|
|
74
|
-
const needle = query.toLowerCase()
|
|
75
|
-
const lowered = haystacks.map((s) => s.toLowerCase())
|
|
76
|
-
if (lowered.some((h) => h.startsWith(needle))) {
|
|
77
|
-
return 2
|
|
78
|
-
}
|
|
79
|
-
return lowered.some((h) => h.includes(needle)) ? 1 : 0
|
|
80
|
-
}
|
|
81
|
-
|
|
82
68
|
export function Composer({
|
|
83
69
|
onSend,
|
|
84
70
|
onInterrupt,
|
|
@@ -87,6 +73,7 @@ export function Composer({
|
|
|
87
73
|
placeholder = 'Message the agent…',
|
|
88
74
|
commands,
|
|
89
75
|
skills,
|
|
76
|
+
clientCommands,
|
|
90
77
|
onSearchFiles,
|
|
91
78
|
onShellCommand,
|
|
92
79
|
attachments,
|
|
@@ -109,6 +96,7 @@ export function Composer({
|
|
|
109
96
|
const fileInput = useRef<HTMLInputElement>(null)
|
|
110
97
|
const [dragging, setDragging] = useState(false)
|
|
111
98
|
const [shellMode, setShellMode] = useState(false)
|
|
99
|
+
const [helpOpen, setHelpOpen] = useState(false)
|
|
112
100
|
|
|
113
101
|
useImperativeHandle(
|
|
114
102
|
ref,
|
|
@@ -125,76 +113,43 @@ export function Composer({
|
|
|
125
113
|
|
|
126
114
|
const triggers = useMemo(() => {
|
|
127
115
|
const configured = []
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (seen.has(name)) {
|
|
134
|
-
return []
|
|
135
|
-
}
|
|
136
|
-
seen.add(name)
|
|
137
|
-
return [{ ...c, name }]
|
|
138
|
-
})
|
|
116
|
+
const rows = mergeComposerRows({ commands, clientCommands, skills })
|
|
117
|
+
if (rows.length > 0) {
|
|
118
|
+
// Keyed by kind as well as name: an engine command and a skill may share one, and a chip's `data`
|
|
119
|
+
// is serialized into the DOM, so the row itself never travels there.
|
|
120
|
+
const byTag = new Map(rows.map((row) => [`${row.kind}:${row.name}`, row]))
|
|
139
121
|
configured.push(
|
|
140
122
|
commandTrigger({
|
|
141
|
-
onSearch: (query: string): TriggerSuggestion[] =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
123
|
+
onSearch: (query: string): TriggerSuggestion[] =>
|
|
124
|
+
rankComposerRows(query, rows).map((row) =>
|
|
125
|
+
row.kind === 'skill'
|
|
126
|
+
? {
|
|
127
|
+
value: row.name,
|
|
128
|
+
label: row.label,
|
|
129
|
+
description: row.description ? `Skill · ${row.description}` : 'Skill · inserts a message you can edit',
|
|
130
|
+
icon: <Sparkles className="size-3.5 text-fg-3" />,
|
|
131
|
+
data: `${row.kind}:${row.name}`,
|
|
132
|
+
}
|
|
133
|
+
: {
|
|
134
|
+
value: row.name,
|
|
135
|
+
label: `/${row.name}${row.argumentHint ? ` ${row.argumentHint}` : ''}`,
|
|
136
|
+
description: row.description,
|
|
137
|
+
icon: row.kind === 'client' ? <SlidersHorizontal className="size-3.5 text-fg-3" /> : undefined,
|
|
138
|
+
data: `${row.kind}:${row.name}`,
|
|
139
|
+
},
|
|
140
|
+
),
|
|
141
|
+
insertAsText: (suggestion) => {
|
|
142
|
+
const row = byTag.get(String(suggestion.data))
|
|
143
|
+
if (row?.kind === 'skill') {
|
|
144
|
+
return skillPrompt(row.skill)
|
|
156
145
|
}
|
|
157
|
-
|
|
158
|
-
return scored.map(({ suggestion }) => suggestion)
|
|
146
|
+
return row?.kind === 'client' && row.command.requiresArgs ? `/${row.name} ` : undefined
|
|
159
147
|
},
|
|
160
148
|
onSelect: (suggestion) => suggestion.value,
|
|
161
149
|
chipClassName: 'font-mono',
|
|
162
150
|
}),
|
|
163
151
|
)
|
|
164
152
|
}
|
|
165
|
-
if (usableSkills.length > 0) {
|
|
166
|
-
configured.push(
|
|
167
|
-
commandTrigger({
|
|
168
|
-
char: '$',
|
|
169
|
-
accessibilityLabel: 'skill',
|
|
170
|
-
onSearch: (query: string): TriggerSuggestion[] => {
|
|
171
|
-
const scored: Array<{ score: number; suggestion: TriggerSuggestion }> = []
|
|
172
|
-
for (const skill of usableSkills) {
|
|
173
|
-
const score = matchScore(query, [skill.name, ...skill.name.split(/[-:_]/)])
|
|
174
|
-
if (score === 0) {
|
|
175
|
-
continue
|
|
176
|
-
}
|
|
177
|
-
const summary = skill.shortDescription ?? skill.description
|
|
178
|
-
scored.push({
|
|
179
|
-
score,
|
|
180
|
-
suggestion: {
|
|
181
|
-
value: skill.name,
|
|
182
|
-
label: skill.displayName ?? skill.name,
|
|
183
|
-
description: summary ? `Skill · ${summary}` : 'Skill · inserts a message you can edit',
|
|
184
|
-
icon: <Sparkles className="size-3.5 text-fg-3" />,
|
|
185
|
-
},
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
scored.sort((a, b) => b.score - a.score)
|
|
189
|
-
return scored.map(({ suggestion }) => suggestion)
|
|
190
|
-
},
|
|
191
|
-
insertAsText: (suggestion) => {
|
|
192
|
-
const skill = usableSkills.find((s) => s.name === suggestion.value)
|
|
193
|
-
return skill ? skillPrompt(skill) : `$${suggestion.value} `
|
|
194
|
-
},
|
|
195
|
-
}),
|
|
196
|
-
)
|
|
197
|
-
}
|
|
198
153
|
if (onSearchFiles) {
|
|
199
154
|
configured.push(
|
|
200
155
|
mentionTrigger({
|
|
@@ -224,7 +179,7 @@ export function Composer({
|
|
|
224
179
|
)
|
|
225
180
|
}
|
|
226
181
|
return configured.length > 0 ? configured : undefined
|
|
227
|
-
}, [commands, skills, onSearchFiles, onShellCommand])
|
|
182
|
+
}, [commands, skills, clientCommands, onSearchFiles, onShellCommand])
|
|
228
183
|
|
|
229
184
|
const saveDraft = draft?.save
|
|
230
185
|
useEffect(() => {
|
|
@@ -261,6 +216,13 @@ export function Composer({
|
|
|
261
216
|
setShellMode(false)
|
|
262
217
|
focus()
|
|
263
218
|
}
|
|
219
|
+
|
|
220
|
+
const hints = [
|
|
221
|
+
triggers?.some((t) => t.char === '/') ? { key: '/', what: 'commands and skills' } : undefined,
|
|
222
|
+
onSearchFiles ? { key: '@', what: 'mention a file' } : undefined,
|
|
223
|
+
onShellCommand ? { key: '!', what: 'run a shell command' } : undefined,
|
|
224
|
+
{ key: '?', what: 'this list, on an empty composer' },
|
|
225
|
+
].filter((h) => h !== undefined)
|
|
264
226
|
// Escape and backspace-on-empty both leave, because both are what a person reaches for when the
|
|
265
227
|
// pink frame was not what they meant. Backspace only when there is nothing left to delete.
|
|
266
228
|
const shellKeys = shellMode
|
|
@@ -273,7 +235,40 @@ export function Composer({
|
|
|
273
235
|
}
|
|
274
236
|
},
|
|
275
237
|
}
|
|
276
|
-
: {
|
|
238
|
+
: {
|
|
239
|
+
onEscape: helpOpen ? () => setHelpOpen(false) : undefined,
|
|
240
|
+
// Only on a genuinely empty composer, so a message that opens with a question mark still types.
|
|
241
|
+
onKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {
|
|
242
|
+
if (e.key === '?' && isEmpty && !e.metaKey && !e.ctrlKey) {
|
|
243
|
+
e.preventDefault()
|
|
244
|
+
setHelpOpen(true)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
if (helpOpen) {
|
|
248
|
+
setHelpOpen(false)
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const helpHints = helpOpen ? (
|
|
254
|
+
<>
|
|
255
|
+
{hints.map((hint) => (
|
|
256
|
+
<span key={hint.key} className="flex items-center gap-1">
|
|
257
|
+
<kbd className="font-mono text-text">{hint.key}</kbd>
|
|
258
|
+
{hint.what}
|
|
259
|
+
</span>
|
|
260
|
+
))}
|
|
261
|
+
</>
|
|
262
|
+
) : null
|
|
263
|
+
const helpRow = helpOpen ? (
|
|
264
|
+
<div
|
|
265
|
+
role="note"
|
|
266
|
+
aria-label="Composer shortcuts"
|
|
267
|
+
className="mx-auto mb-1 flex w-full max-w-[var(--wd-transcript-max-width)] flex-wrap items-center gap-x-3 gap-y-1 text-label text-fg-3"
|
|
268
|
+
>
|
|
269
|
+
{helpHints}
|
|
270
|
+
</div>
|
|
271
|
+
) : null
|
|
277
272
|
|
|
278
273
|
const pick = (files: FileList | null) => {
|
|
279
274
|
if (files && files.length > 0) {
|
|
@@ -405,6 +400,12 @@ export function Composer({
|
|
|
405
400
|
{submitButton}
|
|
406
401
|
</div>
|
|
407
402
|
</div>
|
|
403
|
+
{helpOpen ? (
|
|
404
|
+
<div role="note" aria-label="Composer shortcuts" className="term-row">
|
|
405
|
+
<span aria-hidden className="term-gutter" />
|
|
406
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-[2ch] text-fg-3">{helpHints}</div>
|
|
407
|
+
</div>
|
|
408
|
+
) : null}
|
|
408
409
|
{shellMode ? (
|
|
409
410
|
<div className="term-row">
|
|
410
411
|
<span aria-hidden className="term-gutter" />
|
|
@@ -425,6 +426,7 @@ export function Composer({
|
|
|
425
426
|
|
|
426
427
|
return (
|
|
427
428
|
<div data-slot="composer" className={cn('px-[var(--wd-composer-padding)] pb-[var(--wd-composer-padding)]', className)}>
|
|
429
|
+
{helpRow}
|
|
428
430
|
<div
|
|
429
431
|
{...dropHandlers}
|
|
430
432
|
className={cn(
|
|
@@ -162,7 +162,9 @@ export function SessionItem({
|
|
|
162
162
|
className={cn(
|
|
163
163
|
'flex h-4 min-w-6 shrink-0 items-center justify-center rounded-full px-2',
|
|
164
164
|
'text-[0.75rem] leading-none tracking-[-0.005em] tabular-nums',
|
|
165
|
-
|
|
165
|
+
// Grey while the turn is still producing, accent once it has stopped: the badge's colour
|
|
166
|
+
// answers "is this waiting for me", and a working session is not yet.
|
|
167
|
+
row.state === 'working' ? 'bg-badge text-badge-fg' : 'bg-accent text-accent-fg',
|
|
166
168
|
)}
|
|
167
169
|
>
|
|
168
170
|
{row.unseen}
|