@workerdeck/ui 0.7.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -3
- package/build/SessionPanel-CyhygZx_.d.mts +277 -0
- package/build/SessionPanel-_U8tjX29.mjs +8409 -0
- package/build/SessionPanel-_U8tjX29.mjs.map +1 -0
- package/build/format-DqR56Y8l.mjs +162 -0
- package/build/format-DqR56Y8l.mjs.map +1 -0
- package/build/format-ljc3lKpA.d.mts +59 -0
- package/build/format.d.mts +2 -0
- package/build/format.mjs +2 -0
- package/build/index.d.mts +615 -87
- package/build/index.mjs +6 -5081
- package/build/index.mjs.map +1 -1
- package/build/workspace.d.mts +199 -0
- package/build/workspace.mjs +849 -0
- package/build/workspace.mjs.map +1 -0
- package/package.json +22 -4
- package/src/components/agent/CodeEditor.tsx +300 -0
- package/src/components/agent/Composer.tsx +522 -87
- package/src/components/agent/ContextDialog.tsx +99 -0
- package/src/components/agent/Conversation.tsx +11 -3
- package/src/components/agent/EditorTabs.tsx +165 -0
- package/src/components/agent/FileCard.tsx +26 -0
- package/src/components/agent/FileTree.tsx +287 -0
- package/src/components/agent/FileViewer.tsx +148 -0
- package/src/components/agent/HostFilesDialog.tsx +218 -0
- package/src/components/agent/Loader.tsx +120 -14
- package/src/components/agent/McpDialog.tsx +363 -0
- package/src/components/agent/Message.tsx +51 -17
- package/src/components/agent/ModelSelect.tsx +34 -6
- package/src/components/agent/PermissionModeSelect.tsx +133 -22
- package/src/components/agent/PermissionPrompt.tsx +164 -6
- package/src/components/agent/PromptTokenText.tsx +39 -0
- package/src/components/agent/QuestionPrompt.tsx +122 -0
- package/src/components/agent/Reasoning.tsx +20 -5
- package/src/components/agent/Response.tsx +128 -0
- package/src/components/agent/SessionEmptyState.tsx +65 -0
- package/src/components/agent/SessionInfoDialog.tsx +163 -0
- package/src/components/agent/SessionPanel.tsx +756 -90
- package/src/components/agent/SessionWorkspace.tsx +282 -0
- package/src/components/agent/SkillsDialog.tsx +195 -0
- package/src/components/agent/StatusBar.tsx +85 -18
- package/src/components/agent/ToolCallCard.tsx +243 -30
- package/src/components/agent/Transcript.tsx +540 -27
- package/src/components/agent/UsageDialog.tsx +168 -0
- package/src/components/agent/line-prompt.tsx +249 -0
- package/src/components/agent/transcript-variant.tsx +61 -0
- package/src/components/prompt-area/prompt-area-engine.ts +53 -0
- package/src/components/prompt-area/types.ts +15 -0
- package/src/components/prompt-area/use-prompt-area.ts +20 -0
- package/src/components/ui/CodeBlock.tsx +40 -2
- package/src/components/ui/CopyButton.tsx +28 -3
- package/src/components/ui/Dialog.tsx +92 -0
- package/src/components/ui/Menu.tsx +55 -0
- package/src/components/ui/Splitter.tsx +133 -0
- package/src/components/ui/Tooltip.tsx +22 -5
- package/src/format.ts +10 -0
- package/src/index.ts +63 -2
- package/src/lib/clipboard.ts +56 -0
- package/src/lib/format.ts +114 -0
- package/src/lib/tool-icon.ts +96 -0
- package/src/workspace.ts +28 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionPanel-_U8tjX29.mjs","names":["Select","SelectPrimitive","Menu","MenuPrimitive","Dialog","DialogPrimitive","TooltipPrimitive","rangeStart","File","FileText","FileCard","cssColor","usageTint","Dialog","Dialog","Dialog","Dialog","Select","Select","Dialog","Dialog","Menu"],"sources":["../src/lib/utils.ts","../src/components/ui/Button.tsx","../src/components/ui/Badge.tsx","../src/components/ui/Input.tsx","../src/components/ui/Select.tsx","../src/components/ui/Menu.tsx","../src/components/ui/Dialog.tsx","../src/components/ui/Tooltip.tsx","../src/lib/clipboard.ts","../src/components/ui/CopyButton.tsx","../src/components/ui/Spinner.tsx","../src/components/ui/CodeBlock.tsx","../src/components/ui/Splitter.tsx","../src/components/ui/ProgressRing.tsx","../src/components/prompt-area/prompt-area-engine.ts","../src/components/prompt-area/prompt-area-list-ops.ts","../src/components/prompt-area/dom-helpers.ts","../src/components/prompt-area/cursor-helpers.ts","../src/components/prompt-area/clipboard-helpers.ts","../src/components/prompt-area/html-to-markdown.ts","../src/components/prompt-area/use-prompt-area-events.ts","../src/components/prompt-area/use-trigger-search.ts","../src/components/prompt-area/use-prompt-area.ts","../src/components/prompt-area/trigger-popover.tsx","../src/components/prompt-area/animated-placeholder.tsx","../src/components/prompt-area/remove-button.tsx","../src/components/prompt-area/image-strip.tsx","../src/components/prompt-area/file-strip.tsx","../src/components/prompt-area/prompt-area.tsx","../src/components/prompt-area/use-prompt-area-state.ts","../src/components/prompt-area/trigger-presets.ts","../src/components/agent/Composer.tsx","../src/components/agent/ContextDialog.tsx","../src/components/agent/HostFilesDialog.tsx","../src/components/agent/McpDialog.tsx","../src/components/agent/SkillsDialog.tsx","../src/components/agent/ModelSelect.tsx","../src/components/agent/PermissionModeSelect.tsx","../src/lib/tool-icon.ts","../src/components/agent/transcript-variant.tsx","../src/components/agent/Response.tsx","../src/components/agent/line-prompt.tsx","../src/components/agent/PermissionPrompt.tsx","../src/components/agent/QuestionPrompt.tsx","../src/components/agent/SessionInfoDialog.tsx","../src/components/agent/status.ts","../src/components/agent/StatusBar.tsx","../src/components/agent/Conversation.tsx","../src/components/agent/FileCard.tsx","../src/components/agent/Loader.tsx","../src/components/agent/Message.tsx","../src/components/agent/PromptTokenText.tsx","../src/components/agent/Reasoning.tsx","../src/components/agent/SessionEmptyState.tsx","../src/components/agent/ToolCallCard.tsx","../src/components/agent/Transcript.tsx","../src/components/agent/UsageDialog.tsx","../src/components/agent/SessionPanel.tsx"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n// Register the custom text-* font-size utilities as a font-size class group, or\n// tailwind-merge collapses a size class and a color class (e.g. `text-label\n// text-muted-foreground`) into one bucket and silently drops the size.\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-xl',\n 'display-lg',\n 'display-md',\n 'display-sm',\n 'heading-1',\n 'heading-2',\n 'heading-3',\n 'body',\n 'body-sm',\n 'label',\n 'code',\n ],\n },\n ],\n },\n },\n})\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","import { type ButtonHTMLAttributes, forwardRef } from 'react'\nimport { type VariantProps, cva } from 'class-variance-authority'\nimport { cn } from '../../lib/utils.ts'\n\nconst buttonVariants = cva(\n \"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-1 focus-visible:ring-offset-bg disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-accent-hover',\n outline: 'border-border bg-surface hover:border-border-strong hover:bg-surface-hover',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-surface-hover',\n ghost: 'text-fg-2 hover:bg-surface-hover hover:text-foreground',\n destructive: 'text-danger hover:bg-danger-bg',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n 'default': 'h-8 gap-1.5 px-3',\n 'xs': \"h-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3\",\n 'sm': 'h-7 gap-1 px-2.5 text-xs',\n 'lg': 'h-9 gap-1.5 px-3.5',\n 'icon': 'size-8',\n 'icon-sm': 'size-7',\n },\n },\n defaultVariants: { variant: 'default', size: 'default' },\n },\n)\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant = 'default', size = 'default', type = 'button', ...props }, ref) => (\n <button\n ref={ref}\n type={type}\n data-slot='button'\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n ),\n)\nButton.displayName = 'Button'\n\nexport { buttonVariants }\n","import { type HTMLAttributes } from 'react'\nimport { type VariantProps, cva } from 'class-variance-authority'\nimport { cn } from '../../lib/utils.ts'\n\nconst badgeVariants = cva(\n 'inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-label font-medium whitespace-nowrap',\n {\n variants: {\n variant: {\n neutral: 'border-border bg-surface text-fg-2',\n accent: 'border-transparent bg-accent-bg text-fg-1',\n success: 'border-transparent bg-success-bg text-success',\n warning: 'border-transparent bg-warning-bg text-warning',\n danger: 'border-transparent bg-danger-bg text-danger',\n info: 'border-transparent bg-info-bg text-info',\n },\n mono: {\n true: 'font-mono text-code',\n false: '',\n },\n },\n defaultVariants: { variant: 'neutral', mono: false },\n },\n)\n\nexport interface BadgeProps\n extends HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof badgeVariants> {\n /** Render a leading status dot in the variant's color. */\n dot?: boolean\n}\n\nexport function Badge({ className, variant = 'neutral', mono, dot, children, ...props }: BadgeProps) {\n return (\n <span data-slot='badge' className={cn(badgeVariants({ variant, mono, className }))} {...props}>\n {dot ? <span aria-hidden className='size-1.5 rounded-full bg-current' /> : null}\n {children}\n </span>\n )\n}\n\nexport { badgeVariants }\n","import { type InputHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement>>(\n ({ className, ...props }, ref) => (\n <input\n ref={ref}\n data-slot='input'\n className={cn(\n 'h-8 w-full rounded-md border border-border bg-bg px-2.5 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n ),\n)\nInput.displayName = 'Input'\n","import { type FunctionComponent } from 'react'\nimport { Select as SelectPrimitive } from '@base-ui/react/select'\nimport { Check, ChevronsUpDown } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Select = SelectPrimitive.Root\nexport const SelectValue = SelectPrimitive.Value\nexport const SelectItemText = SelectPrimitive.ItemText\n\nexport const SelectTrigger: FunctionComponent<SelectPrimitive.Trigger.Props> = ({\n className,\n children,\n ...props\n}) => (\n <SelectPrimitive.Trigger\n data-slot='select-trigger'\n className={cn(\n 'inline-flex h-7 items-center justify-between gap-1.5 rounded-md border border-border bg-bg px-2 text-body-sm text-text',\n 'transition-colors outline-none hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'data-popup-open:border-border-strong disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}>\n {children}\n <SelectPrimitive.Icon className='text-fg-4'>\n <ChevronsUpDown className='size-3.5' />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n)\n\nexport const SelectContent: FunctionComponent<\n SelectPrimitive.Popup.Props &\n Pick<SelectPrimitive.Positioner.Props, 'align' | 'alignItemWithTrigger' | 'side' | 'sideOffset'>\n> = ({\n className,\n align = 'start',\n alignItemWithTrigger = false,\n side = 'bottom',\n sideOffset = 6,\n ...props\n}) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Positioner\n align={align}\n alignItemWithTrigger={alignItemWithTrigger}\n side={side}\n sideOffset={sideOffset}\n className='isolate z-60 outline-none'>\n <SelectPrimitive.Popup\n data-slot='select-content'\n className={cn(\n 'max-h-[min(24rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto',\n 'rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',\n className,\n )}\n {...props}\n />\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n)\n\nexport const SelectItem: FunctionComponent<SelectPrimitive.Item.Props> = ({\n className,\n children,\n ...props\n}) => (\n <SelectPrimitive.Item\n data-slot='select-item'\n className={cn(\n 'flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 text-body-sm text-text outline-none select-none',\n 'data-highlighted:bg-surface-hover',\n className,\n )}\n {...props}>\n <span className='flex min-w-0 flex-1 flex-col gap-0.5'>{children}</span>\n <SelectPrimitive.ItemIndicator className='mt-0.5 text-fg-1'>\n <Check className='size-3.5' />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n)\n","import { type FunctionComponent } from 'react'\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Menu = MenuPrimitive.Root\nexport const MenuTrigger = MenuPrimitive.Trigger\n\nexport const MenuContent: FunctionComponent<\n MenuPrimitive.Popup.Props &\n Pick<MenuPrimitive.Positioner.Props, 'align' | 'side' | 'sideOffset'>\n> = ({ className, align = 'end', side = 'bottom', sideOffset = 6, ...props }) => (\n <MenuPrimitive.Portal>\n <MenuPrimitive.Positioner\n align={align}\n side={side}\n sideOffset={sideOffset}\n className='isolate z-60 outline-none'>\n <MenuPrimitive.Popup\n data-slot='menu-content'\n className={cn(\n 'min-w-48 rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n)\n\nexport const MenuItem: FunctionComponent<MenuPrimitive.Item.Props & { destructive?: boolean }> = ({\n className,\n destructive,\n ...props\n}) => (\n <MenuPrimitive.Item\n data-slot='menu-item'\n className={cn(\n 'flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-body-sm outline-none select-none',\n 'data-highlighted:bg-surface-hover',\n destructive ? 'text-danger' : 'text-text',\n className,\n )}\n {...props}\n />\n)\n\nexport const MenuSeparator: FunctionComponent<MenuPrimitive.Separator.Props> = ({\n className,\n ...props\n}) => (\n <MenuPrimitive.Separator className={cn('my-1 h-px bg-border', className)} {...props} />\n)\n","import { type FunctionComponent } from 'react'\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog'\nimport { X } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Dialog = DialogPrimitive.Root\nexport const DialogTrigger = DialogPrimitive.Trigger\nexport const DialogClose = DialogPrimitive.Close\n\n/**\n * A dismissible panel, sized for reading rather than confirming — the web\n * counterpart of the iOS app's detail sheets (context, usage, session info, MCP).\n *\n * Taller than {@link AlertDialogContent} and scrollable inside, because these\n * carry lists whose length is the engine's business, not the layout's.\n */\nexport const DialogContent: FunctionComponent<\n DialogPrimitive.Popup.Props & { size?: 'sm' | 'md' | 'lg' }\n> = ({ className, children, size = 'md', ...props }) => (\n <DialogPrimitive.Portal>\n <DialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <DialogPrimitive.Popup\n data-slot='dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 flex max-h-[min(42rem,calc(100dvh-3rem))] -translate-x-1/2 -translate-y-1/2 flex-col',\n size === 'sm' && 'w-[min(24rem,calc(100vw-2rem))]',\n size === 'md' && 'w-[min(32rem,calc(100vw-2rem))]',\n size === 'lg' && 'w-[min(46rem,calc(100vw-2rem))]',\n 'rounded-lg border border-border bg-surface shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n)\n\n/** Title row with the close button, pinned above the scrolling body. */\nexport const DialogHeader: FunctionComponent<{\n title: string\n description?: string\n /** Rendered between the title and the close button. */\n actions?: React.ReactNode\n}> = ({ title, description, actions }) => (\n <div className='flex items-start gap-2 border-b border-border px-4 py-3'>\n <div className='min-w-0 flex-1'>\n <DialogPrimitive.Title className='truncate text-body-sm font-semibold text-text'>\n {title}\n </DialogPrimitive.Title>\n {description ? (\n <DialogPrimitive.Description className='mt-0.5 text-label text-fg-4'>\n {description}\n </DialogPrimitive.Description>\n ) : null}\n </div>\n {actions}\n <DialogPrimitive.Close\n aria-label='Close'\n className='-mr-1 flex size-6 shrink-0 items-center justify-center rounded-md text-fg-3 transition-colors outline-none hover:bg-surface-hover hover:text-fg-1'>\n <X className='size-3.5' />\n </DialogPrimitive.Close>\n </div>\n)\n\n/** The scrolling region under the header. */\nexport const DialogBody: FunctionComponent<React.HTMLAttributes<HTMLDivElement>> = ({\n className,\n ...props\n}) => <div className={cn('min-h-0 flex-1 overflow-y-auto p-4', className)} {...props} />\n\n/** A label/value row — the shape every one of these panels is mostly made of. */\nexport const DialogRow: FunctionComponent<{\n label: string\n children: React.ReactNode\n mono?: boolean\n}> = ({ label, children, mono }) => (\n <div className='flex items-baseline justify-between gap-4 py-1.5'>\n <span className='shrink-0 text-label text-fg-3'>{label}</span>\n <span className={cn('min-w-0 truncate text-right text-body-sm text-fg-1', mono && 'font-mono text-label')}>\n {children}\n </span>\n </div>\n)\n","import { type FunctionComponent, type ReactElement, type ReactNode } from 'react'\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip'\nimport { cn } from '../../lib/utils.ts'\n\nexport const TooltipProvider = TooltipPrimitive.Provider\n\nexport const TooltipContent: FunctionComponent<\n TooltipPrimitive.Popup.Props & Pick<TooltipPrimitive.Positioner.Props, 'side' | 'sideOffset'>\n> = ({ className, side = 'top', sideOffset = 6, ...props }) => (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Positioner side={side} sideOffset={sideOffset} className='isolate z-60'>\n <TooltipPrimitive.Popup\n data-slot='tooltip-content'\n className={cn(\n 'rounded-md border border-border bg-surface px-2 py-1 text-label text-fg-2 shadow-(--shadow-md) outline-none',\n className,\n )}\n {...props}\n />\n </TooltipPrimitive.Positioner>\n </TooltipPrimitive.Portal>\n)\n\n/**\n * Convenience wrapper: `<Tip content=\"...\"><Button/></Tip>`.\n *\n * Pass `render` when the trigger must *be* an element you already have — a tab, a\n * row — rather than something wrapped in a span. The default span is fine beside\n * a button but would break any layout that styles its own children (a flex tab\n * strip gets an extra box between the container and its items).\n */\nexport function Tip({\n content,\n render,\n side,\n children,\n}: {\n content: ReactNode\n render?: ReactElement\n side?: 'top' | 'right' | 'bottom' | 'left'\n children?: ReactNode\n}) {\n return (\n <TooltipPrimitive.Root>\n <TooltipPrimitive.Trigger render={render ?? <span className='inline-flex' />}>\n {children}\n </TooltipPrimitive.Trigger>\n <TooltipContent side={side}>{content}</TooltipContent>\n </TooltipPrimitive.Root>\n )\n}\n","/**\n * Copy text to the clipboard, on origins where the modern API does not exist.\n *\n * `navigator.clipboard` is gated on a **secure context**: HTTPS, or localhost.\n * A WorkerDeck dashboard reached the way it is meant to be reached — plain HTTP\n * on a LAN address, from a laptop or a phone — is neither, so `navigator.clipboard`\n * is `undefined` there and touching `.writeText` throws outright. That is the\n * normal deployment, not an edge case, which is why this falls back rather than\n * feature-detecting into a disabled button.\n *\n * The fallback is `document.execCommand('copy')` over an off-screen textarea.\n * It is deprecated and it is also the only thing that works here; every browser\n * still implements it. Returns whether the text actually landed, so a caller can\n * avoid claiming success it did not have.\n */\nexport async function copyText(value: string): Promise<boolean> {\n // Optional-chained, not `in`-checked: on an insecure origin the property is\n // absent entirely, and some embedded webviews expose a `clipboard` object\n // whose `writeText` rejects. Both end up in the fallback.\n try {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(value)\n return true\n }\n } catch {\n // Permission denied, or a webview that lied about having the API.\n }\n return legacyCopy(value)\n}\n\nfunction legacyCopy(value: string): boolean {\n if (typeof document === 'undefined') return false\n const textarea = document.createElement('textarea')\n textarea.value = value\n // Off-screen rather than hidden: `display: none` and `visibility: hidden`\n // elements cannot hold a selection, so the copy would silently do nothing.\n // `readOnly` keeps the mobile keyboard from appearing for the instant it exists.\n textarea.setAttribute('readonly', '')\n textarea.style.position = 'fixed'\n textarea.style.top = '-9999px'\n textarea.style.opacity = '0'\n document.body.appendChild(textarea)\n // Preserve where the user was: selecting steals the current selection, and on\n // a text field mid-edit that is visible and annoying.\n const previous = document.activeElement\n try {\n textarea.select()\n textarea.setSelectionRange(0, value.length)\n return document.execCommand('copy')\n } catch {\n return false\n } finally {\n document.body.removeChild(textarea)\n if (previous instanceof HTMLElement) previous.focus()\n }\n}\n","import { useState } from 'react'\nimport { Check, Copy } from 'lucide-react'\nimport { Button, type ButtonProps } from './Button.tsx'\nimport { copyText } from '../../lib/clipboard.ts'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface CopyButtonProps extends Omit<ButtonProps, 'onClick' | 'children'> {\n value: string\n /** Draw the state as characters (`⧉` / `✓`) rather than line-art icons — for\n * terminal-styled surfaces, where an SVG reads as another app's button. */\n glyph?: boolean\n}\n\nexport function CopyButton({\n value,\n glyph,\n className,\n variant = 'ghost',\n size = 'icon-sm',\n ...props\n}: CopyButtonProps) {\n const [copied, setCopied] = useState(false)\n return (\n <Button\n variant={variant}\n size={size}\n aria-label='Copy'\n className={cn('text-fg-3', className)}\n onClick={() => {\n // Through `copyText`, which falls back for insecure origins — the\n // dashboard on a LAN address has no `navigator.clipboard` at all, and\n // reaching straight for `.writeText` there throws.\n void copyText(value).then((ok) => {\n // Only tick when it really copied: a check mark over an empty\n // clipboard is worse than no feedback.\n if (!ok) return\n setCopied(true)\n setTimeout(() => setCopied(false), 1500)\n })\n }}\n {...props}>\n {glyph ? (\n <span className={cn('font-mono text-body-sm leading-5', copied && 'text-success')}>\n {copied ? '✓' : '⧉'}\n </span>\n ) : copied ? (\n <Check className='size-3.5 text-success' />\n ) : (\n <Copy className='size-3.5' />\n )}\n </Button>\n )\n}\n","import { LoaderCircle } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Spinner({ className }: { className?: string }) {\n return <LoaderCircle aria-label='Loading' className={cn('size-4 animate-spin text-fg-3', className)} />\n}\n","import type { ReactNode } from 'react'\nimport { CopyButton } from './CopyButton.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface CodeBlockProps {\n code: string\n /** Header label, e.g. a language or \"Parameters\". */\n label?: ReactNode\n copyable?: boolean\n /**\n * `'panel'` (default) is the framed card: border, header strip, rounded.\n * `'plain'` is the terminal treatment — the label is a dim line, the code a\n * flat band, and copy is a character. For surfaces where a box around every\n * payload is more chrome than content (the transcript's `lines` variant).\n *\n * A prop rather than a read of the transcript's variant context: this is a\n * `ui/` primitive and has no business knowing what a transcript is. The agent\n * component that renders it does.\n */\n variant?: 'panel' | 'plain'\n className?: string\n}\n\n/** Plain (unhighlighted) code panel for structured data like tool inputs. Markdown code\n * inside assistant responses is highlighted by <Response> instead. */\nexport function CodeBlock({\n code,\n label,\n copyable = true,\n variant = 'panel',\n className,\n}: CodeBlockProps) {\n const plain = variant === 'plain'\n const header = label !== undefined || copyable\n\n if (plain) {\n return (\n <div data-slot='code-block' data-variant='plain' className={cn('min-w-0', className)}>\n {header ? (\n <div className='flex items-baseline justify-between gap-2'>\n <span className='truncate text-label leading-5 text-fg-4'>{label}</span>\n {copyable ? (\n <CopyButton glyph value={code} className='size-5 shrink-0 rounded-sm' />\n ) : null}\n </div>\n ) : null}\n <pre className='max-h-64 overflow-auto bg-code-bg px-2 font-mono text-label leading-5 whitespace-pre-wrap text-fg-2'>\n {code}\n </pre>\n </div>\n )\n }\n\n return (\n <div\n data-slot='code-block'\n className={cn('overflow-hidden rounded-md border border-border bg-code-bg', className)}>\n {header ? (\n <div className='flex h-8 items-center justify-between border-b border-border px-2.5'>\n <span className='font-mono text-label text-fg-3'>{label}</span>\n {copyable ? <CopyButton value={code} /> : null}\n </div>\n ) : null}\n <pre className='max-h-64 overflow-auto px-3 py-2 font-mono text-label whitespace-pre-wrap text-fg-2'>\n {code}\n </pre>\n </div>\n )\n}\n","import {\n useCallback,\n useRef,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n} from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface SplitterProps {\n /**\n * ARIA's sense of the word: a `vertical` splitter is a vertical bar between\n * two side-by-side panes, and it resizes a **width**. A `horizontal` one sits\n * between stacked panes and resizes a **height**.\n */\n orientation: 'vertical' | 'horizontal'\n /** Current size of the pane this splitter controls, in pixels. */\n value: number\n onValueChange: (value: number) => void\n min: number\n max: number\n /** Keyboard step. */\n step?: number\n /** Set when dragging the splitter *away* from the origin should shrink the\n * controlled pane — i.e. the pane is on the right or the bottom. */\n inverted?: boolean\n /** Required: \"Resize\" alone does not say which of two splitters this is. */\n 'aria-label': string\n className?: string\n}\n\n/**\n * A draggable pane divider.\n *\n * Hand-rolled rather than depended on: `@base-ui/react` ships no splitter, the\n * behaviour is a hundred lines of pointer events, and this repo's instinct at\n * this layer is to own it (the composer is vendored for the same reason).\n *\n * Pointer capture is what makes it survive a fast drag — without it the pointer\n * leaves the 5px bar within a frame and the moves go to whatever is underneath,\n * which for this layout is an iframe-free but still selection-happy code pane.\n * The drag origin is captured on pointerdown and every move is measured against\n * it, so the pane cannot drift relative to the cursor over a long drag the way\n * per-move deltas do once clamping is involved.\n *\n * Keyboard-operable and announced as a separator, because a pane you can only\n * size by dragging is a pane some people cannot size.\n */\nexport function Splitter({\n orientation,\n value,\n onValueChange,\n min,\n max,\n step = 16,\n inverted,\n 'aria-label': label,\n className,\n}: SplitterProps) {\n const drag = useRef<{ origin: number; start: number } | null>(null)\n const vertical = orientation === 'vertical'\n\n const clamp = useCallback((next: number) => Math.min(max, Math.max(min, next)), [min, max])\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n // Secondary buttons open context menus; they are not drags.\n if (event.button !== 0) return\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = { origin: vertical ? event.clientX : event.clientY, start: value }\n }\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const state = drag.current\n if (!state) return\n const delta = (vertical ? event.clientX : event.clientY) - state.origin\n onValueChange(clamp(state.start + (inverted ? -delta : delta)))\n }\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (!drag.current) return\n drag.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n }\n\n const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n // The arrows that move *along* this splitter's axis of travel; the other\n // pair is left to the page, which is what a separator should do with them.\n const grow = vertical ? 'ArrowRight' : 'ArrowDown'\n const shrink = vertical ? 'ArrowLeft' : 'ArrowUp'\n const direction = inverted ? -1 : 1\n if (event.key === grow) onValueChange(clamp(value + step * direction))\n else if (event.key === shrink) onValueChange(clamp(value - step * direction))\n else if (event.key === 'Home') onValueChange(min)\n else if (event.key === 'End') onValueChange(max)\n else return\n event.preventDefault()\n }\n\n return (\n <div\n data-slot='splitter'\n role='separator'\n tabIndex={0}\n aria-label={label}\n aria-orientation={orientation}\n aria-valuenow={Math.round(value)}\n aria-valuemin={min}\n aria-valuemax={max}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n className={cn(\n // A 1px line that reads as a border, with a larger invisible grab area\n // around it — a hairline is an honest divider and a cruel target.\n 'group relative shrink-0 touch-none bg-border transition-colors',\n 'hover:bg-border-strong focus-visible:bg-accent focus-visible:outline-none',\n vertical ? 'w-px cursor-col-resize' : 'h-px cursor-row-resize',\n className,\n )}>\n <span\n aria-hidden\n className={cn(\n 'absolute',\n vertical ? '-inset-x-[3px] inset-y-0' : '-inset-y-[3px] inset-x-0',\n )}\n />\n </div>\n )\n}\n","import { cn } from '../../lib/utils.ts'\n\nexport interface ProgressRingProps {\n /** Filled share, 0–100 (clamped). */\n value: number\n /** Outer diameter in px. */\n size?: number\n strokeWidth?: number\n className?: string\n}\n\n/** Tiny SVG progress circle; stroke color comes from `currentColor` so callers set it\n * via text color classes (e.g. warning/danger past thresholds). */\nexport function ProgressRing({ value, size = 13, strokeWidth = 2, className }: ProgressRingProps) {\n const clamped = Math.min(100, Math.max(0, value))\n const radius = (size - strokeWidth) / 2\n const circumference = 2 * Math.PI * radius\n return (\n <svg\n data-slot='progress-ring'\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n role='img'\n aria-label={`${Math.round(clamped)}%`}\n className={cn('shrink-0 -rotate-90', className)}>\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill='none'\n stroke='currentColor'\n strokeOpacity={0.2}\n strokeWidth={strokeWidth}\n />\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill='none'\n stroke='currentColor'\n strokeWidth={strokeWidth}\n strokeLinecap='round'\n strokeDasharray={circumference}\n strokeDashoffset={circumference * (1 - clamped / 100)}\n />\n </svg>\n )\n}\n","/**\n * Pure logic engine for the PromptArea component.\n * No DOM dependencies - fully testable in Node.\n */\nimport type { Segment, ChipSegment, TriggerConfig, TriggerPosition, ActiveTrigger } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Serialization\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an array of segments to a plain text string.\n * Chips are represented as `{trigger}{displayText}` (e.g., \"@Alice\").\n */\nexport function segmentsToPlainText(segments: Segment[]): string {\n return segments\n .map((seg) => {\n if (seg.type === 'text') return seg.text\n return `${seg.trigger}${seg.displayText}`\n })\n .join('')\n}\n\n/**\n * Converts plain text into a single text segment.\n * Used for initial value conversion from plain strings.\n */\nexport function plainTextToSegments(text: string): Segment[] {\n if (!text) return []\n return [{ type: 'text', text }]\n}\n\n/**\n * Truncates segments so their combined plain-text length is at most `maxLength`.\n * Whole segments are kept while they fit; a text segment that crosses the limit\n * is sliced to fit, and a chip that would cross the limit is dropped (a chip\n * can't be partially represented).\n */\nexport function truncateSegmentsToLength(segments: Segment[], maxLength: number): Segment[] {\n if (maxLength <= 0) return []\n const result: Segment[] = []\n let length = 0\n for (const seg of segments) {\n const segLength =\n seg.type === 'text' ? seg.text.length : seg.trigger.length + seg.displayText.length\n if (length + segLength <= maxLength) {\n result.push(seg)\n length += segLength\n continue\n }\n if (seg.type === 'text') {\n let remaining = maxLength - length\n if (remaining > 0) {\n // Don't split a surrogate pair: if the cut lands right after a high\n // surrogate, drop the incomplete code point instead of a lone surrogate.\n const code = seg.text.charCodeAt(remaining - 1)\n if (code >= 0xd800 && code <= 0xdbff) remaining -= 1\n if (remaining > 0) result.push({ type: 'text', text: seg.text.slice(0, remaining) })\n }\n }\n break\n }\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Whitespace / word boundaries\n// ---------------------------------------------------------------------------\n\n/**\n * Single source of truth for what counts as an inline-whitespace word boundary\n * in the editor model: a space, newline, or tab.\n *\n * Trigger detection, paste auto-resolution, and position validation all rely\n * on the *same* notion of a boundary — keeping it here prevents the three\n * call sites from silently drifting apart (e.g. one handling tabs and the\n * others not).\n */\nexport function isInlineWhitespace(char: string | undefined): boolean {\n return char === ' ' || char === '\\n' || char === '\\t'\n}\n\n/**\n * Builds a lookup from trigger character to its config. When two triggers\n * share a character the first one wins, preserving the previous `Array.find`\n * semantics while turning the per-character scan into an O(1) map read.\n */\nfunction buildTriggerCharMap(triggers: TriggerConfig[]): Map<string, TriggerConfig> {\n const map = new Map<string, TriggerConfig>()\n for (const trigger of triggers) {\n if (!map.has(trigger.char)) map.set(trigger.char, trigger)\n }\n return map\n}\n\n// ---------------------------------------------------------------------------\n// Trigger position validation\n// ---------------------------------------------------------------------------\n\n/**\n * Checks whether a trigger character at the given position in text\n * is valid according to the position rule.\n *\n * @param text - The full text content\n * @param charIndex - The index of the trigger character in the text\n * @param position - The position rule to validate against\n */\nexport function isValidTriggerPosition(\n text: string,\n charIndex: number,\n position: TriggerPosition,\n): boolean {\n if (charIndex === 0) return true\n\n const prevChar = text[charIndex - 1]\n\n if (position === 'start') {\n return prevChar === '\\n'\n }\n\n // position === 'any': valid after any whitespace\n return isInlineWhitespace(prevChar)\n}\n\n// ---------------------------------------------------------------------------\n// Trigger detection\n// ---------------------------------------------------------------------------\n\n/**\n * Scans backwards from the cursor position to detect if the user is\n * currently typing a trigger word.\n *\n * Returns the active trigger info, or null if no trigger is active.\n *\n * @param text - The full plain text content\n * @param cursorPos - The cursor position (character offset from start)\n * @param triggers - Available trigger configurations\n */\nexport function detectActiveTrigger(\n text: string,\n cursorPos: number,\n triggers: TriggerConfig[],\n): ActiveTrigger | null {\n if (!text || cursorPos === 0 || triggers.length === 0) return null\n\n const triggerByChar = buildTriggerCharMap(triggers)\n\n // Scan backwards from cursor to find the nearest trigger character.\n // Stop at whitespace (trigger word has ended) or start of text.\n for (let i = cursorPos - 1; i >= 0; i--) {\n const char = text[i]\n\n // If we hit whitespace before finding a trigger, check if this whitespace\n // is immediately followed by a trigger character\n if (isInlineWhitespace(char)) {\n // The character after this whitespace could be a trigger\n if (i + 1 < cursorPos) {\n const nextChar = text[i + 1]\n const matchingTrigger = triggerByChar.get(nextChar)\n if (matchingTrigger && isValidTriggerPosition(text, i + 1, matchingTrigger.position)) {\n return {\n config: matchingTrigger,\n startOffset: i + 1,\n query: text.slice(i + 2, cursorPos),\n }\n }\n }\n // No trigger found after this whitespace, stop searching\n return null\n }\n\n // Check if this character is a trigger character\n const matchingTrigger = triggerByChar.get(char)\n if (matchingTrigger && isValidTriggerPosition(text, i, matchingTrigger.position)) {\n return {\n config: matchingTrigger,\n startOffset: i,\n query: text.slice(i + 1, cursorPos),\n }\n }\n }\n\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Chip resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves an active trigger into a chip within the segments array.\n * Replaces the trigger text (trigger char + query) with a chip segment.\n *\n * @param segments - Current document segments\n * @param activeTrigger - The active trigger to resolve\n * @param chip - The chip data (value, displayText, optional data)\n * @returns New segments array with the chip inserted, and the new cursor position\n */\nexport function resolveChip(\n segments: Segment[],\n activeTrigger: ActiveTrigger,\n chip: { value: string; displayText: string; data?: unknown; autoResolved?: boolean },\n): { segments: Segment[]; cursorOffset: number } {\n const triggerStart = activeTrigger.startOffset\n const triggerEnd = triggerStart + 1 + activeTrigger.query.length // +1 for trigger char\n\n // Build the new segments by mapping plain text positions back to segment boundaries\n const newSegments: Segment[] = []\n let offset = 0\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipText = `${seg.trigger}${seg.displayText}`\n const chipStart = offset\n const chipEnd = offset + chipText.length\n\n // If the trigger range overlaps with this chip, something is wrong.\n // Chips should not be partially replaced.\n if (chipEnd <= triggerStart || chipStart >= triggerEnd) {\n newSegments.push(seg)\n }\n offset = chipEnd\n } else {\n const textStart = offset\n const textEnd = offset + seg.text.length\n\n if (textEnd <= triggerStart) {\n // Entirely before the trigger - keep as-is\n newSegments.push(seg)\n } else if (textStart >= triggerEnd) {\n // Entirely after the trigger - keep as-is\n newSegments.push(seg)\n } else {\n // This text segment contains (part of) the trigger range\n const beforeText = seg.text.slice(0, Math.max(0, triggerStart - textStart))\n const afterText = seg.text.slice(Math.min(seg.text.length, triggerEnd - textStart))\n\n if (beforeText) {\n newSegments.push({ type: 'text', text: beforeText })\n }\n\n const newChip: ChipSegment = {\n type: 'chip',\n trigger: activeTrigger.config.char,\n value: chip.value,\n displayText: chip.displayText,\n ...(chip.data !== undefined ? { data: chip.data } : {}),\n ...(chip.autoResolved ? { autoResolved: true } : {}),\n }\n newSegments.push(newChip)\n\n // Add trailing space after chip, then any remaining text\n if (afterText) {\n newSegments.push({ type: 'text', text: ' ' + afterText.replace(/^\\s/, '') })\n } else {\n newSegments.push({ type: 'text', text: ' ' })\n }\n }\n\n offset = textEnd\n }\n }\n\n // Merge adjacent text segments\n const merged = mergeAdjacentTextSegments(newSegments)\n\n // Cursor should be placed after the chip + trailing space.\n // Find the *last* matching chip so duplicates resolve correctly.\n let lastChipEndOffset = -1\n let runningOffset = 0\n for (const seg of merged) {\n if (seg.type === 'text') {\n runningOffset += seg.text.length\n } else {\n runningOffset += seg.trigger.length + seg.displayText.length\n if (\n seg.value === chip.value &&\n seg.displayText === chip.displayText &&\n seg.trigger === activeTrigger.config.char\n ) {\n lastChipEndOffset = runningOffset\n }\n }\n }\n // +1 accounts for the trailing space after the chip\n const cursorOffset = lastChipEndOffset === -1 ? runningOffset : lastChipEndOffset + 1\n\n return { segments: merged, cursorOffset }\n}\n\n/**\n * Replaces the active trigger's range with **plain text** instead of a chip.\n *\n * The sibling of {@link resolveChip}, for suggestions that are a typing aid\n * rather than a token: what lands in the document is ordinary editable text the\n * user is expected to finish and change, and it must not look or behave like a\n * resolved chip — no immutability, no trigger character, nothing for a consumer\n * to parse back out. The caret is left at the end of the inserted text so typing\n * continues from there.\n */\nexport function resolveText(\n segments: Segment[],\n activeTrigger: ActiveTrigger,\n text: string,\n): { segments: Segment[]; cursorOffset: number } {\n const triggerStart = activeTrigger.startOffset\n const triggerEnd = triggerStart + 1 + activeTrigger.query.length // +1 for trigger char\n\n const newSegments: Segment[] = []\n let offset = 0\n let cursorOffset = triggerStart + text.length\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipEnd = offset + `${seg.trigger}${seg.displayText}`.length\n // A trigger range can never overlap a chip — chips are atomic — so a chip\n // is either wholly before or wholly after, and is kept either way.\n if (chipEnd <= triggerStart || offset >= triggerEnd) newSegments.push(seg)\n offset = chipEnd\n continue\n }\n const textStart = offset\n const textEnd = offset + seg.text.length\n if (textEnd <= triggerStart || textStart >= triggerEnd) {\n newSegments.push(seg)\n } else {\n const before = seg.text.slice(0, Math.max(0, triggerStart - textStart))\n const after = seg.text.slice(Math.min(seg.text.length, triggerEnd - textStart))\n if (before) newSegments.push({ type: 'text', text: before })\n newSegments.push({ type: 'text', text })\n if (after) newSegments.push({ type: 'text', text: after })\n }\n offset = textEnd\n }\n\n const merged = mergeAdjacentTextSegments(newSegments)\n // Clamp: a trigger detected against a stale document could otherwise leave the\n // caret past the end, which renders as no caret at all.\n const total = segmentsToPlainText(merged).length\n if (cursorOffset > total) cursorOffset = total\n return { segments: merged, cursorOffset }\n}\n\n// ---------------------------------------------------------------------------\n// Chip removal\n// ---------------------------------------------------------------------------\n\n/**\n * Removes a chip at the given segment index and merges adjacent text segments.\n *\n * @param segments - Current document segments\n * @param index - The segment index to remove\n * @returns New segments array with the chip removed\n */\nexport function removeChipAtIndex(segments: Segment[], index: number): Segment[] {\n if (index < 0 || index >= segments.length) return segments\n if (segments[index].type !== 'chip') return segments\n\n const result = [...segments.slice(0, index), ...segments.slice(index + 1)]\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Reverts an auto-resolved chip at the given segment index back to plain text.\n * The text includes the trigger character + display text (e.g., \"#readme\").\n *\n * @param segments - Current document segments\n * @param index - The segment index to revert\n * @returns New segments with the chip replaced by text, or null if not applicable\n */\nexport function revertChipAtIndex(\n segments: Segment[],\n index: number,\n): { segments: Segment[]; revertedText: string } | null {\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n if (seg.type !== 'chip' || !seg.autoResolved) return null\n\n const revertedText = `${seg.trigger}${seg.displayText}`\n const result = [\n ...segments.slice(0, index),\n { type: 'text' as const, text: revertedText },\n ...segments.slice(index + 1),\n ]\n return { segments: mergeAdjacentTextSegments(result), revertedText }\n}\n\n// ---------------------------------------------------------------------------\n// Paste: resolve trigger patterns in segments\n// ---------------------------------------------------------------------------\n\n/**\n * Scans text segments for trigger patterns and auto-resolves them into chips.\n * Only resolves triggers that have `resolveOnSpace: true`.\n *\n * Trigger patterns must appear at word boundaries: start of text, after\n * whitespace, or after a newline. This avoids false positives like email\n * addresses (user@example.com).\n */\nexport function resolveTriggersInSegments(\n segments: Segment[],\n triggers: TriggerConfig[],\n): Segment[] {\n const autoResolveTriggers = triggers.filter((t) => t.resolveOnSpace)\n if (autoResolveTriggers.length === 0) return segments\n\n const triggerByChar = buildTriggerCharMap(autoResolveTriggers)\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n result.push(seg)\n continue\n }\n\n const parts = splitTextByTriggerPatterns(seg.text, triggerByChar)\n result.push(...parts)\n }\n\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Splits a text string into text and chip segments based on trigger patterns.\n * A trigger pattern is: (start-of-string | whitespace) + trigger_char + word_chars\n * followed by whitespace or end-of-string.\n */\nfunction splitTextByTriggerPatterns(\n text: string,\n triggerByChar: Map<string, TriggerConfig>,\n): Segment[] {\n if (!text) return []\n\n const segments: Segment[] = []\n let i = 0\n\n while (i < text.length) {\n const char = text[i]\n\n if (triggerByChar.has(char)) {\n const isAtBoundary = i === 0 || isInlineWhitespace(text[i - 1])\n\n if (isAtBoundary) {\n const trigger = triggerByChar.get(char)\n if (trigger && isValidTriggerPosition(text, i, trigger.position)) {\n let end = i + 1\n while (end < text.length && !isInlineWhitespace(text[end])) {\n end++\n }\n\n const query = text.slice(i + 1, end)\n if (query.length > 0) {\n // Treat both undefined and '' from onSelect as \"no custom label\"\n // and fall back to the query — an empty displayText would render\n // a blank chip.\n const displayText = trigger.onSelect?.({ value: query, label: query }) || query\n segments.push({\n type: 'chip',\n trigger: char,\n value: query,\n displayText,\n autoResolved: true,\n })\n i = end\n continue\n }\n }\n }\n }\n\n const start = i\n i++\n while (i < text.length && !(triggerByChar.has(text[i]) && isInlineWhitespace(text[i - 1]))) {\n i++\n }\n segments.push({ type: 'text', text: text.slice(start, i) })\n }\n\n return segments\n}\n\n// ---------------------------------------------------------------------------\n// Text range replacement\n// ---------------------------------------------------------------------------\n\n/**\n * Replaces a range of plain text within the segments array.\n * Handles segment boundaries correctly, preserving chip segments.\n *\n * @param segments - Current document segments\n * @param start - Start offset in plain text\n * @param end - End offset in plain text\n * @param replacement - The replacement text\n * @returns New segments array with the replacement applied\n */\nexport function replaceTextRange(\n segments: Segment[],\n start: number,\n end: number,\n replacement: string,\n): Segment[] {\n const newSegments: Segment[] = []\n let offset = 0\n let inserted = false\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipText = `${seg.trigger}${seg.displayText}`\n const chipStart = offset\n const chipEnd = offset + chipText.length\n\n // For insertion (start === end), insert before this chip if position matches\n if (!inserted && start === end && chipStart === start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n\n if (chipEnd <= start || chipStart >= end) {\n newSegments.push(seg)\n }\n // Chips within the range are removed\n offset = chipEnd\n } else {\n const textStart = offset\n const textEnd = offset + seg.text.length\n\n // Check if this segment contains the insertion/replacement point\n const isBefore = start === end ? textEnd < start : textEnd <= start\n const isAfter = start === end ? textStart > end : textStart >= end\n\n if (isBefore) {\n // Entirely before the range\n newSegments.push(seg)\n } else if (isAfter) {\n // Entirely after the range\n newSegments.push(seg)\n } else {\n // Overlaps with the range (or contains the insertion point)\n const beforeText = seg.text.slice(0, Math.max(0, start - textStart))\n const afterText = seg.text.slice(Math.min(seg.text.length, end - textStart))\n\n if (beforeText) {\n newSegments.push({ type: 'text', text: beforeText })\n }\n // Insert replacement only once (when we first enter the range)\n if (!inserted && textStart <= start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n if (afterText) {\n newSegments.push({ type: 'text', text: afterText })\n }\n }\n\n offset = textEnd\n }\n }\n\n // Fallback: if replacement wasn't inserted (e.g., insertion at very end)\n if (!inserted && replacement) {\n newSegments.push({ type: 'text', text: replacement })\n }\n\n return mergeAdjacentTextSegments(newSegments)\n}\n\n// ---------------------------------------------------------------------------\n// Markdown formatting shortcuts\n// ---------------------------------------------------------------------------\n\n/**\n * Toggles markdown wrap markers around a selected text range.\n * If the selection is already wrapped with the marker, unwraps it.\n * If not wrapped, wraps it.\n *\n * @param segments - Current document segments\n * @param selectionStart - Start offset in plain text\n * @param selectionEnd - End offset in plain text\n * @param marker - The markdown marker (e.g., '**' for bold, '*' for italic)\n * @returns New segments and selection offsets, or null if selection is collapsed\n */\nexport function toggleMarkdownWrap(\n segments: Segment[],\n selectionStart: number,\n selectionEnd: number,\n marker: string,\n): { segments: Segment[]; selectionStart: number; selectionEnd: number } | null {\n if (selectionStart === selectionEnd) return null\n\n const plainText = segmentsToPlainText(segments)\n const markerLen = marker.length\n\n // Check if already wrapped\n const hasOpeningMarker =\n selectionStart >= markerLen &&\n plainText.slice(selectionStart - markerLen, selectionStart) === marker\n const hasClosingMarker =\n selectionEnd + markerLen <= plainText.length &&\n plainText.slice(selectionEnd, selectionEnd + markerLen) === marker\n\n let isWrapped = hasOpeningMarker && hasClosingMarker\n\n // For single-char markers (e.g., '*'), ensure we're not matching\n // inside a multi-char marker (e.g., '**')\n if (isWrapped && markerLen === 1) {\n const charBeforeOpening =\n selectionStart > markerLen ? plainText[selectionStart - markerLen - 1] : ''\n const charAfterClosing =\n selectionEnd + markerLen < plainText.length ? plainText[selectionEnd + markerLen] : ''\n if (charBeforeOpening === marker || charAfterClosing === marker) {\n isWrapped = false\n }\n }\n\n if (isWrapped) {\n // Unwrap: remove closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd + markerLen, '')\n const afterOpening = replaceTextRange(\n afterClosing,\n selectionStart - markerLen,\n selectionStart,\n '',\n )\n return {\n segments: afterOpening,\n selectionStart: selectionStart - markerLen,\n selectionEnd: selectionEnd - markerLen,\n }\n }\n\n // Wrap: insert closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd, marker)\n const afterOpening = replaceTextRange(afterClosing, selectionStart, selectionStart, marker)\n return {\n segments: afterOpening,\n selectionStart: selectionStart + markerLen,\n selectionEnd: selectionEnd + markerLen,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Inline markdown parsing\n// ---------------------------------------------------------------------------\n\nexport type MarkdownToken =\n | { type: 'plain'; text: string }\n | { type: 'bold'; text: string }\n | { type: 'italic'; text: string }\n | { type: 'bold-italic'; text: string }\n | { type: 'url'; text: string }\n\n/**\n * Parses text for simple inline markdown: bold, italic, bold-italic, and URLs.\n * Does NOT handle block-level markdown (lists, headings, etc.).\n */\nexport function parseInlineMarkdown(text: string): MarkdownToken[] {\n if (!text) return []\n\n const tokens: MarkdownToken[] = []\n // Regex patterns for inline markdown elements:\n // 1. ***text*** or ___text___ -> bold-italic\n // 2. **text** or __text__ -> bold\n // 3. *text* or _text_ -> italic\n // 4. https://... or http://... -> URL\n const pattern = /(\\*{3}(.+?)\\*{3})|(\\*{2}(.+?)\\*{2})|(\\*(.+?)\\*)|(https?:\\/\\/[^\\s),]+)/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n while ((match = pattern.exec(text)) !== null) {\n // Add any plain text before this match\n if (match.index > lastIndex) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex, match.index) })\n }\n\n if (match[1] && match[2]) {\n // ***bold-italic***\n tokens.push({ type: 'bold-italic', text: match[2] })\n } else if (match[3] && match[4]) {\n // **bold**\n tokens.push({ type: 'bold', text: match[4] })\n } else if (match[5] && match[6]) {\n // *italic*\n tokens.push({ type: 'italic', text: match[6] })\n } else if (match[7]) {\n // URL\n tokens.push({ type: 'url', text: match[7] })\n }\n\n lastIndex = match.index + match[0].length\n }\n\n // Add any remaining plain text\n if (lastIndex < text.length) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex) })\n }\n\n return tokens\n}\n\n// ---------------------------------------------------------------------------\n// Segment comparison\n// ---------------------------------------------------------------------------\n\n/**\n * Shallow equality check for two segment arrays.\n * Compares type, text, trigger, value, displayText, and autoResolved fields.\n * Avoids JSON.stringify overhead for the common case.\n */\nexport function segmentsEqual(a: Segment[], b: Segment[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n\n for (let i = 0; i < a.length; i++) {\n const sa = a[i]\n const sb = b[i]\n if (sa.type !== sb.type) return false\n if (sa.type === 'text') {\n if (sb.type !== 'text' || sa.text !== sb.text) return false\n } else {\n if (\n sb.type !== 'chip' ||\n sa.trigger !== sb.trigger ||\n sa.value !== sb.value ||\n sa.displayText !== sb.displayText ||\n sa.autoResolved !== sb.autoResolved\n )\n return false\n }\n }\n return true\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merges adjacent text segments into single text segments.\n * Also removes empty text segments.\n */\nexport function mergeAdjacentTextSegments(segments: Segment[]): Segment[] {\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'text' && seg.text === '') continue\n\n const last = result[result.length - 1]\n if (seg.type === 'text' && last?.type === 'text') {\n // Merge with previous text segment\n result[result.length - 1] = { type: 'text', text: last.text + seg.text }\n } else {\n result.push(seg)\n }\n }\n\n return result\n}\n","/**\n * List auto-formatting logic for the PromptArea component.\n * Pure — no DOM dependencies, fully testable in Node.\n */\nimport type { Segment } from './types.ts'\nimport { replaceTextRange, segmentsToPlainText } from './prompt-area-engine.ts'\n\n/**\n * Information about a list line at a given cursor position.\n */\nexport type ListContext = {\n /** Offset in plain text where the line begins */\n lineStart: number\n /** The full prefix including indentation (e.g., \" • \") */\n prefix: string\n /** Number of indentation levels (each = 2 spaces) */\n indent: number\n /** Type of list */\n listType: 'bullet' | 'numbered'\n /** For bullet lists, the marker char actually used (`•`, `-`, or `*`) */\n marker?: string\n /** For numbered lists, the number */\n number?: number\n /** Offset in plain text where content after the prefix starts */\n contentStart: number\n}\n\n/**\n * Parsed shape of a single list line — the SINGLE source of truth for what\n * counts as a list line (both `getListContext` and the renumber engine derive\n * from this, so the bullet/number regexes live in exactly one place).\n *\n * Offsets (`numberStart`/`numberEnd`) are relative to the START of the line.\n */\ntype ParsedListLine =\n | { kind: 'bullet'; indent: number; marker: string; prefixLen: number }\n | {\n kind: 'numbered'\n indent: number\n number: number\n /** Offset of the first digit within the line. */\n numberStart: number\n /** Offset just past the last digit within the line (exclusive). */\n numberEnd: number\n prefixLen: number\n }\n\n/** Classifies a single line as a bullet/numbered list item, or null. */\nfunction parseListLine(line: string): ParsedListLine | null {\n const bulletMatch = line.match(/^(\\s*)([•\\-*]) /)\n if (bulletMatch) {\n return {\n kind: 'bullet',\n indent: Math.floor(bulletMatch[1].length / 2),\n marker: bulletMatch[2],\n prefixLen: bulletMatch[0].length,\n }\n }\n\n const numberMatch = line.match(/^(\\s*)(\\d+)\\. /)\n if (numberMatch) {\n const numberStart = numberMatch[1].length\n return {\n kind: 'numbered',\n indent: Math.floor(numberMatch[1].length / 2),\n number: parseInt(numberMatch[2], 10),\n numberStart,\n numberEnd: numberStart + numberMatch[2].length,\n prefixLen: numberMatch[0].length,\n }\n }\n\n return null\n}\n\n/**\n * Detects if the cursor is in a list line and returns context about it.\n *\n * @param text - The full plain text content\n * @param cursorPos - The cursor position (character offset from start)\n * @returns List context if the cursor is in a list line, null otherwise\n */\nexport function getListContext(text: string, cursorPos: number): ListContext | null {\n const lineStart = text.lastIndexOf('\\n', cursorPos - 1) + 1\n const lineEnd = text.indexOf('\\n', cursorPos)\n const line = text.slice(lineStart, lineEnd === -1 ? text.length : lineEnd)\n\n const parsed = parseListLine(line)\n if (!parsed) return null\n\n return {\n lineStart,\n prefix: line.slice(0, parsed.prefixLen),\n indent: parsed.indent,\n listType: parsed.kind,\n ...(parsed.kind === 'bullet' ? { marker: parsed.marker } : { number: parsed.number }),\n contentStart: lineStart + parsed.prefixLen,\n }\n}\n\n/**\n * Detects if the user just typed a list trigger pattern (e.g., \"- \" or \"* \")\n * and returns the segments with the replacement applied.\n */\nexport function autoFormatListPrefix(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const lineStart = plainText.lastIndexOf('\\n', cursorPos - 1) + 1\n const lineText = plainText.slice(lineStart, cursorPos)\n\n const match = lineText.match(/^(\\s*)[-*] $/)\n if (!match) return null\n\n const indent = match[1]\n const replacement = `${indent}• `\n const rangeStart = lineStart\n const rangeEnd = lineStart + lineText.length\n\n const newSegments = replaceTextRange(segments, rangeStart, rangeEnd, replacement)\n return {\n segments: newSegments,\n cursorOffset: lineStart + replacement.length,\n }\n}\n\n/**\n * Handles Enter key in a list line — continues the list or exits.\n */\nexport function insertListContinuation(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n const lineEnd = plainText.indexOf('\\n', cursorPos)\n const lineContent = plainText.slice(ctx.contentStart, lineEnd === -1 ? plainText.length : lineEnd)\n\n if (lineContent.trim() === '') {\n // Enter on an empty item: outdent one level if nested (Notion/Docs style),\n // otherwise remove the prefix and exit the list to plain text.\n if (ctx.indent > 0) {\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart + 2, '')\n return {\n segments: newSegments,\n cursorOffset: Math.max(ctx.lineStart, cursorPos - 2),\n }\n }\n const newSegments = replaceTextRange(\n segments,\n ctx.lineStart,\n ctx.lineStart + ctx.prefix.length,\n '',\n )\n return {\n segments: newSegments,\n cursorOffset: ctx.lineStart,\n }\n }\n\n const indent = ' '.repeat(ctx.indent)\n let nextPrefix: string\n if (ctx.listType === 'bullet') {\n nextPrefix = `${indent}${ctx.marker ?? '•'} `\n } else {\n const nextNum = (ctx.number ?? 1) + 1\n nextPrefix = `${indent}${nextNum}. `\n }\n\n const insertion = `\\n${nextPrefix}`\n const newSegments = replaceTextRange(segments, cursorPos, cursorPos, insertion)\n return {\n segments: newSegments,\n cursorOffset: cursorPos + insertion.length,\n }\n}\n\n/** Returns the indent level of the list line directly above `lineStart`, or null. */\nfunction getPrevListLineLevel(text: string, lineStart: number): number | null {\n if (lineStart === 0) return null\n const prevLineStart = text.lastIndexOf('\\n', lineStart - 2) + 1\n const parsed = parseListLine(text.slice(prevLineStart, lineStart - 1))\n return parsed ? parsed.indent : null\n}\n\n/**\n * Indents a list item by one level (adds 2 spaces before the prefix), capped at\n * one level deeper than the line above. An item can only nest under a preceding\n * sibling, so the first item of a list — or an item already one level below its\n * parent — cannot indent further (returns null). This keeps sub-items visually\n * connected to a parent instead of drifting arbitrarily deep.\n */\nexport function indentListItem(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n const prevLevel = getPrevListLineLevel(plainText, ctx.lineStart)\n const maxLevel = prevLevel === null ? 0 : prevLevel + 1\n if (ctx.indent >= maxLevel) return null\n\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart, ' ')\n return {\n segments: newSegments,\n cursorOffset: cursorPos + 2,\n }\n}\n\n/**\n * Outdents a list item by one level (removes 2 spaces from before the prefix).\n */\nexport function outdentListItem(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx || ctx.indent === 0) return null\n\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart + 2, '')\n return {\n segments: newSegments,\n cursorOffset: Math.max(ctx.lineStart, cursorPos - 2),\n }\n}\n\n/**\n * Removes the list prefix from the current line (e.g., on Backspace).\n */\nexport function removeListPrefix(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n if (cursorPos > ctx.contentStart) return null\n\n const newSegments = replaceTextRange(\n segments,\n ctx.lineStart,\n ctx.contentStart,\n ' '.repeat(ctx.indent),\n )\n return {\n segments: newSegments,\n cursorOffset: ctx.lineStart + ctx.indent * 2,\n }\n}\n\n/** A line that opens or closes a fenced code block (```), optionally indented. */\nconst FENCE_LINE = /^\\s*```/\n\n/** Swaps the leading list marker on a single line (\"- \" ↔ \"• \"). */\nfunction swapListPrefixLine(line: string, markdownEnabled: boolean): string {\n return markdownEnabled ? line.replace(/^(\\s*)- /, '$1• ') : line.replace(/^(\\s*)• /, '$1- ')\n}\n\n/**\n * Returns the set of line indices that sit inside a *balanced* fenced code\n * block (a ```…``` pair), so their leading \"- \"/\"• \" markers are preserved\n * verbatim. An unterminated (unpaired) fence marker is NOT protective — its\n * following lines still normalize — so a stray \"```\" in prose does not silently\n * suppress bullet normalization for the rest of the text.\n */\nfunction fenceProtectedLineIndices(lines: string[]): Set<number> {\n const protectedLines = new Set<number>()\n let openIndex = -1\n lines.forEach((line, i) => {\n if (!FENCE_LINE.test(line)) return\n if (openIndex === -1) {\n openIndex = i\n } else {\n for (let k = openIndex; k <= i; k++) protectedLines.add(k)\n openIndex = -1\n }\n })\n return protectedLines\n}\n\n/**\n * Normalizes markdown list prefixes in a raw text string (single source of\n * truth for the bullet-glyph swap, shared by segment normalization and paste):\n * - When markdown is enabled, converts \"- \" at line starts to \"• \"\n * - When markdown is disabled, converts \"• \" at line starts to \"- \"\n *\n * Lines inside a balanced ```…``` block are preserved verbatim.\n */\nexport function normalizeListPrefixText(text: string, markdownEnabled: boolean): string {\n const lines = text.split('\\n')\n const protectedLines = fenceProtectedLineIndices(lines)\n return lines\n .map((line, i) => (protectedLines.has(i) ? line : swapListPrefixLine(line, markdownEnabled)))\n .join('\\n')\n}\n\n/**\n * Normalizes markdown list prefixes across text segments. See\n * {@link normalizeListPrefixText} for the per-line rule. Fence detection spans\n * the whole document (text segments flattened to a global line sequence), so a\n * code block split into per-line text segments on paste still has its \"- \" lines\n * preserved, while an unterminated fence does not suppress later bullets.\n */\nexport function normalizeListPrefixes(segments: Segment[], markdownEnabled: boolean): Segment[] {\n const globalLines: string[] = []\n segments.forEach((seg) => {\n if (seg.type === 'text') globalLines.push(...seg.text.split('\\n'))\n })\n const protectedLines = fenceProtectedLineIndices(globalLines)\n\n let globalIndex = 0\n let changed = false\n const result = segments.map((seg) => {\n if (seg.type !== 'text') return seg\n const newText = seg.text\n .split('\\n')\n .map((line) => {\n const out = protectedLines.has(globalIndex)\n ? line\n : swapListPrefixLine(line, markdownEnabled)\n globalIndex++\n return out\n })\n .join('\\n')\n if (newText === seg.text) return seg\n changed = true\n return { ...seg, text: newText }\n })\n return changed ? result : segments\n}\n\n// ---------------------------------------------------------------------------\n// Ordered-list renumbering\n//\n// The visible number is a projection of position (like BlockNote/ProseMirror/\n// Notion), recomputed on every structural edit rather than trusted as stored\n// text. A per-indent-level counter STACK models nested lists: descending starts\n// a fresh counter at 1, ascending continues the shallower level and clears\n// deeper ones. Every run restarts at 1, so `1. 1. 1.` rebuilds to `1. 2. 3.`\n// and Tab-indenting an item restarts its sublist at 1.\n// ---------------------------------------------------------------------------\n\n/**\n * A single rewritten number's digit run, in the INPUT text's coordinates.\n * `[oldStart, oldEnd)` spans only the digits (never the indentation or `. `).\n */\nexport type NumberEdit = { oldStart: number; oldEnd: number; newText: string }\n\n/**\n * Whether the text holds a genuine ordered-list run worth renumbering — a run\n * of 2+ consecutive same-level numbered lines that either starts at 1 or is\n * already a contiguous `n, n+1, …` sequence. Used to gate the paste path so a\n * copied list fragment (`3. 4. 5.` → renumber, or a broken `1. 1. 1.`) is\n * rebuilt, while incidental numeric-leading prose that `parseListLine` would\n * otherwise treat as a list — `1985. Born / 2020. Died`, `5. / 10. / 15.` — is\n * left untouched.\n */\nexport function hasOrderedListRun(text: string): boolean {\n let runLevel: number | null = null\n let runStart = 0\n let prevNumber = 0\n let runLength = 0\n let sequential = true\n\n for (const line of text.split('\\n')) {\n const parsed = parseListLine(line)\n if (parsed?.kind === 'numbered' && parsed.indent === runLevel) {\n sequential = sequential && parsed.number === prevNumber + 1\n prevNumber = parsed.number\n runLength++\n if (runLength >= 2 && (runStart === 1 || sequential)) return true\n } else if (parsed?.kind === 'numbered') {\n // Start a fresh run at this line's level.\n runLevel = parsed.indent\n runStart = parsed.number\n prevNumber = parsed.number\n runLength = 1\n sequential = true\n } else {\n runLevel = null\n runLength = 0\n }\n }\n\n return false\n}\n\n/**\n * Recomputes ordered-list numbering across the whole text. Returns the new text\n * plus the list of changed digit runs (ascending by `oldStart`) for cursor\n * remapping. When nothing changes, returns the SAME text reference and an empty\n * `edits` array — the no-op guard that keeps this off the typing hot path.\n */\nexport function renumberOrderedListLines(text: string): { text: string; edits: NumberEdit[] } {\n // Cheap pre-gate: with no ordered-list line there is nothing to renumber, so\n // skip the per-line scan and throwaway rebuild. This runs on every structural\n // edit (Enter, Tab, bold/italic wrap) and each paste, most of which never\n // touch a numbered list.\n if (!/^[ \\t]*\\d+\\. /m.test(text)) return { text, edits: [] }\n\n const counters = new Map<number, number>()\n const edits: NumberEdit[] = []\n const lines = text.split('\\n')\n let out = ''\n let lineStart = 0\n\n const clearDeeperThan = (level: number, inclusive: boolean) => {\n for (const key of counters.keys()) {\n if (inclusive ? key >= level : key > level) counters.delete(key)\n }\n }\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const parsed = parseListLine(line)\n\n if (!parsed) {\n // A blank or plain (non-list) line breaks every open list run.\n counters.clear()\n out += line\n } else if (parsed.kind === 'bullet') {\n // A bullet interrupts numbered runs at its level and deeper; a shallower\n // numbered list continues across it.\n clearDeeperThan(parsed.indent, true)\n out += line\n } else {\n const level = parsed.indent\n clearDeeperThan(level, false)\n const current = counters.get(level)\n const n = current === undefined ? 1 : current + 1\n counters.set(level, n)\n\n const newDigits = String(n)\n if (newDigits === String(parsed.number)) {\n out += line\n } else {\n edits.push({\n oldStart: lineStart + parsed.numberStart,\n oldEnd: lineStart + parsed.numberEnd,\n newText: newDigits,\n })\n out += line.slice(0, parsed.numberStart) + newDigits + line.slice(parsed.numberEnd)\n }\n }\n\n if (i < lines.length - 1) out += '\\n'\n lineStart += line.length + 1 // + 1 for the consumed '\\n'\n }\n\n return edits.length === 0 ? { text, edits } : { text: out, edits }\n}\n\n/**\n * Remaps a caret/selection offset (in the renumber INPUT's coordinates) across\n * the digit-run edits. A single scalar delta is wrong: many spans each change\n * width, so the shift depends on how many changed spans lie strictly before the\n * offset, with a clamp when the offset sits inside a resized number.\n */\nexport function remapOffset(old: number, edits: NumberEdit[]): number {\n let shift = 0\n for (const e of edits) {\n if (e.oldEnd <= old) {\n shift += e.newText.length - (e.oldEnd - e.oldStart) // fully before\n } else if (e.oldStart >= old) {\n break // this and every later span is after the offset\n } else {\n return e.oldStart + shift + Math.min(old - e.oldStart, e.newText.length) // inside → clamp\n }\n }\n return old + shift\n}\n\n/**\n * Segment-level renumber used by the edit-commit path. Applies the digit-run\n * edits to the segments (right-to-left so earlier offsets stay valid) and\n * returns the changed spans for {@link remapOffset}. Digit runs are pure text at\n * line starts, so chips are never touched.\n */\nexport function renumberOrderedListSegments(segments: Segment[]): {\n segments: Segment[]\n edits: NumberEdit[]\n} {\n const { edits } = renumberOrderedListLines(segmentsToPlainText(segments))\n if (edits.length === 0) return { segments, edits }\n\n let result = segments\n for (let i = edits.length - 1; i >= 0; i--) {\n const e = edits[i]\n result = replaceTextRange(result, e.oldStart, e.oldEnd, e.newText)\n }\n return { segments: result, edits }\n}\n","/**\n * Type-safe DOM helper functions for PromptArea.\n *\n * These replace all `as` type assertions with proper type guards,\n * following the codebase rule: \"Never use `any` or `as` assertions.\"\n */\n\nimport type { ChipSegment } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Type Guards\n// ---------------------------------------------------------------------------\n\n/**\n * Type guard: checks if a DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return node instanceof HTMLElement\n}\n\n/**\n * Type guard: checks if a DOM node is a chip element\n * (an HTMLElement with data-chip-trigger attribute).\n */\nexport function isChipElement(node: Node): node is HTMLElement {\n return node instanceof HTMLElement && node.dataset.chipTrigger !== undefined\n}\n\n/**\n * Type guard: checks if a DOM node is a BR element.\n */\nexport function isBRElement(node: Node): node is HTMLBRElement {\n return node instanceof HTMLBRElement\n}\n\n/**\n * Type guard: checks if a DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node instanceof Text\n}\n\n/**\n * Checks whether a chip element was auto-resolved (created by pressing space\n * on resolveOnSpace triggers, rather than explicit dropdown selection).\n */\nexport function getChipAutoResolved(node: Node): boolean {\n return isChipElement(node) && node.dataset.chipAutoResolved === 'true'\n}\n\n/**\n * Type guard: checks if a DOM node is a URL link element\n * (an HTMLAnchorElement with data-url attribute).\n */\nexport function isLinkElement(node: Node): node is HTMLAnchorElement {\n return node instanceof HTMLAnchorElement && node.dataset.url === 'true'\n}\n\n// ---------------------------------------------------------------------------\n// Safe JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Safely parses a JSON string, returning `unknown` instead of `any`.\n * Returns `undefined` if parsing fails.\n */\nexport function safeJsonParse(json: string): unknown {\n try {\n // JSON.parse returns `any` by default. We narrow it to `unknown`\n // which is the safest pattern — callers must validate before use.\n const parsed: unknown = JSON.parse(json)\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Safely serializes a value to JSON, returning undefined on failure.\n */\nexport function safeJsonStringify(value: unknown): string | undefined {\n try {\n return JSON.stringify(value)\n } catch {\n return undefined\n }\n}\n\n// ---------------------------------------------------------------------------\n// DOM reading helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Reads the chip trigger character from a chip element's dataset.\n * Returns undefined if the node is not a chip element.\n */\nexport function getChipTrigger(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipTrigger\n}\n\n/**\n * Reads the chip value from a chip element's dataset.\n */\nexport function getChipValue(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipValue\n}\n\n/**\n * Reads the chip display text from a chip element's dataset.\n */\nexport function getChipDisplay(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipDisplay ?? node.textContent ?? undefined\n}\n\n/**\n * Reads and safely parses the chip data from a chip element's dataset.\n */\nexport function getChipData(node: Node): unknown {\n if (!isChipElement(node)) return undefined\n const raw = node.dataset.chipData\n if (!raw) return undefined\n return safeJsonParse(raw)\n}\n\n/**\n * Length of a chip's plain-text representation (`trigger + displayText`).\n *\n * This is the single definition used wherever DOM offsets are mapped to the\n * plain-text model (cursor mapping, selection sizing). The fallbacks mirror\n * how chips are rendered: `chipDisplay` is always set, but we degrade to\n * `textContent` for resilience against externally-mutated nodes.\n */\nexport function chipNodeTextLength(node: HTMLElement): number {\n const trigger = node.dataset.chipTrigger ?? ''\n const display = node.dataset.chipDisplay ?? node.textContent ?? ''\n return trigger.length + display.length\n}\n\n/**\n * Reads a chip element into a `ChipSegment`, mirroring how chips are written\n * in `renderSegmentsToDOM`. Returns null when the node is not a chip or is\n * missing a required attribute (trigger, value, or display text).\n *\n * This is the single chip reader shared by the DOM->model sync, chip-click\n * delegation, and clipboard serialization, so they cannot diverge on which\n * fields are required or how optional `data` / `autoResolved` are attached.\n */\nexport function chipNodeToSegment(node: Node): ChipSegment | null {\n if (!isChipElement(node)) return null\n\n const trigger = getChipTrigger(node)\n const value = getChipValue(node)\n const displayText = getChipDisplay(node)\n if (!trigger || value === undefined || !displayText) return null\n\n const data = getChipData(node)\n const autoResolved = getChipAutoResolved(node)\n\n return {\n type: 'chip',\n trigger,\n value,\n displayText,\n ...(data !== undefined ? { data } : {}),\n ...(autoResolved ? { autoResolved: true } : {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// DOM manipulation helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Finds the index of a direct child node within a parent element.\n * Returns -1 if not found.\n */\nexport function indexOfChildNode(parent: HTMLElement, child: Node): number {\n const children = parent.childNodes\n for (let i = 0; i < children.length; i++) {\n if (children[i] === child) return i\n }\n return -1\n}\n\n/**\n * Whether a direct editor child node produces a segment when read by\n * `readSegmentsFromDOM` in use-prompt-area.ts. This is the single predicate\n * shared with `domChildIndexToSegmentIndex` below, so a DOM child index can\n * never map to a different segment index than the one the reader actually\n * produces — decoration elements (the URL `<a>` from `decorateURLsInEditor`,\n * the markdown `<span data-md>` from `decorateMarkdownInEditor`) fall through\n * to the reader's \"unknown element\" branch and DO produce a text segment, so\n * they must count here too, not just chips/text/`<br>`. A chip element only\n * counts if `chipNodeToSegment` would actually accept it — the reader skips a\n * chip missing a required attribute (trigger/value/display), so this must too.\n */\nexport function childProducesSegment(child: Node): boolean {\n if (child.nodeType === Node.TEXT_NODE) return (child.textContent ?? '') !== ''\n if (isBRElement(child)) return !child.dataset.sentinel\n if (isChipElement(child)) return chipNodeToSegment(child) !== null\n if (isHTMLElement(child)) return (child.textContent ?? '') !== ''\n return false\n}\n\n/**\n * Maps the index of a direct child node within the editor to the index of the\n * corresponding segment in the model array, by counting `childProducesSegment`\n * matches up to (but not including) `childIndex`.\n *\n * Keeping this in one place ensures chip-removal, chip-revert, and chip\n * in-place replacement all agree on the exact same mapping rules as the reader.\n */\nexport function domChildIndexToSegmentIndex(editor: HTMLElement, childIndex: number): number {\n let segIdx = 0\n for (let i = 0; i < childIndex; i++) {\n if (childProducesSegment(editor.childNodes[i])) segIdx++\n }\n return segIdx\n}\n\n/**\n * Gets the direct child of `ancestor` that contains `descendant`.\n * Walks up from descendant until we find a node whose parent is ancestor.\n * Returns null if descendant is not inside ancestor.\n */\nexport function getDirectChildContaining(ancestor: HTMLElement, descendant: Node): Node | null {\n let node: Node | null = descendant\n while (node !== null) {\n if (node.parentNode === ancestor) return node\n node = node.parentNode\n }\n return null\n}\n\n/**\n * Unwraps a block element (div, p) by replacing it with its child nodes\n * plus a trailing BR. Used for browser DOM normalization.\n */\nexport function unwrapBlockElement(parent: HTMLElement, block: HTMLElement): void {\n const fragment = document.createDocumentFragment()\n\n // Move all children to fragment\n while (block.firstChild) {\n fragment.appendChild(block.firstChild)\n }\n\n // Add a BR after the unwrapped content\n fragment.appendChild(document.createElement('br'))\n\n parent.replaceChild(fragment, block)\n}\n\n/**\n * Normalizes the editor DOM after browser mutations.\n *\n * Browsers insert various wrapper elements on Enter/paste:\n * - Chrome wraps new lines in <div>\n * - Safari may use <div><br></div>\n * - Some use <p> tags\n *\n * This function unwraps all non-chip block elements, leaving only:\n * - Text nodes\n * - <br> elements\n * - Chip <span> elements (with data-chip-trigger)\n */\nexport function normalizeEditorDOM(editor: HTMLElement): boolean {\n let changed = false\n const blockTags = new Set(['DIV', 'P', 'SECTION', 'ARTICLE', 'BLOCKQUOTE'])\n\n // Iterate backwards since we're modifying the DOM\n for (let i = editor.childNodes.length - 1; i >= 0; i--) {\n const child = editor.childNodes[i]\n\n // Skip non-element nodes, chip elements, and BR elements\n if (!(child instanceof HTMLElement)) continue\n if (child.dataset.chipTrigger !== undefined) continue\n if (child instanceof HTMLBRElement) continue\n\n const tag = child.tagName\n if (blockTags.has(tag)) {\n unwrapBlockElement(editor, child)\n changed = true\n } else if (\n tag === 'FONT' ||\n tag === 'B' ||\n tag === 'I' ||\n tag === 'U' ||\n tag === 'STRONG' ||\n tag === 'EM' ||\n tag === 'A' ||\n tag === 'SPAN'\n ) {\n // Unwrap inline formatting/decoration elements (browser-inserted or markdown decorations)\n const text = child.textContent ?? ''\n if (text) {\n editor.replaceChild(document.createTextNode(text), child)\n } else {\n editor.removeChild(child)\n }\n changed = true\n }\n }\n\n // Merge adjacent text nodes\n editor.normalize()\n\n return changed\n}\n\n// ---------------------------------------------------------------------------\n// URL decoration\n// ---------------------------------------------------------------------------\n\n/** URL pattern for detecting URLs in text content */\nconst URL_PATTERN = /https?:\\/\\/[^\\s),]+/g\n\n/**\n * Walks direct-child text nodes in the editor and wraps URL text in\n * `<a>` elements for visual styling and clickability.\n *\n * This is a DOM-only decoration — it does NOT modify the segment model.\n * The `<a>` elements are stripped by `normalizeEditorDOM` on every input cycle,\n * so they are re-applied fresh each time.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateURLsInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n // Collect text nodes first (avoid modifying while iterating)\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n URL_PATTERN.lastIndex = 0\n const matches: Array<{ url: string; index: number }> = []\n let match: RegExpExecArray | null\n\n while ((match = URL_PATTERN.exec(text)) !== null) {\n // Trim trailing punctuation that's likely not part of the URL\n let url = match[0]\n while (url.length > 0 && /[.;:!?]$/.test(url)) {\n url = url.slice(0, -1)\n }\n if (url.length > 0) {\n matches.push({ url, index: match.index })\n }\n }\n\n if (matches.length === 0) continue\n\n const parent = textNode.parentNode\n if (!parent) continue\n\n // Validate URLs upfront – only keep those with safe protocols (CWE-79)\n const safeMatches: Array<{ url: string; href: string; index: number }> = []\n for (const { url, index } of matches) {\n try {\n const parsed = new URL(url)\n if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {\n safeMatches.push({ url, href: parsed.href, index })\n }\n } catch {\n // skip malformed URLs\n }\n }\n\n if (safeMatches.length === 0) continue\n\n decorated = true\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { url, href, index } of safeMatches) {\n // Text before this URL\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n\n // Create the link element\n const anchor = document.createElement('a')\n anchor.href = href\n anchor.target = '_blank'\n anchor.rel = 'noopener noreferrer'\n anchor.dataset.url = 'true'\n anchor.className = 'text-accent hover:text-accent-hover underline cursor-pointer'\n anchor.textContent = url\n fragment.appendChild(anchor)\n\n lastIndex = index + url.length\n }\n\n // Text after the last URL\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n// ---------------------------------------------------------------------------\n// Markdown inline decoration\n// ---------------------------------------------------------------------------\n\n/** Pattern to find ***bold-italic***, **bold**, and *italic* markdown spans */\nconst MARKDOWN_INLINE_PATTERN = /(\\*{3})(.+?)\\*{3}|(\\*{2})(.+?)\\*{2}|(\\*)(.+?)\\*/g\n\n/**\n * Walks direct-child text nodes in the editor and wraps markdown-formatted\n * text (`**bold**`, `*italic*`, `***bold-italic***`) in styled `<span>` elements.\n *\n * This is a DOM-only decoration — it does NOT modify the segment model.\n * The `<span>` elements are stripped by `normalizeEditorDOM` on every input cycle,\n * so they are re-applied fresh each time.\n *\n * The `*` markers stay visible in the text; only the CSS styling changes.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateMarkdownInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n // Collect text nodes first (avoid modifying while iterating)\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n MARKDOWN_INLINE_PATTERN.lastIndex = 0\n const matches: Array<{\n fullMatch: string\n marker: string\n content: string\n index: number\n className: string\n }> = []\n let match: RegExpExecArray | null\n\n while ((match = MARKDOWN_INLINE_PATTERN.exec(text)) !== null) {\n if (match[1] && match[2]) {\n // ***bold-italic***\n matches.push({\n fullMatch: match[0],\n marker: match[1],\n content: match[2],\n index: match.index,\n className: 'font-bold italic',\n })\n } else if (match[3] && match[4]) {\n // **bold**\n matches.push({\n fullMatch: match[0],\n marker: match[3],\n content: match[4],\n index: match.index,\n className: 'font-bold',\n })\n } else if (match[5] && match[6]) {\n // *italic*\n matches.push({\n fullMatch: match[0],\n marker: match[5],\n content: match[6],\n index: match.index,\n className: 'italic',\n })\n }\n }\n\n if (matches.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { fullMatch, marker, content, index, className } of matches) {\n // Text before this match\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n\n // Parent span — textContent still returns full match (e.g. \"**world**\")\n const span = document.createElement('span')\n span.dataset.md = 'true'\n\n // Opening marker (visually hidden)\n const openMarker = document.createElement('span')\n openMarker.className = 'prompt-area-md-marker'\n openMarker.textContent = marker\n\n // Styled content\n const styledContent = document.createElement('span')\n styledContent.className = className\n styledContent.textContent = content\n\n // Closing marker (visually hidden)\n const closeMarker = document.createElement('span')\n closeMarker.className = 'prompt-area-md-marker'\n closeMarker.textContent = marker\n\n span.appendChild(openMarker)\n span.appendChild(styledContent)\n span.appendChild(closeMarker)\n fragment.appendChild(span)\n\n lastIndex = index + fullMatch.length\n }\n\n // Text after the last match\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/** Matches a `•` bullet glyph at the start of a line (optionally indented). */\nconst LIST_BULLET_PATTERN = /(^|\\n)([ \\t]*)•/g\n\n/**\n * Walks direct-child text nodes and wraps each line-leading `•` bullet glyph in\n * a `<span class=\"prompt-area-list-bullet\">` so CSS can size it up (the raw\n * U+2022 glyph renders much smaller than the surrounding text).\n *\n * Like {@link decorateMarkdownInEditor}, this is a DOM-only decoration: the span\n * is stripped by {@link normalizeEditorDOM} on every input cycle, so the `•`\n * stays a plain character in the segment model and is re-decorated each render.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateBulletsInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent?.includes('•')) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n LIST_BULLET_PATTERN.lastIndex = 0\n const bulletIndices: number[] = []\n let match: RegExpExecArray | null\n while ((match = LIST_BULLET_PATTERN.exec(text)) !== null) {\n bulletIndices.push(match.index + match[1].length + match[2].length)\n }\n\n if (bulletIndices.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const index of bulletIndices) {\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n const span = document.createElement('span')\n span.dataset.md = 'true'\n span.className = 'prompt-area-list-bullet'\n span.textContent = '•'\n fragment.appendChild(span)\n lastIndex = index + 1 // the bullet is a single character\n }\n\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/**\n * Matches the line-leading whitespace run of an indented list line (bullet or\n * numbered). The lookahead keeps the list prefix itself out of the capture, so\n * only the indentation is wrapped.\n */\nconst LIST_INDENT_PATTERN = /(^|\\n)([ \\t]+)(?=(?:[•\\-*] |\\d+\\. ))/g\n\n/**\n * Wraps each list line's leading indentation in an inline-block\n * `<span class=\"prompt-area-list-indent\">` sized per nesting level, so nested\n * items read with a wide, Notion-like indent instead of the raw 2-space gap.\n *\n * Like the other decorations this is display-only: the span keeps the original\n * whitespace as its textContent (so plain-text length and caret offsets are\n * unchanged) and is stripped by {@link normalizeEditorDOM} each input cycle.\n * Must run BEFORE the node-splitting passes ({@link decorateURLsInEditor},\n * {@link decorateMarkdownInEditor}, {@link decorateBulletsInEditor}) so every\n * direct-child text node is still a whole line — otherwise a mid-line split\n * fragment beginning with whitespace would let the `^` anchor false-match\n * non-line-leading whitespace.\n *\n * @returns Whether any decorations were applied\n */\nexport function decorateListIndentInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node)) textNodes.push(node)\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n LIST_INDENT_PATTERN.lastIndex = 0\n const runs: { start: number; end: number }[] = []\n let match: RegExpExecArray | null\n while ((match = LIST_INDENT_PATTERN.exec(text)) !== null) {\n const start = match.index + match[1].length\n runs.push({ start, end: start + match[2].length })\n }\n\n if (runs.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { start, end } of runs) {\n if (start > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)))\n }\n const whitespace = text.slice(start, end)\n const level = Math.floor(whitespace.length / 2)\n const span = document.createElement('span')\n span.dataset.md = 'true'\n span.className = 'prompt-area-list-indent'\n span.style.width = `calc(var(--prompt-area-indent-size, 1.5em) * ${level})`\n span.textContent = whitespace\n fragment.appendChild(span)\n lastIndex = end\n }\n\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/**\n * Applies every display-only decoration to the editor in one pass: URL links\n * always, plus markdown emphasis, list indentation, and list bullets when\n * markdown mode is on. Each decoration is stripped by {@link normalizeEditorDOM}\n * on the next input cycle and re-applied here, so the segment model is never\n * mutated.\n *\n * List indentation runs FIRST, while each direct-child text node is still a\n * whole line: the URL and markdown passes split text nodes mid-line, and a tail\n * fragment starting with whitespace would let the indent regex's `^` anchor\n * false-match non-line-leading whitespace (e.g. `see http://x 1. y`).\n */\nexport function decorateEditor(editor: HTMLElement, markdownEnabled: boolean): void {\n // Whole-line passes run FIRST, while each direct-child text node is still a\n // full line. The URL and markdown passes split text nodes mid-line, so a tail\n // fragment beginning with \"•\" would let the bullet regex's `^` anchor\n // false-match a mid-line separator (e.g. `**bold** • middle`).\n if (markdownEnabled) {\n decorateListIndentInEditor(editor)\n decorateBulletsInEditor(editor)\n }\n decorateURLsInEditor(editor)\n if (markdownEnabled) decorateMarkdownInEditor(editor)\n}\n\n// ---------------------------------------------------------------------------\n// Selection helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the first Range from the current window selection, or null if\n * there is no selection or it has no ranges.\n */\nexport function getSelectionRange(): Range | null {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n return sel.getRangeAt(0)\n}\n","/**\n * Cursor/selection utilities for the PromptArea contentEditable.\n *\n * Invariants:\n * - All functions are synchronous. Never return a promise or cross a microtask\n * boundary after a DOM mutation — that can cause the browser to fire\n * `selectionchange` and reset the caret.\n * - Never cache a `Selection` or `Range` across calls. Ranges become detached\n * after DOM mutations. Each function calls `window.getSelection()` or\n * `getSelectionRange()` fresh.\n * - Chip nodes are treated atomically via `isChipElement` — we never descend\n * into a contentEditable=false subtree when mapping offsets.\n */\nimport {\n chipNodeTextLength,\n getDirectChildContaining,\n getSelectionRange,\n indexOfChildNode,\n isBRElement,\n isChipElement,\n isHTMLElement,\n} from './dom-helpers.ts'\n\nexport type SavedCursor = {\n nodeIndex: number\n offset: number\n}\n\nexport function saveCursorPosition(editor: HTMLElement): SavedCursor | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const node = range.startContainer\n if (node === editor) {\n return { nodeIndex: range.startOffset, offset: 0 }\n }\n\n // Walk up to find the direct child of editor using type-safe helper\n const directChild = getDirectChildContaining(editor, node)\n if (!directChild) return null\n\n const nodeIndex = indexOfChildNode(editor, directChild)\n return { nodeIndex, offset: range.startOffset }\n}\n\nexport function restoreCursorPosition(editor: HTMLElement, saved: SavedCursor): void {\n const sel = window.getSelection()\n if (!sel) return\n\n const childNodes = editor.childNodes\n if (childNodes.length === 0) return\n\n const range = document.createRange()\n\n if (saved.nodeIndex >= childNodes.length) {\n const lastChild = childNodes[childNodes.length - 1]\n if (lastChild.nodeType === Node.TEXT_NODE) {\n range.setStart(lastChild, (lastChild.textContent ?? '').length)\n } else {\n range.setStartAfter(lastChild)\n }\n } else {\n const targetNode = childNodes[saved.nodeIndex]\n if (targetNode.nodeType === Node.TEXT_NODE) {\n const maxOffset = (targetNode.textContent ?? '').length\n range.setStart(targetNode, Math.min(saved.offset, maxOffset))\n } else {\n range.setStartAfter(targetNode)\n }\n }\n\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\nexport function getCursorOffset(editor: HTMLElement): number | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const preRange = document.createRange()\n preRange.selectNodeContents(editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n\n return getTextLengthInRange(preRange)\n}\n\n/**\n * Create a collapsed Range at the given plain-text offset inside the editor.\n * Returns null if the offset can't be mapped to a DOM position.\n */\nexport function createRangeAtOffset(editor: HTMLElement, targetOffset: number): Range | null {\n const pos = findDOMPosition(editor, targetOffset)\n if (!pos) return null\n\n const range = document.createRange()\n range.setStart(pos.node, pos.offset)\n range.collapse(true)\n return range\n}\n\nexport function setCursorAtOffset(editor: HTMLElement, targetOffset: number): void {\n const sel = window.getSelection()\n if (!sel) return\n\n const pos = findDOMPosition(editor, targetOffset)\n if (pos) {\n const range = document.createRange()\n range.setStart(pos.node, pos.offset)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n return\n }\n\n // Fallback: place cursor at end\n const range = document.createRange()\n range.selectNodeContents(editor)\n range.collapse(false)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\nexport function getTextLengthInRange(range: Range): number {\n const fragment = range.cloneContents()\n let length = 0\n\n const walk = (node: Node): void => {\n if (node.nodeType === Node.TEXT_NODE) {\n length += (node.textContent ?? '').length\n } else if (isChipElement(node)) {\n length += chipNodeTextLength(node)\n } else if (isHTMLElement(node) && node.tagName === 'BR') {\n if (node.dataset.sentinel) return // skip sentinel <br>\n length += 1\n } else if (isHTMLElement(node)) {\n node.childNodes.forEach(walk)\n }\n }\n\n fragment.childNodes.forEach(walk)\n return length\n}\n\n/**\n * Returns the start and end plain-text offsets of the current selection.\n * Returns null if there's no selection or it's outside the editor.\n */\nexport function getSelectionOffsets(editor: HTMLElement): { start: number; end: number } | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const startRange = document.createRange()\n startRange.selectNodeContents(editor)\n startRange.setEnd(range.startContainer, range.startOffset)\n const start = getTextLengthInRange(startRange)\n\n if (range.collapsed) return { start, end: start }\n\n const endRange = document.createRange()\n endRange.selectNodeContents(editor)\n endRange.setEnd(range.endContainer, range.endOffset)\n const end = getTextLengthInRange(endRange)\n\n return { start, end }\n}\n\n/**\n * Sets a (potentially non-collapsed) selection at the given plain-text offsets.\n * Used to restore selection after markdown wrap/unwrap operations.\n */\nexport function setSelectionAtOffsets(\n editor: HTMLElement,\n startOffset: number,\n endOffset: number,\n): void {\n const sel = window.getSelection()\n if (!sel) return\n\n if (startOffset === endOffset) {\n setCursorAtOffset(editor, startOffset)\n return\n }\n\n const startPos = findDOMPosition(editor, startOffset)\n const endPos = findDOMPosition(editor, endOffset)\n if (!startPos || !endPos) return\n\n const range = document.createRange()\n range.setStart(startPos.node, startPos.offset)\n range.setEnd(endPos.node, endPos.offset)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\n/**\n * Maps a plain-text offset to a DOM node + offset pair.\n * Recurses into decoration elements (markdown spans, URL anchors).\n */\nexport function findDOMPosition(\n container: HTMLElement,\n targetOffset: number,\n): { node: Node; offset: number } | null {\n let remaining = targetOffset\n\n for (let i = 0; i < container.childNodes.length; i++) {\n const child = container.childNodes[i]\n\n if (child.nodeType === Node.TEXT_NODE) {\n const len = (child.textContent ?? '').length\n if (remaining <= len) {\n return { node: child, offset: remaining }\n }\n remaining -= len\n } else if (isChipElement(child)) {\n const chipLen = chipNodeTextLength(child)\n if (remaining <= chipLen) {\n // Position after the chip element\n return { node: container, offset: i + 1 }\n }\n remaining -= chipLen\n } else if (isBRElement(child)) {\n if (child.dataset.sentinel) continue // skip sentinel <br>\n if (remaining <= 1) {\n return { node: container, offset: i + 1 }\n }\n remaining -= 1\n } else if (isHTMLElement(child)) {\n // Decoration element (markdown span, URL anchor) — recurse\n const textLen = (child.textContent ?? '').length\n if (remaining <= textLen) {\n const result = findDOMPosition(child, remaining)\n if (result) return result\n }\n remaining -= textLen\n }\n }\n\n // Fallback: end of container\n return { node: container, offset: container.childNodes.length }\n}\n","/**\n * Clipboard-related DOM utilities for the PromptArea component.\n * Handles serializing selections and inserting pasted segments at the cursor.\n *\n * Not merged into dom-helpers.ts to avoid overcrowding that file with\n * clipboard I/O concerns alongside its DOM traversal and chip-accessor helpers.\n */\nimport type { Segment, ChipSegment } from './types.ts'\nimport {\n chipNodeToSegment,\n getChipDisplay,\n getChipTrigger,\n getSelectionRange,\n isChipElement,\n isHTMLElement,\n} from './dom-helpers.ts'\nimport { mergeAdjacentTextSegments } from './prompt-area-engine.ts'\nimport { getTextLengthInRange } from './cursor-helpers.ts'\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Visitor callbacks for {@link walkFragmentNodes}. Each fires for the\n * corresponding node kind encountered during a depth-first walk.\n */\ntype FragmentVisitor = {\n /** A text node — receives its text content (may be empty). */\n onText: (text: string) => void\n /** A chip element (has `data-chip-trigger`). */\n onChip: (node: HTMLElement) => void\n /** A `<br>` line break. */\n onBreak: () => void\n}\n\n/**\n * Depth-first walk of a selection fragment, classifying each node as text,\n * chip, or line break and recursing into any other element (decoration spans,\n * anchors, browser-inserted wrappers).\n *\n * Both fragment serializers share this single traversal so they cannot drift\n * on which nodes count as chips/breaks or how nested decorations are unwrapped.\n */\nfunction walkFragmentNodes(fragment: DocumentFragment, visitor: FragmentVisitor): void {\n const walk = (node: Node): void => {\n if (node.nodeType === Node.TEXT_NODE) {\n visitor.onText(node.textContent ?? '')\n } else if (isChipElement(node)) {\n visitor.onChip(node)\n } else if (isHTMLElement(node) && node.tagName === 'BR') {\n visitor.onBreak()\n } else {\n node.childNodes.forEach(walk)\n }\n }\n\n fragment.childNodes.forEach(walk)\n}\n\n/**\n * Serializes a DocumentFragment (from selection) to plain text,\n * converting chip elements to their `trigger + displayText` form.\n */\nexport function serializeFragmentToPlainText(fragment: DocumentFragment): string {\n let text = ''\n\n walkFragmentNodes(fragment, {\n onText: (value) => {\n text += value\n },\n onChip: (node) => {\n text += (getChipTrigger(node) ?? '') + (getChipDisplay(node) ?? '')\n },\n onBreak: () => {\n text += '\\n'\n },\n })\n\n return text\n}\n\n/**\n * Serializes a DocumentFragment to an array of Segment objects,\n * preserving chip data for internal copy/paste.\n */\nexport function serializeFragmentToSegments(fragment: DocumentFragment): Segment[] {\n const segments: Segment[] = []\n\n walkFragmentNodes(fragment, {\n onText: (value) => {\n if (value) segments.push({ type: 'text', text: value })\n },\n onChip: (node) => {\n const chip = chipNodeToSegment(node)\n if (chip) segments.push(chip)\n },\n onBreak: () => {\n segments.push({ type: 'text', text: '\\n' })\n },\n })\n\n return segments\n}\n\n/**\n * Parses segment JSON from the clipboard. Returns null if invalid.\n */\nexport function parseSegmentsFromClipboard(json: string): Segment[] | null {\n try {\n const parsed: unknown = JSON.parse(json)\n if (!Array.isArray(parsed)) return null\n\n const segments: Segment[] = []\n for (const item of parsed) {\n if (!isRecord(item)) return null\n\n if (item.type === 'text' && typeof item.text === 'string') {\n segments.push({ type: 'text', text: item.text })\n } else if (\n item.type === 'chip' &&\n typeof item.trigger === 'string' &&\n typeof item.value === 'string' &&\n typeof item.displayText === 'string'\n ) {\n const chip: ChipSegment = {\n type: 'chip',\n trigger: item.trigger,\n value: item.value,\n displayText: item.displayText,\n ...(item.data !== undefined ? { data: item.data } : {}),\n ...(item.autoResolved ? { autoResolved: true } : {}),\n }\n segments.push(chip)\n } else {\n return null\n }\n }\n\n return segments\n } catch {\n return null\n }\n}\n\n/**\n * Inserts pasted segments at the current cursor position within existing segments.\n * Splits any text segment that straddles the cursor so the pasted content lands\n * exactly at the cursor and nothing before or after is lost.\n */\nexport function insertSegmentsAtCursor(\n currentSegments: Segment[],\n pastedSegments: Segment[],\n editor: HTMLElement,\n): Segment[] {\n const range = getSelectionRange()\n if (!range) return [...currentSegments, ...pastedSegments]\n\n const preRange = document.createRange()\n preRange.selectNodeContents(editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n const cursorOffset = getTextLengthInRange(preRange)\n\n const result: Segment[] = []\n let offset = 0\n let inserted = false\n\n const insertOnce = (): void => {\n if (!inserted) {\n result.push(...pastedSegments)\n inserted = true\n }\n }\n\n for (const seg of currentSegments) {\n if (seg.type === 'chip') {\n const chipLen = seg.trigger.length + seg.displayText.length\n if (offset >= cursorOffset) insertOnce()\n result.push(seg)\n offset += chipLen\n continue\n }\n\n const segEnd = offset + seg.text.length\n if (segEnd <= cursorOffset) {\n // Entirely before the cursor\n result.push(seg)\n } else if (offset >= cursorOffset) {\n // Entirely after the cursor\n insertOnce()\n result.push(seg)\n } else {\n // Cursor falls inside this text segment — split it.\n const splitAt = cursorOffset - offset\n const before = seg.text.slice(0, splitAt)\n const after = seg.text.slice(splitAt)\n if (before) result.push({ type: 'text', text: before })\n insertOnce()\n if (after) result.push({ type: 'text', text: after })\n }\n offset = segEnd\n }\n\n insertOnce()\n return mergeAdjacentTextSegments(result)\n}\n","/**\n * Hand-rolled, dependency-free HTML -> Markdown converter.\n *\n * Used by the paste handler: when the editor is in markdown mode and the\n * clipboard carries rich `text/html` (web pages, Notion, Google Docs, GitHub,\n * Slack, etc.), we convert it to markdown SOURCE text so the paste keeps its\n * formatting. The resulting string flows through the same insertion path as a\n * plain-text paste, and the editor's inline decorators render `*`/`**`/`***`\n * and bare URLs automatically.\n *\n * Design constraints (see .size-limit.json): no runtime deps. Parsing uses the\n * ambient `DOMParser`, walking is a small recursive switch. Type-safe: no\n * `any`, DOM narrowed via the guards in `dom-helpers.ts`.\n */\nimport { isHTMLElement, isTextNode } from './dom-helpers.ts'\n\n// ---------------------------------------------------------------------------\n// Inline style / emphasis detection\n// ---------------------------------------------------------------------------\n\n/** Reads a single declaration value from an inline `style` attribute string. */\nfunction getStyleValue(style: string, prop: string): string {\n const match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style)\n return match ? match[1].trim().toLowerCase() : ''\n}\n\n/** Whether a CSS font-weight value is bold (`bold`, `bolder`, or >= 600). */\nfunction isBoldWeight(value: string): boolean {\n if (value === 'bold' || value === 'bolder') return true\n const numeric = Number.parseInt(value, 10)\n return !Number.isNaN(numeric) && numeric >= 600\n}\n\n/**\n * Computes the markdown emphasis markers for an element from BOTH its tag and\n * its inline style. Google Docs emits `<span style=\"font-weight:700\">` rather\n * than `<b>`, and wraps everything in `<b style=\"font-weight:normal\">`, so an\n * explicit `font-weight`/`font-style` always wins over the tag name.\n */\nfunction inlineEmphasis(node: HTMLElement): { prefix: string; suffix: string } {\n const tag = node.tagName\n const style = node.getAttribute('style') ?? ''\n\n const weight = getStyleValue(style, 'font-weight')\n const bold = weight ? isBoldWeight(weight) : tag === 'B' || tag === 'STRONG'\n\n const fontStyle = getStyleValue(style, 'font-style')\n const italic = fontStyle\n ? fontStyle === 'italic' || fontStyle === 'oblique'\n : tag === 'I' || tag === 'EM'\n\n const decoration = getStyleValue(style, 'text-decoration')\n const strike =\n tag === 'S' || tag === 'DEL' || tag === 'STRIKE' || decoration.includes('line-through')\n\n const prefix = (strike ? '~~' : '') + (bold ? '**' : '') + (italic ? '*' : '')\n const suffix = (italic ? '*' : '') + (bold ? '**' : '') + (strike ? '~~' : '')\n return { prefix, suffix }\n}\n\n// ---------------------------------------------------------------------------\n// Text handling\n// ---------------------------------------------------------------------------\n\n/** Collapses HTML whitespace runs (incl. ` ` -> U+00A0) to single spaces. */\nfunction collapseWhitespace(text: string): string {\n return text.replace(/\\s+/g, ' ')\n}\n\n/** Escapes literal `*` from HTML text so prose isn't re-read as emphasis. */\nfunction escapeText(text: string): string {\n return text.replace(/\\*/g, '\\\\*')\n}\n\n// ---------------------------------------------------------------------------\n// Block serializers\n// ---------------------------------------------------------------------------\n\n/** Derives a fenced-block language from `class=\"language-ts\"` or `lang=\"ts\"`. */\nfunction detectCodeLang(pre: HTMLElement): string {\n const code = pre.querySelector('code')\n const classNames = `${pre.className} ${code?.className ?? ''}`\n const fromClass = /(?:language|lang)-([\\w-]+)/.exec(classNames)\n if (fromClass) return fromClass[1]\n return pre.getAttribute('lang') ?? code?.getAttribute('lang') ?? ''\n}\n\nfunction serializePre(pre: HTMLElement): string {\n const lang = detectCodeLang(pre)\n const raw = (pre.textContent ?? '').replace(/\\n$/, '')\n return `\\n\\n\\`\\`\\`${lang}\\n${raw}\\n\\`\\`\\`\\n\\n`\n}\n\nfunction serializeInlineCode(node: HTMLElement): string {\n const content = node.textContent ?? ''\n if (content.includes('`')) return `\\`\\` ${content} \\`\\``\n return `\\`${content}\\``\n}\n\n/** http(s)/mailto only; drops `#`, empty, and `javascript:` hrefs. */\nfunction isSafeHref(href: string): boolean {\n return /^(https?:|mailto:)/i.test(href)\n}\n\nfunction serializeAnchor(node: HTMLElement, depth: number): string {\n const href = node.getAttribute('href') ?? ''\n const label = serializeChildren(node, depth).trim()\n if (!isSafeHref(href)) return label\n if (!label || label === href) return href\n return `[${label}](${href})`\n}\n\nfunction serializeImage(node: HTMLElement): string {\n const src = node.getAttribute('src') ?? ''\n // Gate the src through the same allow-list as anchors: only http(s)/mailto\n // survive, so a `javascript:`/`vbscript:`/`data:` src never reaches the\n // emitted markdown (defense-in-depth for consumers that render it as HTML).\n if (!src || !isSafeHref(src)) return ''\n return ``\n}\n\nfunction serializeBlockquote(node: HTMLElement, depth: number): string {\n const inner = serializeChildren(node, depth).trim()\n const quoted = inner\n .split('\\n')\n .map((line) => (line ? `> ${line}` : '>'))\n .join('\\n')\n return `\\n\\n${quoted}\\n\\n`\n}\n\n/**\n * Serializes a `<ul>`/`<ol>` at nesting `depth` (0 = top level). Each `<li>`'s\n * own inline content becomes the marker line; a nested `<ul>`/`<ol>` child is\n * serialized at `depth + 1` and appended indented below its parent item.\n */\nfunction serializeList(list: HTMLElement, depth: number): string {\n const ordered = list.tagName === 'OL'\n const start = Number.parseInt(list.getAttribute('start') ?? '', 10)\n let index = Number.isNaN(start) ? 1 : start\n const indent = ' '.repeat(depth)\n const lines: string[] = []\n\n for (const child of Array.from(list.childNodes)) {\n if (!isHTMLElement(child) || child.tagName !== 'LI') continue\n\n const marker = ordered ? `${index}. ` : '- '\n index++\n\n let label = ''\n let nested = ''\n for (const liChild of Array.from(child.childNodes)) {\n if (isHTMLElement(liChild) && (liChild.tagName === 'UL' || liChild.tagName === 'OL')) {\n nested += `\\n${serializeList(liChild, depth + 1)}`\n } else {\n label += serializeNode(liChild, depth)\n }\n }\n lines.push(`${indent}${marker}${label.trim()}${nested}`)\n }\n\n return lines.join('\\n')\n}\n\nfunction serializeTable(table: HTMLElement, depth: number): string {\n const rows = Array.from(table.querySelectorAll('tr'))\n if (rows.length === 0) return ''\n\n const cells = rows.map((row) =>\n Array.from(row.children)\n .filter((cell) => cell.tagName === 'TD' || cell.tagName === 'TH')\n .map((cell) =>\n serializeChildren(cell, depth).replace(/\\n+/g, ' ').replace(/\\|/g, '\\\\|').trim(),\n ),\n )\n\n const header = cells[0]\n const separator = header.map(() => '---')\n const toRow = (row: string[]): string => `| ${row.join(' | ')} |`\n\n return [toRow(header), toRow(separator), ...cells.slice(1).map(toRow)].join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Recursive walker\n// ---------------------------------------------------------------------------\n\nfunction serializeChildren(node: Node, depth: number): string {\n let out = ''\n node.childNodes.forEach((child) => {\n out += serializeNode(child, depth)\n })\n return out\n}\n\nfunction serializeNode(node: Node, depth: number): string {\n if (isTextNode(node)) return escapeText(collapseWhitespace(node.textContent ?? ''))\n if (!isHTMLElement(node)) return ''\n\n const tag = node.tagName\n switch (tag) {\n case 'SCRIPT':\n case 'STYLE':\n case 'NOSCRIPT':\n case 'HEAD':\n case 'TITLE':\n return ''\n case 'BR':\n return '\\n'\n case 'HR':\n return '\\n\\n---\\n\\n'\n case 'H1':\n case 'H2':\n case 'H3':\n case 'H4':\n case 'H5':\n case 'H6':\n return `\\n\\n${'#'.repeat(Number(tag[1]))} ${serializeChildren(node, depth).trim()}\\n\\n`\n case 'P':\n return `\\n\\n${serializeChildren(node, depth).trim()}\\n\\n`\n case 'DIV':\n return `\\n${serializeChildren(node, depth).trim()}\\n`\n case 'BLOCKQUOTE':\n return serializeBlockquote(node, depth)\n case 'UL':\n case 'OL':\n return `\\n\\n${serializeList(node, depth)}\\n\\n`\n case 'LI':\n // A stray <li> outside a list wrapper — emit its content as a line.\n return `${serializeChildren(node, depth).trim()}\\n`\n case 'PRE':\n return serializePre(node)\n case 'CODE':\n // Inline code only: <pre> handles its own <code> via textContent.\n return serializeInlineCode(node)\n case 'A':\n return serializeAnchor(node, depth)\n case 'IMG':\n return serializeImage(node)\n case 'TABLE':\n return `\\n\\n${serializeTable(node, depth)}\\n\\n`\n default: {\n // Inline emphasis (B/STRONG/I/EM/S/DEL + styled SPAN/FONT) and the\n // \"span soup\" unwrap case both resolve here: emphasis markers when the\n // tag or inline style is meaningful, otherwise a bare unwrap.\n const { prefix, suffix } = inlineEmphasis(node)\n return prefix + serializeChildren(node, depth) + suffix\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Output normalization\n// ---------------------------------------------------------------------------\n\n/** Trims trailing spaces and caps consecutive blank lines at one. */\nfunction normalizeOutput(markdown: string): string {\n return markdown\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n[ \\t]*\\n[ \\t\\n]*/g, '\\n\\n')\n .trim()\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an HTML string to markdown source text. Returns '' for empty or\n * body-less input. Block markdown (headings, lists, quotes, fences, tables,\n * links) is emitted as literal markdown text — that is the editor's intended\n * display; only `*`/`**`/`***` and bare URLs get visually decorated inline.\n */\nexport function htmlToMarkdown(html: string): string {\n if (!html) return ''\n const doc = new DOMParser().parseFromString(html, 'text/html')\n if (!doc.body) return ''\n return normalizeOutput(serializeChildren(doc.body, 0))\n}\n","'use client'\n\nimport { useCallback, useRef } from 'react'\nimport type { Segment, ChipSegment, TriggerConfig } from './types.ts'\nimport { resolveTriggersInSegments } from './prompt-area-engine.ts'\nimport { normalizeEditorDOM, safeJsonStringify, getSelectionRange } from './dom-helpers.ts'\nimport {\n serializeFragmentToPlainText,\n serializeFragmentToSegments,\n parseSegmentsFromClipboard,\n insertSegmentsAtCursor,\n} from './clipboard-helpers.ts'\nimport { htmlToMarkdown } from './html-to-markdown.ts'\nimport {\n normalizeListPrefixText,\n renumberOrderedListLines,\n hasOrderedListRun,\n} from './prompt-area-list-ops.ts'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype EventHandlerDeps = {\n editorRef: React.RefObject<HTMLDivElement | null>\n readSegmentsFromDOM: () => Segment[]\n onChange: (segments: Segment[]) => void\n renderSegmentsToDOM: (segments: Segment[]) => void\n runTriggerDetection: () => void\n dismissTrigger: () => void\n triggers: TriggerConfig[]\n /** When true, rich `text/html` on the clipboard is converted to markdown. */\n markdownEnabled: boolean\n /** When true, pasted list markers (\"- \") are normalized to the \"•\" glyph. */\n normalizeBullets: boolean\n onPaste?: (data: { segments: Segment[]; source: 'internal' | 'external' }) => void\n onUndo?: (segments: Segment[]) => void\n onRedo?: (segments: Segment[]) => void\n onChipAdd?: (chip: ChipSegment) => void\n onImagePaste?: (file: File) => void\n onRawPaste?: (e: React.ClipboardEvent<HTMLDivElement>) => void\n}\n\ntype PromptAreaEventHandlers = {\n handlePaste: (e: React.ClipboardEvent<HTMLDivElement>) => void\n handleCopy: (e: React.ClipboardEvent<HTMLDivElement>) => void\n handleCut: (e: React.ClipboardEvent<HTMLDivElement>) => void\n handleDrop: (e: React.DragEvent<HTMLDivElement>) => void\n handleDragOver: (e: React.DragEvent<HTMLDivElement>) => void\n handleCompositionStart: () => void\n handleCompositionEnd: () => void\n handleBlur: () => void\n handleKeyDownForUndoRedo: (e: React.KeyboardEvent<HTMLDivElement>) => boolean\n pushUndo: (segments: Segment[]) => void\n resetUndoHistory: () => void\n isComposing: React.RefObject<boolean>\n}\n\n// ---------------------------------------------------------------------------\n// Undo/Redo Stack\n// ---------------------------------------------------------------------------\n\nconst MAX_UNDO_HISTORY = 100\n\n/** Delay before dismissing trigger on blur, so popover clicks register first */\nexport const BLUR_DELAY_MS = 150\n\ntype UndoState = {\n undoStack: Segment[][]\n redoStack: Segment[][]\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Encapsulates all edge-case event handlers for the prompt area component:\n * paste, copy, cut, drag/drop, IME composition, blur, and undo/redo.\n */\nexport function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHandlers {\n const {\n editorRef,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n runTriggerDetection,\n dismissTrigger,\n triggers,\n markdownEnabled,\n normalizeBullets,\n onPaste: onPasteCallback,\n onUndo,\n onRedo,\n onChipAdd,\n onImagePaste,\n onRawPaste,\n } = deps\n\n const isComposing = useRef(false)\n\n // -----------------------------------------------------------------------\n // Undo/redo stack (MAX_UNDO_HISTORY entries; clears redo on new push).\n // Invariants: stacks live in refs (not useState) so pushUndo /\n // resetUndoHistory / handleKeyDownForUndoRedo keep stable identity.\n // Destabilizing this would re-create handleInput / handleKeyDown /\n // imperative handle on every render and silently regress IME + debounced\n // undo in the parent hook.\n // -----------------------------------------------------------------------\n const undoState = useRef<UndoState>({ undoStack: [], redoStack: [] })\n\n const pushUndo = useCallback((segments: Segment[]) => {\n const state = undoState.current\n state.undoStack.push(segments)\n if (state.undoStack.length > MAX_UNDO_HISTORY) {\n state.undoStack.shift()\n }\n // Clear redo stack on new change\n state.redoStack = []\n }, [])\n\n const resetUndoHistory = useCallback(() => {\n undoState.current = { undoStack: [], redoStack: [] }\n }, [])\n\n // -----------------------------------------------------------------------\n // Paste: strip HTML, insert plain text only\n // -----------------------------------------------------------------------\n\n const handlePaste = useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n // Let a consumer take over the paste entirely (e.g. divert large text or\n // arbitrary files to an upload pipeline) by calling preventDefault().\n onRawPaste?.(e)\n if (e.defaultPrevented) return\n e.preventDefault()\n\n const editor = editorRef.current\n if (!editor) return\n\n // Check for image files in clipboard before processing text\n // Some browsers/OSes provide pasted images via `items` instead of `files` (e.g. screenshots)\n const imageFile =\n Array.from(e.clipboardData.files).find((f) => f.type.startsWith('image/')) ??\n (() => {\n const item = Array.from(e.clipboardData.items).find((i) => i.type.startsWith('image/'))\n return item?.getAsFile() ?? null\n })()\n if (imageFile) {\n onImagePaste?.(imageFile)\n return\n }\n\n // Record undo snapshot\n const currentSegments = readSegmentsFromDOM()\n pushUndo(currentSegments)\n\n // Check for internal segment data (copy/paste within the editor)\n const segmentJson = e.clipboardData.getData('text/prompt-area-segments')\n if (segmentJson) {\n const parsed = parseSegmentsFromClipboard(segmentJson)\n if (parsed && parsed.length > 0) {\n // Insert the copied segments at cursor position\n const range = getSelectionRange()\n if (!range) return\n\n range.deleteContents()\n\n // Merge pasted segments into current segments at cursor position\n const beforePaste = readSegmentsFromDOM()\n const merged = insertSegmentsAtCursor(beforePaste, parsed, editor)\n onChange(merged)\n renderSegmentsToDOM(merged)\n\n // Notify: internal paste with chip data preserved\n onPasteCallback?.({ segments: merged, source: 'internal' })\n for (const seg of parsed) {\n if (seg.type === 'chip') {\n onChipAdd?.(seg)\n }\n }\n\n runTriggerDetection()\n return\n }\n }\n\n // When markdown mode is on, prefer the richest clipboard flavor:\n // 1. text/markdown — some apps (e.g. Slack) hand out markdown directly,\n // preserving nested lists that their text/plain flattens.\n // 2. text/html — convert web/Notion/Docs/GitHub HTML to markdown.\n // Otherwise (markdown off, or neither present) fall back to plain text.\n let text = ''\n if (markdownEnabled) {\n text = e.clipboardData.getData('text/markdown')\n if (text) {\n // Slack over-escapes inert punctuation (e.g. `\\(` `\\)`); unescape\n // parentheses so the source reads cleanly. They carry no markdown\n // meaning, unlike `\\*` / `\\.` / `\\-` which are left intact.\n text = text.replace(/\\\\([()])/g, '$1')\n } else {\n const html = e.clipboardData.getData('text/html')\n // A converter failure (e.g. stack overflow on pathologically deep\n // nesting) must not drop the paste — leave text empty so the\n // text/plain fallback below still runs.\n if (html) {\n try {\n text = htmlToMarkdown(html)\n } catch {\n text = ''\n }\n }\n }\n }\n if (!text) text = e.clipboardData.getData('text/plain')\n if (!text) return\n\n // Normalize pasted list markers (\"- \" → \"•\") so pasted bullets match\n // typed input. Applies to both the HTML→markdown and plain-text paths.\n if (markdownEnabled && normalizeBullets) {\n text = normalizeListPrefixText(text, true)\n }\n\n // Rebuild ordered-list numbering in the pasted block so a copied list with\n // stale numbers lands sequential. Gated on `hasOrderedListRun` so incidental\n // numeric-leading prose (e.g. `1985. Born / 2020. Died`) is left untouched.\n // Done at the raw-string level (the caret collapses to end-of-content after\n // insertion, so no offset remap needed).\n if (markdownEnabled && hasOrderedListRun(text)) {\n text = renumberOrderedListLines(text).text\n }\n\n // Insert plain text at cursor position using Selection API\n const range = getSelectionRange()\n if (!range) return\n\n range.deleteContents()\n\n // Handle multi-line paste: split into lines with BR elements\n const lines = text.split('\\n')\n const fragment = document.createDocumentFragment()\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i]) {\n fragment.appendChild(document.createTextNode(lines[i]))\n }\n if (i < lines.length - 1) {\n fragment.appendChild(document.createElement('br'))\n }\n }\n\n range.insertNode(fragment)\n\n // Move cursor to end of pasted content\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n\n // Normalize DOM, sync model, detect triggers\n normalizeEditorDOM(editor)\n const newSegments = readSegmentsFromDOM()\n\n // Auto-resolve trigger patterns in pasted text (e.g., #readme -> chip)\n const resolvedSegments = resolveTriggersInSegments(newSegments, triggers)\n\n if (resolvedSegments !== newSegments) {\n onChange(resolvedSegments)\n renderSegmentsToDOM(resolvedSegments)\n\n // Notify about auto-resolved chips from pasted text\n for (const seg of resolvedSegments) {\n if (\n seg.type === 'chip' &&\n !newSegments.some(\n (s) =>\n s.type === 'chip' &&\n s.trigger === seg.trigger &&\n s.value === seg.value &&\n s.displayText === seg.displayText,\n )\n ) {\n onChipAdd?.(seg)\n }\n }\n } else {\n onChange(newSegments)\n }\n\n onPasteCallback?.({ segments: resolvedSegments, source: 'external' })\n runTriggerDetection()\n },\n [\n editorRef,\n readSegmentsFromDOM,\n onChange,\n pushUndo,\n runTriggerDetection,\n renderSegmentsToDOM,\n triggers,\n markdownEnabled,\n normalizeBullets,\n onPasteCallback,\n onChipAdd,\n onImagePaste,\n onRawPaste,\n ],\n )\n\n // -----------------------------------------------------------------------\n // Copy: serialize chips into plain text\n // -----------------------------------------------------------------------\n\n const handleCopy = useCallback((e: React.ClipboardEvent<HTMLDivElement>) => {\n e.preventDefault()\n\n const range = getSelectionRange()\n if (!range) return\n\n const fragment = range.cloneContents()\n\n // Walk fragment and serialize, converting chips to their text representation\n const plainText = serializeFragmentToPlainText(fragment)\n e.clipboardData.setData('text/plain', plainText)\n\n // Also serialize chip segments as JSON for internal paste\n const fragmentSegments = serializeFragmentToSegments(fragment)\n const hasChips = fragmentSegments.some((s) => s.type === 'chip')\n if (hasChips) {\n const json = safeJsonStringify(fragmentSegments)\n if (json) {\n e.clipboardData.setData('text/prompt-area-segments', json)\n }\n }\n }, [])\n\n // -----------------------------------------------------------------------\n // Cut: copy + delete\n // -----------------------------------------------------------------------\n\n const handleCut = useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n // First, do the copy\n handleCopy(e)\n\n // Then delete the selection\n const range = getSelectionRange()\n if (!range) return\n\n const currentSegments = readSegmentsFromDOM()\n pushUndo(currentSegments)\n\n range.deleteContents()\n\n const editor = editorRef.current\n if (editor) {\n normalizeEditorDOM(editor)\n }\n\n const newSegments = readSegmentsFromDOM()\n onChange(newSegments)\n runTriggerDetection()\n },\n [handleCopy, editorRef, readSegmentsFromDOM, onChange, pushUndo, runTriggerDetection],\n )\n\n // -----------------------------------------------------------------------\n // Drag & Drop: prevent to avoid unpredictable DOM mutations\n // -----------------------------------------------------------------------\n\n const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n }, [])\n\n const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n }, [])\n\n // -----------------------------------------------------------------------\n // IME Composition: track state, defer trigger detection\n // -----------------------------------------------------------------------\n\n const handleCompositionStart = useCallback(() => {\n isComposing.current = true\n }, [])\n\n const handleCompositionEnd = useCallback(() => {\n isComposing.current = false\n // Run trigger detection after composition ends\n runTriggerDetection()\n }, [runTriggerDetection])\n\n // -----------------------------------------------------------------------\n // Blur: dismiss trigger dropdown with delay (so popover clicks work)\n // -----------------------------------------------------------------------\n\n const handleBlur = useCallback(() => {\n setTimeout(() => {\n const editor = editorRef.current\n if (!editor) return\n\n // Only dismiss if focus didn't move to an element within the editor container\n const activeEl = document.activeElement\n if (activeEl && editor.parentElement?.contains(activeEl)) return\n\n dismissTrigger()\n }, BLUR_DELAY_MS)\n }, [editorRef, dismissTrigger])\n\n // -----------------------------------------------------------------------\n // Undo/Redo: intercept Ctrl+Z / Ctrl+Shift+Z\n // -----------------------------------------------------------------------\n\n const handleKeyDownForUndoRedo = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>): boolean => {\n const isMeta = e.metaKey || e.ctrlKey\n\n if (!isMeta || e.key !== 'z') return false\n\n e.preventDefault()\n const state = undoState.current\n\n if (e.shiftKey) {\n // Redo: Ctrl+Shift+Z\n if (state.redoStack.length === 0) return true\n\n const segments = state.redoStack.pop()\n if (!segments) return true\n\n const current = readSegmentsFromDOM()\n state.undoStack.push(current)\n\n onChange(segments)\n renderSegmentsToDOM(segments)\n onRedo?.(segments)\n } else {\n // Undo: Ctrl+Z\n if (state.undoStack.length === 0) return true\n\n const segments = state.undoStack.pop()\n if (!segments) return true\n\n const current = readSegmentsFromDOM()\n state.redoStack.push(current)\n\n onChange(segments)\n renderSegmentsToDOM(segments)\n onUndo?.(segments)\n }\n\n return true\n },\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, onUndo, onRedo],\n )\n\n return {\n handlePaste,\n handleCopy,\n handleCut,\n handleDrop,\n handleDragOver,\n handleCompositionStart,\n handleCompositionEnd,\n handleBlur,\n handleKeyDownForUndoRedo,\n pushUndo,\n resetUndoHistory,\n isComposing,\n }\n}\n","'use client'\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { TriggerConfig, TriggerSuggestion } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype UseTriggerSearchReturn = {\n suggestions: TriggerSuggestion[]\n suggestionsLoading: boolean\n suggestionsError: string | null\n /** Run a search for the given query using the trigger's onSearch config. */\n search: (query: string, config: TriggerConfig) => void\n /** Cancel any in-flight search and reset state. */\n reset: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Manages async trigger search lifecycle: debouncing, AbortController\n * cancellation, race-condition prevention, loading/error state.\n *\n * Extracted from `usePromptArea` so the main hook stays focused on\n * editing concerns while this hook owns the data-fetching side.\n */\nexport function useTriggerSearch(): UseTriggerSearchReturn {\n const [suggestions, setSuggestions] = useState<TriggerSuggestion[]>([])\n const [suggestionsLoading, setSuggestionsLoading] = useState(false)\n const [suggestionsError, setSuggestionsError] = useState<string | null>(null)\n\n // Version counter – belt-and-suspenders alongside AbortController\n const searchVersion = useRef(0)\n // AbortController for cancelling in-flight async searches\n const abortController = useRef<AbortController | null>(null)\n // Debounce timer for search queries\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const reset = useCallback(() => {\n abortController.current?.abort()\n if (debounceTimer.current) clearTimeout(debounceTimer.current)\n setSuggestions([])\n setSuggestionsLoading(false)\n setSuggestionsError(null)\n }, [])\n\n const search = useCallback((query: string, config: TriggerConfig) => {\n if (!config.onSearch) return\n\n // Cancel any previous in-flight request and pending debounce\n abortController.current?.abort()\n if (debounceTimer.current) clearTimeout(debounceTimer.current)\n\n setSuggestionsLoading(true)\n setSuggestionsError(null)\n searchVersion.current++\n const version = searchVersion.current\n\n const controller = new AbortController()\n abortController.current = controller\n const { onSearch, onSearchError, searchDebounceMs } = config\n\n const executeSearch = () => {\n const result = onSearch(query, { signal: controller.signal })\n\n if (result instanceof Promise) {\n void result.then(\n (items) => {\n if (controller.signal.aborted || searchVersion.current !== version) return\n setSuggestions(items)\n setSuggestionsLoading(false)\n },\n (error: unknown) => {\n if (controller.signal.aborted || searchVersion.current !== version) return\n // Silently ignore AbortError (expected when superseded)\n if (error instanceof DOMException && error.name === 'AbortError') return\n setSuggestionsError(error instanceof Error ? error.message : 'Search failed')\n setSuggestionsLoading(false)\n onSearchError?.(error)\n },\n )\n } else {\n setSuggestions(result)\n setSuggestionsLoading(false)\n }\n }\n\n // Debounce subsequent searches but fire immediately for the initial empty query\n if (searchDebounceMs && searchDebounceMs > 0 && query.length > 0) {\n debounceTimer.current = setTimeout(executeSearch, searchDebounceMs)\n } else {\n executeSearch()\n }\n }, [])\n\n // Clean up on unmount\n useEffect(() => {\n return () => {\n abortController.current?.abort()\n if (debounceTimer.current) clearTimeout(debounceTimer.current)\n }\n }, [])\n\n return {\n suggestions,\n suggestionsLoading,\n suggestionsError,\n search,\n reset,\n }\n}\n","'use client'\n\nimport { cn } from '../../lib/utils.ts'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type {\n Segment,\n TriggerConfig,\n ActiveTrigger,\n TriggerSuggestion,\n ChipSegment,\n PromptAreaHandle,\n} from './types.ts'\nimport {\n detectActiveTrigger,\n isValidTriggerPosition,\n segmentsToPlainText,\n plainTextToSegments,\n segmentsEqual,\n resolveChip,\n resolveText,\n removeChipAtIndex,\n revertChipAtIndex,\n replaceTextRange,\n toggleMarkdownWrap,\n truncateSegmentsToLength,\n} from './prompt-area-engine.ts'\nimport {\n getListContext,\n autoFormatListPrefix,\n insertListContinuation,\n indentListItem,\n outdentListItem,\n removeListPrefix,\n normalizeListPrefixes,\n renumberOrderedListSegments,\n remapOffset,\n hasOrderedListRun,\n} from './prompt-area-list-ops.ts'\nimport {\n isHTMLElement,\n isChipElement,\n isLinkElement,\n isBRElement,\n chipNodeToSegment,\n getChipAutoResolved,\n getDirectChildContaining,\n indexOfChildNode,\n domChildIndexToSegmentIndex,\n normalizeEditorDOM,\n decorateEditor,\n safeJsonStringify,\n getSelectionRange,\n} from './dom-helpers.ts'\nimport {\n saveCursorPosition,\n restoreCursorPosition,\n getCursorOffset,\n setCursorAtOffset,\n createRangeAtOffset,\n getSelectionOffsets,\n setSelectionAtOffsets,\n} from './cursor-helpers.ts'\nimport { usePromptAreaEvents } from './use-prompt-area-events.ts'\nimport { useTriggerSearch } from './use-trigger-search.ts'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype UsePromptAreaOptions = {\n value: Segment[]\n onChange: (segments: Segment[]) => void\n triggers?: TriggerConfig[]\n disabled?: boolean\n onSubmit?: (segments: Segment[]) => void\n onEscape?: () => void\n onChipClick?: (chip: ChipSegment) => void\n onChipAdd?: (chip: ChipSegment) => void\n onChipDelete?: (chip: ChipSegment) => void\n onLinkClick?: (url: string) => void\n onPaste?: (data: { segments: Segment[]; source: 'internal' | 'external' }) => void\n onRawPaste?: (e: React.ClipboardEvent<HTMLDivElement>) => void\n onUndo?: (segments: Segment[]) => void\n onRedo?: (segments: Segment[]) => void\n onImagePaste?: (file: File) => void\n markdown?: boolean\n normalizeBullets?: boolean\n submitOnEnter?: boolean\n maxLength?: number\n}\n\ntype UsePromptAreaReturn = {\n editorRef: React.RefObject<HTMLDivElement | null>\n activeTrigger: ActiveTrigger | null\n suggestions: TriggerSuggestion[]\n suggestionsLoading: boolean\n suggestionsError: string | null\n selectedSuggestionIndex: number\n handleInput: () => void\n handleKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void\n handleClick: (e: React.MouseEvent<HTMLDivElement>) => void\n handleMouseDown: (e: React.MouseEvent<HTMLDivElement>) => void\n selectSuggestion: (suggestion: TriggerSuggestion) => void\n dismissTrigger: () => void\n handle: PromptAreaHandle\n triggerRect: DOMRect | null\n eventHandlers: {\n onPaste: (e: React.ClipboardEvent<HTMLDivElement>) => void\n onCopy: (e: React.ClipboardEvent<HTMLDivElement>) => void\n onCut: (e: React.ClipboardEvent<HTMLDivElement>) => void\n onDrop: (e: React.DragEvent<HTMLDivElement>) => void\n onDragOver: (e: React.DragEvent<HTMLDivElement>) => void\n onCompositionStart: () => void\n onCompositionEnd: () => void\n onBlur: () => void\n }\n}\n\n/** Debounce interval for grouping typed characters into a single undo snapshot */\nconst UNDO_DEBOUNCE_MS = 300\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function usePromptArea({\n value,\n onChange,\n triggers = [],\n disabled = false,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onLinkClick,\n onPaste,\n onRawPaste,\n onUndo,\n onRedo,\n onImagePaste,\n markdown: markdownEnabled = true,\n normalizeBullets = true,\n submitOnEnter = true,\n maxLength,\n}: UsePromptAreaOptions): UsePromptAreaReturn {\n const editorRef = useRef<HTMLDivElement | null>(null)\n const [activeTrigger, setActiveTrigger] = useState<ActiveTrigger | null>(null)\n const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(0)\n const [triggerRect, setTriggerRect] = useState<DOMRect | null>(null)\n\n // Chip whose dropdown was reopened via `reopenOnChipClick`. While set, the\n // active dropdown edits this chip in place instead of resolving typed text.\n // Cleared on dismiss and by any fresh trigger detection (typing).\n //\n // `segIndex` is the segment index at CLICK time (computed from the live DOM,\n // so it's always accurate then) — deliberately not the DOM node itself,\n // which can detach if `renderSegmentsToDOM` re-renders while the dropdown is\n // open (external value update, undo/redo). At selection time we re-verify\n // this index still holds the same chip and fall back to a trigger+value\n // search if the model shifted underneath, instead of silently no-op'ing.\n const editingChip = useRef<{ chip: ChipSegment; segIndex: number } | null>(null)\n\n // The DOM node of the chip currently being edited via `reopenOnChipClick`,\n // kept in lockstep with `editingChip`/`activeTrigger` (set when opened,\n // cleared by `dismissTrigger`). Used only to answer \"is THIS exact chip\n // element the one whose dropdown is open right now\" by reference identity —\n // never by trigger+value, which can't distinguish two chips that happen to\n // share the same value.\n const openChipNode = useRef<HTMLElement | null>(null)\n\n // Set by `handleMouseDown` to the chip node a mousedown landed on, but only\n // when that node === `openChipNode.current` at that instant; read and\n // cleared by the following `handleClick` to distinguish \"reopen\" from\n // \"toggle closed\" for that one click. A real `onMouseDown` (bubble-phase,\n // attached to the editor root) is used instead of piggybacking on\n // `dismissTrigger` because DOM bubbling reaches the editor root before it\n // reaches `document` (where TriggerPopover's outside-click dismiss listens),\n // so this always observes `openChipNode` before that dismiss clears it —\n // and unlike a `dismissTrigger`-driven flag, it is scoped to mousedowns on\n // this exact node, so Escape/blur/an unrelated dismiss can never poison a\n // later, unrelated click on the same chip.\n const suppressReopenChip = useRef<HTMLElement | null>(null)\n\n const {\n suggestions,\n suggestionsLoading,\n suggestionsError,\n search: runSearch,\n reset: resetSearch,\n } = useTriggerSearch()\n\n // Guard against circular DOM <-> model syncs\n const isSyncing = useRef(false)\n const lastRenderedValue = useRef<Segment[]>([])\n\n // Debounced undo: groups consecutive keystrokes into a single undo snapshot\n const undoTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const undoBaseState = useRef<Segment[] | null>(null)\n\n // -----------------------------------------------------------------------\n // DOM -> Model: read segments from the contentEditable DOM\n // -----------------------------------------------------------------------\n\n const readSegmentsFromDOM = useCallback((): Segment[] => {\n const editor = editorRef.current\n if (!editor) return []\n\n const segments: Segment[] = []\n // Track whether the editor holds any real content (text/chip) or a sentinel\n // <br> that renderSegmentsToDOM added. When it holds neither, any <br> nodes\n // present are the browser's filler <br> (see the empty-editor check below).\n let hasRealContent = false\n let hasSentinel = false\n\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n\n if (node.nodeType === Node.TEXT_NODE) {\n const text = node.textContent ?? ''\n if (text) {\n segments.push({ type: 'text', text })\n hasRealContent = true\n }\n } else if (isChipElement(node)) {\n const chip = chipNodeToSegment(node)\n if (chip) {\n segments.push(chip)\n hasRealContent = true\n }\n } else if (isBRElement(node)) {\n if (node.dataset.sentinel) {\n hasSentinel = true\n continue // skip sentinel <br>\n }\n segments.push({ type: 'text', text: '\\n' })\n } else if (isHTMLElement(node)) {\n // Unknown element — extract text content\n const text = node.textContent ?? ''\n if (text) {\n segments.push({ type: 'text', text })\n hasRealContent = true\n }\n }\n }\n\n // When the user empties the editor (types something, then deletes it all),\n // the browser leaves a lone filler <br> so the contentEditable block stays\n // visible and focusable. Reading that <br> as a \"\\n\" text segment would make\n // `value` permanently non-empty and keep the placeholder hidden forever.\n // A newline we actually rendered always carries surrounding text/chip\n // content or a trailing sentinel <br>, so when neither is present the only\n // <br> nodes are filler and the editor is genuinely empty.\n if (!hasRealContent && !hasSentinel) return []\n\n return segments\n }, [])\n\n // -----------------------------------------------------------------------\n // Model -> DOM: render segments into the contentEditable div\n // -----------------------------------------------------------------------\n\n const renderSegmentsToDOM = useCallback(\n (segments: Segment[]) => {\n const editor = editorRef.current\n if (!editor) return\n\n isSyncing.current = true\n\n const savedCursor = saveCursorPosition(editor)\n\n // Clear DOM safely (no innerHTML assignment)\n while (editor.firstChild) {\n editor.removeChild(editor.firstChild)\n }\n\n for (const seg of segments) {\n if (seg.type === 'text') {\n const lines = seg.text.split('\\n')\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n if (lines[lineIdx]) {\n editor.appendChild(document.createTextNode(lines[lineIdx]))\n }\n if (lineIdx < lines.length - 1) {\n editor.appendChild(document.createElement('br'))\n }\n }\n } else {\n // Render chip as non-editable span\n const chip = document.createElement('span')\n chip.contentEditable = 'false'\n chip.dataset.chipTrigger = seg.trigger\n chip.dataset.chipValue = seg.value\n chip.dataset.chipDisplay = seg.displayText\n if (seg.data !== undefined) {\n const json = safeJsonStringify(seg.data)\n if (json) {\n chip.dataset.chipData = json\n }\n }\n if (seg.autoResolved) {\n chip.dataset.chipAutoResolved = 'true'\n }\n const triggerConfig = triggers.find((t) => t.char === seg.trigger)\n const chipStyle = triggerConfig?.chipStyle ?? 'pill'\n chip.dataset.chipStyle = chipStyle\n chip.className = cn(\n 'prompt-area-chip',\n chipStyle === 'inline' && 'prompt-area-chip--inline',\n triggerConfig?.chipClassName,\n )\n chip.textContent = `${seg.trigger}${seg.displayText}`\n chip.setAttribute('role', 'button')\n chip.setAttribute('tabindex', '-1')\n editor.appendChild(chip)\n }\n }\n\n // Append sentinel <br> so trailing newlines are visible in contentEditable\n if (editor.lastChild && isBRElement(editor.lastChild)) {\n const sentinel = document.createElement('br')\n sentinel.dataset.sentinel = 'true'\n editor.appendChild(sentinel)\n }\n\n // Decorate URLs, markdown formatting, and list bullets in text nodes\n decorateEditor(editor, markdownEnabled)\n\n if (savedCursor) {\n restoreCursorPosition(editor, savedCursor)\n }\n\n lastRenderedValue.current = segments\n isSyncing.current = false\n },\n [triggers, markdownEnabled],\n )\n\n // -----------------------------------------------------------------------\n // Trigger detection (extracted so events module can call it)\n // -----------------------------------------------------------------------\n\n // Builds the insertChip handed to callback/launch activations: replaces the\n // trigger's range with a chip and notifies onChipAdd.\n const buildInsertChip = useCallback(\n (segments: Segment[], trigger: ActiveTrigger) => (chip: Omit<ChipSegment, 'type'>) => {\n const chipResult = resolveChip(segments, trigger, {\n value: chip.value,\n displayText: chip.displayText,\n data: chip.data,\n })\n onChange(chipResult.segments)\n renderSegmentsToDOM(chipResult.segments)\n onChipAdd?.({\n type: 'chip',\n trigger: trigger.config.char,\n value: chip.value,\n displayText: chip.displayText,\n ...(chip.data !== undefined ? { data: chip.data } : {}),\n })\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, chipResult.cursorOffset)\n },\n [onChange, renderSegmentsToDOM, onChipAdd],\n )\n\n const runTriggerDetection = useCallback(() => {\n const editor = editorRef.current\n if (!editor) return\n\n const segments = readSegmentsFromDOM()\n const plainText = segmentsToPlainText(segments)\n const cursorPos = getCursorOffset(editor)\n\n if (cursorPos === null) return\n\n const detected = detectActiveTrigger(plainText, cursorPos, triggers)\n\n // Typing supersedes a chip-click dropdown: whichever branch we take next,\n // the popover no longer edits the clicked chip.\n editingChip.current = null\n openChipNode.current = null\n\n if (detected) {\n setActiveTrigger(detected)\n setSelectedSuggestionIndex(0)\n\n // Position the popover at the trigger character, not the cursor.\n // Build a range at detected.startOffset so the dropdown anchors to\n // the trigger char even when the cursor has moved past it.\n const triggerRange = createRangeAtOffset(editor, detected.startOffset)\n if (triggerRange) {\n const rect = triggerRange.getBoundingClientRect()\n // A zero rect means the range couldn't be mapped (e.g. after DOM\n // re-render). Skip updating triggerRect so we keep the last valid one.\n if (rect.height > 0 || rect.left > 0 || rect.top > 0) {\n setTriggerRect(rect)\n }\n }\n\n // Fetch suggestions for dropdown mode\n if (detected.config.mode === 'dropdown' && detected.config.onSearch) {\n runSearch(detected.query, detected.config)\n }\n\n // Fire callback for callback mode\n if (detected.config.mode === 'callback' && detected.config.onActivate) {\n detected.config.onActivate({\n text: plainText,\n cursorPosition: cursorPos,\n insertChip: buildInsertChip(segments, detected),\n })\n }\n } else {\n setActiveTrigger(null)\n resetSearch()\n }\n }, [triggers, readSegmentsFromDOM, buildInsertChip, resetSearch, runSearch])\n\n // -----------------------------------------------------------------------\n // Dismiss trigger\n // -----------------------------------------------------------------------\n\n const dismissTrigger = useCallback(() => {\n editingChip.current = null\n openChipNode.current = null\n setActiveTrigger(null)\n setSelectedSuggestionIndex(0)\n resetSearch()\n }, [resetSearch])\n\n // -----------------------------------------------------------------------\n // Wire up edge-case event handlers\n // -----------------------------------------------------------------------\n\n const events = usePromptAreaEvents({\n editorRef,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n runTriggerDetection,\n dismissTrigger,\n triggers,\n markdownEnabled,\n normalizeBullets,\n onPaste,\n onRawPaste,\n onUndo,\n onRedo,\n onChipAdd,\n onImagePaste,\n })\n\n // -----------------------------------------------------------------------\n // Sync value prop -> DOM on external changes\n // -----------------------------------------------------------------------\n\n useEffect(() => {\n if (isSyncing.current) return\n if (segmentsEqual(value, lastRenderedValue.current)) return\n\n // Normalize list prefixes (e.g., \"- \" → \"• \" when markdown is on)\n // so externally-provided segments render bullet characters correctly.\n if (markdownEnabled && normalizeBullets) {\n const normalized = normalizeListPrefixes(value, true)\n if (normalized !== value) {\n onChange(normalized)\n return // onChange will trigger a re-render with the normalized value\n }\n }\n\n renderSegmentsToDOM(value)\n }, [value, renderSegmentsToDOM, markdownEnabled, normalizeBullets, onChange])\n\n // Re-render when markdown mode changes to apply/strip decorations\n // Also convert bullet characters: • ↔ - in text segments\n const prevMarkdown = useRef(markdownEnabled)\n useEffect(() => {\n if (prevMarkdown.current === markdownEnabled) return\n prevMarkdown.current = markdownEnabled\n\n const converted = normalizeBullets ? normalizeListPrefixes(value, markdownEnabled) : value\n if (converted !== value) {\n onChange(converted)\n } else {\n renderSegmentsToDOM(value)\n }\n }, [markdownEnabled, normalizeBullets, renderSegmentsToDOM, value, onChange])\n\n // Clean up undo debounce timer on unmount\n useEffect(() => {\n return () => {\n if (undoTimer.current) clearTimeout(undoTimer.current)\n }\n }, [])\n\n // -----------------------------------------------------------------------\n // Handle input events\n // -----------------------------------------------------------------------\n\n const handleInput = useCallback(() => {\n if (isSyncing.current) return\n\n // During IME composition, sync model but skip trigger detection\n if (events.isComposing.current) {\n const segments = readSegmentsFromDOM()\n lastRenderedValue.current = segments\n onChange(segments)\n return\n }\n\n const editor = editorRef.current\n\n // Capture cursor offset BEFORE normalizeEditorDOM strips <a> elements,\n // otherwise the anchor node becomes detached and we lose the position.\n const savedCursorOffset = editor ? getCursorOffset(editor) : null\n\n if (editor) {\n // Normalize browser-inserted block elements (div, p, font, a, etc.)\n normalizeEditorDOM(editor)\n }\n\n const segments = readSegmentsFromDOM()\n\n // Enforce maxLength: if the edit pushed the editor past the cap, truncate\n // back to maxLength characters and keep the caret where the user was\n // editing (clamped to the cap) rather than forcing it to the end.\n if (maxLength != null && editor && segmentsToPlainText(segments).length > maxLength) {\n const caret = getCursorOffset(editor)\n const truncated = truncateSegmentsToLength(segments, maxLength)\n lastRenderedValue.current = truncated\n onChange(truncated)\n renderSegmentsToDOM(truncated)\n setCursorAtOffset(editor, caret != null ? Math.min(caret, maxLength) : maxLength)\n runTriggerDetection()\n return\n }\n\n // Check for list auto-formatting (e.g., \"- \" -> \"bullet \")\n if (markdownEnabled && normalizeBullets && editor && savedCursorOffset !== null) {\n const formatted = autoFormatListPrefix(segments, savedCursorOffset)\n if (formatted) {\n lastRenderedValue.current = formatted.segments\n onChange(formatted.segments)\n renderSegmentsToDOM(formatted.segments)\n setCursorAtOffset(editor, formatted.cursorOffset)\n runTriggerDetection()\n return\n }\n }\n\n // Native structural edits (e.g. a Backspace that deleted or merged a list\n // row) bypass applyEditResult, so rebuild ordered-list numbering here too.\n // handleInput fires on every keystroke, so gate on a genuine ordered-list\n // run — this renumbers a real list (1,2,4 → 1,2,3) but leaves incidental\n // numeric prose (\"1985. Born / 2020. Died\") untouched.\n let nextSegments = segments\n let renumberedCursor: number | null = null\n if (\n markdownEnabled &&\n savedCursorOffset !== null &&\n hasOrderedListRun(segmentsToPlainText(segments))\n ) {\n const renumbered = renumberOrderedListSegments(segments)\n if (renumbered.edits.length > 0) {\n nextSegments = renumbered.segments\n renumberedCursor = remapOffset(savedCursorOffset, renumbered.edits)\n }\n }\n\n // Debounced undo: capture the pre-edit state at the start of a typing\n // session and push it to the undo stack after UNDO_DEBOUNCE_MS of idle.\n if (!undoBaseState.current) {\n undoBaseState.current = lastRenderedValue.current\n }\n\n lastRenderedValue.current = nextSegments\n onChange(nextSegments)\n if (undoTimer.current) clearTimeout(undoTimer.current)\n undoTimer.current = setTimeout(() => {\n if (undoBaseState.current) {\n events.pushUndo(undoBaseState.current)\n undoBaseState.current = null\n }\n undoTimer.current = null\n }, UNDO_DEBOUNCE_MS)\n\n // Apply the recomputed model to the DOM. A renumber rewrites text nodes, so\n // it needs a full re-render (which also re-decorates); otherwise just\n // re-decorate the existing DOM in place.\n if (editor) {\n if (renumberedCursor !== null) {\n renderSegmentsToDOM(nextSegments)\n setCursorAtOffset(editor, renumberedCursor)\n } else {\n decorateEditor(editor, markdownEnabled)\n if (savedCursorOffset !== null) {\n setCursorAtOffset(editor, savedCursorOffset)\n }\n }\n }\n\n runTriggerDetection()\n }, [\n onChange,\n readSegmentsFromDOM,\n runTriggerDetection,\n renderSegmentsToDOM,\n markdownEnabled,\n normalizeBullets,\n maxLength,\n events,\n ])\n\n // -----------------------------------------------------------------------\n // Chip click delegation\n // -----------------------------------------------------------------------\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n const target = e.target\n if (!(target instanceof Node)) return\n\n const editor = editorRef.current\n if (!editor) return\n\n // Walk from the click target up to find a link or chip element\n let node: Node | null = target\n while (node && node !== editor) {\n // Check for URL link click — only navigate on Cmd/Ctrl+Click;\n // plain click just positions the cursor for editing.\n if (isLinkElement(node)) {\n if (e.metaKey || e.ctrlKey) {\n e.preventDefault()\n onLinkClick?.(node.href)\n window.open(node.href, '_blank', 'noopener,noreferrer')\n return\n }\n // Plain click: let the browser place the cursor inside the link text\n break\n }\n\n if (isChipElement(node)) {\n // Spawn ripple effect. `isChipElement` has already narrowed `node`\n // to HTMLElement, so no cast is needed.\n const rect = node.getBoundingClientRect()\n const ripple = document.createElement('span')\n ripple.className = 'prompt-area-chip-ripple'\n const size = Math.max(rect.width, rect.height)\n ripple.style.width = `${size}px`\n ripple.style.height = `${size}px`\n ripple.style.left = `${e.clientX - rect.left - size / 2}px`\n ripple.style.top = `${e.clientY - rect.top - size / 2}px`\n node.appendChild(ripple)\n ripple.addEventListener('animationend', () => ripple.remove())\n\n const chip = chipNodeToSegment(node)\n if (chip) {\n // Native chip-click dropdown: reopen this trigger's suggestions\n // anchored to the chip so the selection can replace it in place.\n // Gated on `!disabled` — a disabled composer must not accept edits\n // through any path, including this one.\n const config = triggers.find((t) => t.char === chip.trigger)\n // A click on THIS exact chip element while its own dropdown was\n // open just closed it (see `suppressReopenChip` and\n // `handleMouseDown`) — treat that as a toggle-close, not a reopen.\n const wasOpenForThisChip = suppressReopenChip.current === node\n suppressReopenChip.current = null\n if (\n !disabled &&\n !wasOpenForThisChip &&\n config?.reopenOnChipClick &&\n config.mode === 'dropdown' &&\n config.onSearch\n ) {\n const childIdx = indexOfChildNode(editor, node)\n editingChip.current = {\n chip,\n segIndex: domChildIndexToSegmentIndex(editor, childIdx),\n }\n openChipNode.current = node\n setActiveTrigger({ config, startOffset: 0, query: '' })\n setSelectedSuggestionIndex(0)\n setTriggerRect(rect)\n runSearch('', config)\n }\n onChipClick?.(chip)\n }\n return\n }\n node = node.parentNode\n }\n },\n [onChipClick, onLinkClick, triggers, runSearch, disabled],\n )\n\n // -----------------------------------------------------------------------\n // Chip mousedown delegation — feeds `suppressReopenChip` for handleClick's\n // toggle-close detection. See `openChipNode`/`suppressReopenChip` above for\n // why this needs to be a real mousedown listener rather than piggybacking\n // on `dismissTrigger`.\n // -----------------------------------------------------------------------\n\n const handleMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\n const target = e.target\n const editor = editorRef.current\n if (!editor || !(target instanceof Node)) {\n suppressReopenChip.current = null\n return\n }\n\n let node: Node | null = target\n while (node && node !== editor) {\n if (isChipElement(node)) {\n suppressReopenChip.current = openChipNode.current === node ? node : null\n return\n }\n node = node.parentNode\n }\n suppressReopenChip.current = null\n }, [])\n\n // -----------------------------------------------------------------------\n // Remove a chip node from DOM and sync model\n // -----------------------------------------------------------------------\n\n const removeChipNodeFromDOM = useCallback(\n (editor: HTMLElement, chipNode: HTMLElement): boolean => {\n const segments = readSegmentsFromDOM()\n const chipIdx = indexOfChildNode(editor, chipNode)\n if (chipIdx === -1) return false\n\n const segIdx = domChildIndexToSegmentIndex(editor, chipIdx)\n const deletedChip = segments[segIdx]\n const newSegments = removeChipAtIndex(segments, segIdx)\n onChange(newSegments)\n renderSegmentsToDOM(newSegments)\n\n if (deletedChip?.type === 'chip') {\n onChipDelete?.(deletedChip)\n }\n\n return true\n },\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, onChipDelete],\n )\n\n // -----------------------------------------------------------------------\n // Revert an auto-resolved chip back to plain text\n // -----------------------------------------------------------------------\n\n const revertChipNodeToText = useCallback(\n (editor: HTMLElement, chipNode: HTMLElement): boolean => {\n const segments = readSegmentsFromDOM()\n const chipIdx = indexOfChildNode(editor, chipNode)\n if (chipIdx === -1) return false\n\n const segIdx = domChildIndexToSegmentIndex(editor, chipIdx)\n const revertedChip = segments[segIdx]\n const result = revertChipAtIndex(segments, segIdx)\n if (!result) return false\n\n // Compute cursor target: plain text offset at end of reverted text\n let targetOffset = 0\n for (let i = 0; i < segIdx; i++) {\n const s = segments[i]\n if (s.type === 'text') {\n targetOffset += s.text.length\n } else {\n targetOffset += s.trigger.length + s.displayText.length\n }\n }\n targetOffset += result.revertedText.length\n\n onChange(result.segments)\n renderSegmentsToDOM(result.segments)\n setCursorAtOffset(editor, targetOffset)\n\n if (revertedChip?.type === 'chip') {\n onChipDelete?.(revertedChip)\n }\n\n return true\n },\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, onChipDelete],\n )\n\n // -----------------------------------------------------------------------\n // Chip backspace (delete chip behind cursor as whole unit)\n // -----------------------------------------------------------------------\n\n const handleChipBackspace = useCallback((): boolean => {\n const editor = editorRef.current\n if (!editor) return false\n\n const range = getSelectionRange()\n if (!range || !range.collapsed) return false\n\n const node = range.startContainer\n const offset = range.startOffset\n\n // Case 1: cursor is at the editor level (between child nodes)\n if (node === editor && offset > 0) {\n const prevChild = editor.childNodes[offset - 1]\n if (prevChild && isChipElement(prevChild)) {\n if (getChipAutoResolved(prevChild)) {\n return revertChipNodeToText(editor, prevChild)\n }\n return removeChipNodeFromDOM(editor, prevChild)\n }\n }\n\n // Case 2: cursor is at start of a text node, check previous sibling\n if (node.nodeType === Node.TEXT_NODE && offset === 0) {\n const directChild = getDirectChildContaining(editor, node)\n if (!directChild) return false\n\n let prevSibling = directChild.previousSibling\n while (\n prevSibling &&\n prevSibling.nodeType === Node.TEXT_NODE &&\n prevSibling.textContent === ''\n ) {\n prevSibling = prevSibling.previousSibling\n }\n if (prevSibling && isChipElement(prevSibling)) {\n if (getChipAutoResolved(prevSibling)) {\n return revertChipNodeToText(editor, prevSibling)\n }\n return removeChipNodeFromDOM(editor, prevSibling)\n }\n }\n\n return false\n }, [removeChipNodeFromDOM, revertChipNodeToText])\n\n // -----------------------------------------------------------------------\n // Chip forward delete (delete chip in front of cursor)\n // -----------------------------------------------------------------------\n\n const handleChipForwardDelete = useCallback((): boolean => {\n const editor = editorRef.current\n if (!editor) return false\n\n const range = getSelectionRange()\n if (!range || !range.collapsed) return false\n\n const node = range.startContainer\n const offset = range.startOffset\n\n // Case 1: cursor at the editor level\n if (node === editor && offset < editor.childNodes.length) {\n const nextChild = editor.childNodes[offset]\n if (nextChild && isChipElement(nextChild)) {\n return removeChipNodeFromDOM(editor, nextChild)\n }\n }\n\n // Case 2: cursor at end of a text node, check next sibling\n if (node.nodeType === Node.TEXT_NODE && offset === (node.textContent ?? '').length) {\n const directChild = getDirectChildContaining(editor, node)\n if (!directChild) return false\n\n let nextSibling = directChild.nextSibling\n while (\n nextSibling &&\n nextSibling.nodeType === Node.TEXT_NODE &&\n nextSibling.textContent === ''\n ) {\n nextSibling = nextSibling.nextSibling\n }\n if (nextSibling && isChipElement(nextSibling)) {\n return removeChipNodeFromDOM(editor, nextSibling)\n }\n }\n\n return false\n }, [removeChipNodeFromDOM])\n\n // -----------------------------------------------------------------------\n // Auto-resolve active trigger on space\n // -----------------------------------------------------------------------\n\n const autoResolveActiveTrigger = useCallback(\n (trigger: ActiveTrigger) => {\n const segments = readSegmentsFromDOM()\n const query = trigger.query\n\n // Create a synthetic suggestion so onSelect can customize display text\n const syntheticSuggestion: TriggerSuggestion = {\n value: query,\n label: query,\n }\n\n const displayText = trigger.config.onSelect?.(syntheticSuggestion) ?? query\n\n const chipData = {\n value: query,\n displayText: displayText || query,\n autoResolved: true,\n }\n const result = resolveChip(segments, trigger, chipData)\n\n onChange(result.segments)\n renderSegmentsToDOM(result.segments)\n\n onChipAdd?.({\n type: 'chip',\n trigger: trigger.config.char,\n ...chipData,\n })\n\n // Position cursor after the auto-resolved chip + trailing space\n const editor = editorRef.current\n if (editor) {\n setCursorAtOffset(editor, result.cursorOffset)\n }\n\n dismissTrigger()\n },\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, dismissTrigger, onChipAdd],\n )\n\n // -----------------------------------------------------------------------\n // Select a suggestion from the dropdown\n // -----------------------------------------------------------------------\n\n const selectSuggestionInternal = useCallback(\n (suggestion: TriggerSuggestion) => {\n if (!activeTrigger) return\n\n const segments = readSegmentsFromDOM()\n\n // Text-resolved suggestions leave before any chip machinery runs: there\n // is no chip, so there is nothing for the chip-click editing path or the\n // onChipAdd callback below to be about.\n const asText = activeTrigger.config.insertAsText?.(suggestion)\n if (asText !== undefined) {\n const inserted = resolveText(segments, activeTrigger, asText)\n events.pushUndo(segments)\n onChange(inserted.segments)\n renderSegmentsToDOM(inserted.segments)\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, inserted.cursorOffset)\n dismissTrigger()\n setTimeout(() => {\n editorRef.current?.focus()\n }, 0)\n return\n }\n\n const displayText = activeTrigger.config.onSelect?.(suggestion) ?? suggestion.label\n\n const chipData = {\n value: suggestion.value,\n displayText: displayText || suggestion.label,\n data: suggestion.data,\n }\n\n // Chip-click dropdown (`reopenOnChipClick`): replace the clicked chip in\n // place instead of resolving typed trigger text at the caret. Disabled\n // is re-checked here (not just at open time) in case the composer\n // became disabled while the popover was still open.\n const editing = editingChip.current\n if (editing) {\n const editor = editorRef.current\n if (editor && !disabled) {\n // Re-verify the click-time index still holds the same chip — the\n // model may have shifted (external value update, undo/redo) while\n // the dropdown was open. If it moved, recover ONLY when exactly one\n // chip in the document now matches trigger+value: with duplicates,\n // guessing risks silently editing the wrong instance, which is\n // worse than the no-op this falls back to.\n const atIndex = segments[editing.segIndex]\n const stillThere =\n atIndex?.type === 'chip' &&\n atIndex.trigger === editing.chip.trigger &&\n atIndex.value === editing.chip.value\n const segIdx = stillThere\n ? editing.segIndex\n : (() => {\n const matches: number[] = []\n segments.forEach((seg, i) => {\n if (\n seg.type === 'chip' &&\n seg.trigger === editing.chip.trigger &&\n seg.value === editing.chip.value\n ) {\n matches.push(i)\n }\n })\n return matches.length === 1 ? matches[0] : -1\n })()\n const oldChip = segIdx !== -1 ? segments[segIdx] : undefined\n\n if (oldChip?.type === 'chip') {\n const newChip: ChipSegment = {\n type: 'chip',\n trigger: activeTrigger.config.char,\n ...chipData,\n }\n let newSegments = segments.map((seg, i) => (i === segIdx ? newChip : seg))\n\n // Guarantee a real landing spot after the replaced chip, mirroring\n // resolveChip's trailing-space convention (prompt-area-engine.ts):\n // if the new chip is now the last segment (or directly followed by\n // another chip), the caret would land at a bare element boundary\n // with no text node, which some engines fail to render/snap a\n // visible caret at.\n const nextSeg = newSegments[segIdx + 1]\n const insertedSpace = !nextSeg || nextSeg.type !== 'text' || nextSeg.text.length === 0\n if (insertedSpace) {\n newSegments = [\n ...newSegments.slice(0, segIdx + 1),\n { type: 'text', text: ' ' },\n ...newSegments.slice(segIdx + 1),\n ]\n }\n\n events.pushUndo(segments)\n onChange(newSegments)\n renderSegmentsToDOM(newSegments)\n\n // Same value + display text + data: treat as a no-op confirmation\n // rather than a destructive delete+add — onChipDelete is\n // documented as firing on backspace/forward-delete, not on\n // re-confirming the already-selected suggestion.\n const unchanged =\n oldChip.value === newChip.value &&\n oldChip.displayText === newChip.displayText &&\n safeJsonStringify(oldChip.data) === safeJsonStringify(newChip.data)\n if (!unchanged) {\n onChipDelete?.(oldChip)\n onChipAdd?.(newChip)\n }\n\n // +1 when a space was inserted, matching resolveChip's own\n // \"+1 accounts for the trailing space after the chip\" placement —\n // landing exactly at the chip's end would put the caret at the\n // same bare element boundary the inserted space exists to avoid.\n const caretOffset =\n segmentsToPlainText(newSegments.slice(0, segIdx + 1)).length + (insertedSpace ? 1 : 0)\n setCursorAtOffset(editor, caretOffset)\n }\n }\n\n dismissTrigger()\n setTimeout(() => {\n editorRef.current?.focus()\n }, 0)\n return\n }\n\n const result = resolveChip(segments, activeTrigger, chipData)\n\n onChange(result.segments)\n renderSegmentsToDOM(result.segments)\n\n onChipAdd?.({\n type: 'chip',\n trigger: activeTrigger.config.char,\n ...chipData,\n })\n\n // Position cursor after the chip + trailing space\n const editor = editorRef.current\n if (editor) {\n setCursorAtOffset(editor, result.cursorOffset)\n }\n\n dismissTrigger()\n\n // Refocus editor after popover interaction\n setTimeout(() => {\n editorRef.current?.focus()\n }, 0)\n },\n [\n activeTrigger,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n dismissTrigger,\n onChipAdd,\n onChipDelete,\n events,\n disabled,\n ],\n )\n\n const selectSuggestion = selectSuggestionInternal\n\n // Chip-click dropdown: once the empty-query suggestions arrive, preselect\n // the chip's current value so the list opens \"on\" the existing choice.\n useEffect(() => {\n const editing = editingChip.current\n if (!editing || !activeTrigger?.config.reopenOnChipClick) return\n const idx = suggestions.findIndex((s) => s.value === editing.chip.value)\n if (idx > 0) setSelectedSuggestionIndex(idx)\n }, [suggestions, activeTrigger])\n\n // -----------------------------------------------------------------------\n // Handle key events\n // -----------------------------------------------------------------------\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n const applyEditResult = (\n editor: HTMLDivElement,\n result: { segments: Segment[]; cursorOffset: number },\n ) => {\n // Ordered-list numbers are a projection of position: rebuild them on\n // every structural edit and remap the caret across any digit-run width\n // changes. No-op (same reference) when there are no ordered lists.\n let { segments, cursorOffset } = result\n if (markdownEnabled) {\n const renumbered = renumberOrderedListSegments(segments)\n segments = renumbered.segments\n cursorOffset = remapOffset(cursorOffset, renumbered.edits)\n }\n lastRenderedValue.current = segments\n onChange(segments)\n renderSegmentsToDOM(segments)\n setCursorAtOffset(editor, cursorOffset)\n }\n\n const tryListContinuation = (editor: HTMLDivElement): boolean => {\n if (!markdownEnabled) return false\n const segments = readSegmentsFromDOM()\n const cursorPos = getCursorOffset(editor)\n if (cursorPos === null) return false\n const plainText = segmentsToPlainText(segments)\n if (!getListContext(plainText, cursorPos)) return false\n const result = insertListContinuation(segments, cursorPos)\n if (result) applyEditResult(editor, result)\n return true\n }\n\n // 1. Flush pending undo debounce so Cmd+Z has the latest checkpoint\n if ((e.metaKey || e.ctrlKey) && e.key === 'z' && undoBaseState.current) {\n if (undoTimer.current) {\n clearTimeout(undoTimer.current)\n undoTimer.current = null\n }\n events.pushUndo(undoBaseState.current)\n undoBaseState.current = null\n }\n\n // 1a. Undo/redo intercept\n if (events.handleKeyDownForUndoRedo(e)) return\n\n // 1.5 Markdown formatting shortcuts (Cmd+B bold, Cmd+I italic)\n if (\n markdownEnabled &&\n (e.metaKey || e.ctrlKey) &&\n !e.shiftKey &&\n (e.key === 'b' || e.key === 'i')\n ) {\n e.preventDefault()\n const editor = editorRef.current\n if (!editor) return\n\n const offsets = getSelectionOffsets(editor)\n if (!offsets || offsets.start === offsets.end) return\n\n const marker = e.key === 'b' ? '**' : '*'\n const currentSegments = readSegmentsFromDOM()\n events.pushUndo(currentSegments)\n\n const result = toggleMarkdownWrap(currentSegments, offsets.start, offsets.end, marker)\n if (!result) return\n\n lastRenderedValue.current = result.segments\n onChange(result.segments)\n renderSegmentsToDOM(result.segments)\n setSelectionAtOffsets(editor, result.selectionStart, result.selectionEnd)\n return\n }\n\n // 1.75 Launch triggers: a trigger with mode 'launch' fires onActivate on\n // keydown and suppresses the char so it never enters the editor — for\n // opening an external surface (dialog, palette). The DOM read is gated on\n // the typed key actually matching a launch char, so it stays off the hot\n // path. insertChip still inserts a chip at the cursor if the consumer\n // wants one after the external selection.\n if (\n !e.metaKey &&\n !e.ctrlKey &&\n !e.altKey &&\n !e.nativeEvent.isComposing &&\n e.key.length === 1\n ) {\n const launcher = triggers.find((t) => t.mode === 'launch' && t.char === e.key)\n const editor = editorRef.current\n if (launcher?.onActivate && editor) {\n const cursorPos = getCursorOffset(editor)\n if (cursorPos !== null) {\n const segments = readSegmentsFromDOM()\n const plainText = segmentsToPlainText(segments)\n if (isValidTriggerPosition(plainText, cursorPos, launcher.position)) {\n e.preventDefault()\n launcher.onActivate({\n text: plainText,\n cursorPosition: cursorPos,\n insertChip: buildInsertChip(\n replaceTextRange(segments, cursorPos, cursorPos, launcher.char),\n { config: launcher, startOffset: cursorPos, query: '' },\n ),\n })\n return\n }\n }\n }\n }\n\n // 2. Trigger dropdown navigation. Gated on the dropdown actually being\n // ON SCREEN, which matches TriggerPopover's own render condition\n // (non-empty suggestions, OR loading/error/emptyMessage) rather than\n // just `suggestions.length > 0` — otherwise a popover left open in a\n // loading/empty state (e.g. right after a chip-click reopen, before its\n // empty-query search resolves) lets Enter fall through to onSubmit and\n // Escape fall through to onEscape while still visibly on screen.\n const dropdownVisible =\n activeTrigger &&\n activeTrigger.config.mode === 'dropdown' &&\n (suggestions.length > 0 ||\n suggestionsLoading ||\n suggestionsError !== null ||\n !!activeTrigger.config.emptyMessage)\n if (dropdownVisible) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n if (suggestions.length > 0) {\n setSelectedSuggestionIndex((prev) => Math.min(prev + 1, suggestions.length - 1))\n }\n return\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n if (suggestions.length > 0) {\n setSelectedSuggestionIndex((prev) => Math.max(prev - 1, 0))\n }\n return\n }\n if (e.key === 'Enter' || e.key === 'Tab') {\n e.preventDefault()\n const selected = suggestions[selectedSuggestionIndex]\n if (selected) {\n selectSuggestionInternal(selected)\n }\n return\n }\n if (e.key === 'Escape') {\n e.preventDefault()\n dismissTrigger()\n return\n }\n }\n\n // 2.5. Auto-resolve on Space when trigger has resolveOnSpace\n if (e.key === ' ' && activeTrigger && activeTrigger.config.resolveOnSpace) {\n const query = activeTrigger.query.trim()\n if (query.length > 0) {\n e.preventDefault()\n autoResolveActiveTrigger(activeTrigger)\n return\n }\n }\n\n // 2.6. Tab/Shift+Tab for list indentation (only when trigger dropdown is NOT open)\n if (markdownEnabled && e.key === 'Tab' && !activeTrigger) {\n const editor = editorRef.current\n if (editor) {\n const segments = readSegmentsFromDOM()\n const plainText = segmentsToPlainText(segments)\n const cursorPos = getCursorOffset(editor)\n if (cursorPos !== null) {\n const ctx = getListContext(plainText, cursorPos)\n if (ctx) {\n e.preventDefault()\n const result = e.shiftKey\n ? outdentListItem(segments, cursorPos)\n : indentListItem(segments, cursorPos)\n if (result) applyEditResult(editor, result)\n return\n }\n }\n }\n }\n\n // Insert a newline at the model level (avoids the browser's broken\n // contentEditable behaviour near <a> elements).\n const insertPlainNewline = (editor: HTMLDivElement): void => {\n const offsets = getSelectionOffsets(editor)\n if (!offsets) return\n const currentSegments = readSegmentsFromDOM()\n events.pushUndo(currentSegments)\n const newSegments = replaceTextRange(currentSegments, offsets.start, offsets.end, '\\n')\n applyEditResult(editor, { segments: newSegments, cursorOffset: offsets.start + 1 })\n }\n\n // 2.8 Shift+Enter always inserts a newline (after a list-continuation check).\n if (e.key === 'Enter' && e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault()\n const editor = editorRef.current\n if (editor && !tryListContinuation(editor)) insertPlainNewline(editor)\n return\n }\n\n // 3. Enter without Shift (skipping IME): continue a list, else submit when\n // `submitOnEnter` is set, else insert a newline.\n if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {\n const editor = editorRef.current\n if (editor && tryListContinuation(editor)) {\n e.preventDefault()\n return\n }\n if (submitOnEnter) {\n e.preventDefault()\n onSubmit?.(readSegmentsFromDOM())\n return\n }\n e.preventDefault()\n if (editor) insertPlainNewline(editor)\n return\n }\n\n // 4. Escape\n if (e.key === 'Escape' && onEscape) {\n onEscape()\n return\n }\n\n // 4.5 Non-collapsed selection delete (Backspace/Delete across <a> boundaries)\n if ((e.key === 'Backspace' || e.key === 'Delete') && !e.nativeEvent.isComposing) {\n const editor = editorRef.current\n if (editor) {\n const offsets = getSelectionOffsets(editor)\n if (offsets && offsets.start !== offsets.end) {\n e.preventDefault()\n const currentSegments = readSegmentsFromDOM()\n events.pushUndo(currentSegments)\n const newSegments = replaceTextRange(currentSegments, offsets.start, offsets.end, '')\n applyEditResult(editor, { segments: newSegments, cursorOffset: offsets.start })\n runTriggerDetection()\n return\n }\n }\n }\n\n // 5. Backspace: check list prefix removal, then chip deletion\n if (e.key === 'Backspace') {\n const editor = editorRef.current\n if (editor) {\n const segments = readSegmentsFromDOM()\n const cursorPos = getCursorOffset(editor)\n if (markdownEnabled && cursorPos !== null) {\n const result = removeListPrefix(segments, cursorPos)\n if (result) {\n e.preventDefault()\n applyEditResult(editor, result)\n runTriggerDetection()\n return\n }\n }\n }\n if (handleChipBackspace()) {\n e.preventDefault()\n runTriggerDetection()\n return\n }\n }\n\n // 6. Delete (forward): delete chip as whole unit\n if (e.key === 'Delete' && handleChipForwardDelete()) {\n e.preventDefault()\n runTriggerDetection()\n return\n }\n },\n [\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n onSubmit,\n submitOnEnter,\n onEscape,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n markdownEnabled,\n dismissTrigger,\n handleChipBackspace,\n handleChipForwardDelete,\n autoResolveActiveTrigger,\n runTriggerDetection,\n selectSuggestionInternal,\n events,\n triggers,\n buildInsertChip,\n ],\n )\n\n // -----------------------------------------------------------------------\n // Imperative handle (memoized to avoid identity changes)\n // -----------------------------------------------------------------------\n\n const handle: PromptAreaHandle = useMemo(\n () => ({\n focus: () => editorRef.current?.focus(),\n blur: () => editorRef.current?.blur(),\n insertChip: (chip) => {\n const segments = readSegmentsFromDOM()\n const newChip: ChipSegment = { type: 'chip', ...chip }\n const newSegments: Segment[] = [...segments, newChip, { type: 'text', text: ' ' }]\n onChange(newSegments)\n renderSegmentsToDOM(newSegments)\n onChipAdd?.(newChip)\n },\n getPlainText: () => segmentsToPlainText(readSegmentsFromDOM()),\n clear: () => {\n onChange([])\n const editor = editorRef.current\n if (editor) {\n while (editor.firstChild) editor.removeChild(editor.firstChild)\n }\n events.resetUndoHistory()\n if (undoTimer.current) {\n clearTimeout(undoTimer.current)\n undoTimer.current = null\n }\n undoBaseState.current = null\n },\n setText: (text) => {\n events.pushUndo(readSegmentsFromDOM())\n const segments = plainTextToSegments(text)\n onChange(segments)\n renderSegmentsToDOM(segments)\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, text.length)\n },\n appendText: (text) => {\n const segments = readSegmentsFromDOM()\n events.pushUndo(segments)\n // Merge into the trailing text segment so the onChange value doesn't\n // carry two adjacent un-merged text segments.\n const last = segments[segments.length - 1]\n const next: Segment[] =\n last?.type === 'text'\n ? [...segments.slice(0, -1), { type: 'text', text: last.text + text }]\n : [...segments, { type: 'text', text }]\n onChange(next)\n renderSegmentsToDOM(next)\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, segmentsToPlainText(next).length)\n },\n getCursorPosition: () => {\n const editor = editorRef.current\n return editor ? getCursorOffset(editor) : null\n },\n setCursorPosition: (offset) => {\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, offset)\n },\n setCursorToEnd: () => {\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, segmentsToPlainText(readSegmentsFromDOM()).length)\n },\n getSelection: () => {\n const editor = editorRef.current\n return editor ? getSelectionOffsets(editor) : null\n },\n setSelection: (start, end) => {\n const editor = editorRef.current\n if (editor) setSelectionAtOffsets(editor, start, end)\n },\n }),\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, onChipAdd, events],\n )\n\n // -----------------------------------------------------------------------\n // Compose event handlers\n // -----------------------------------------------------------------------\n\n const eventHandlers = useMemo(\n () => ({\n onPaste: events.handlePaste,\n onCopy: events.handleCopy,\n onCut: events.handleCut,\n onDrop: events.handleDrop,\n onDragOver: events.handleDragOver,\n onCompositionStart: events.handleCompositionStart,\n onCompositionEnd: events.handleCompositionEnd,\n onBlur: events.handleBlur,\n }),\n [\n events.handlePaste,\n events.handleCopy,\n events.handleCut,\n events.handleDrop,\n events.handleDragOver,\n events.handleCompositionStart,\n events.handleCompositionEnd,\n events.handleBlur,\n ],\n )\n\n return {\n editorRef,\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n handleInput,\n handleKeyDown,\n handleClick,\n handleMouseDown,\n selectSuggestion,\n dismissTrigger,\n handle,\n triggerRect,\n eventHandlers,\n }\n}\n","'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport type { TriggerSuggestion } from './types.ts'\n\ntype TriggerPopoverProps = {\n suggestions: TriggerSuggestion[]\n loading: boolean\n error?: string | null\n emptyMessage?: string\n selectedIndex: number\n onSelect: (suggestion: TriggerSuggestion) => void\n onDismiss: () => void\n triggerRect: DOMRect | null\n triggerChar: string\n}\n\n// Single source of truth for the popover height: drives both the flip-above\n// calculation and the maxHeight style.\nconst POPOVER_MAX_HEIGHT = 240\n\n/**\n * Floating popover that displays trigger suggestions.\n * Positioned relative to the trigger character location in the editor.\n */\nexport function TriggerPopover({\n suggestions,\n loading,\n error,\n emptyMessage,\n selectedIndex,\n onSelect,\n onDismiss,\n triggerRect,\n triggerChar,\n}: TriggerPopoverProps) {\n const popoverRef = useRef<HTMLDivElement>(null)\n const selectedRef = useRef<HTMLButtonElement>(null)\n\n // Scroll selected item into view\n useEffect(() => {\n selectedRef.current?.scrollIntoView({ block: 'nearest' })\n }, [selectedIndex])\n\n // Click outside to dismiss\n useEffect(() => {\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target\n if (popoverRef.current && target instanceof Node && !popoverRef.current.contains(target)) {\n onDismiss()\n }\n }\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [onDismiss])\n\n if (!triggerRect) return null\n if (suggestions.length === 0 && !loading && !error && !emptyMessage) return null\n\n // Position the popover relative to the trigger character, clamped to the\n // viewport. Flip above the trigger when there isn't enough room below, so the\n // suggestion list stays on-screen near the bottom edge.\n const popoverMaxWidth = Math.min(320, window.innerWidth - 16)\n const left = Math.min(triggerRect.left, window.innerWidth - popoverMaxWidth - 8)\n const spaceBelow = window.innerHeight - triggerRect.bottom\n const positionAbove = spaceBelow < POPOVER_MAX_HEIGHT && triggerRect.top > spaceBelow\n const style: React.CSSProperties = {\n position: 'fixed',\n left: `${Math.max(8, left)}px`,\n zIndex: 50,\n maxWidth: `${popoverMaxWidth}px`,\n maxHeight: `${POPOVER_MAX_HEIGHT}px`,\n ...(positionAbove\n ? { bottom: `${window.innerHeight - triggerRect.top + 4}px` }\n : { top: `${triggerRect.bottom + 4}px` }),\n }\n\n return (\n <div\n ref={popoverRef}\n className={cn(\n 'min-w-[200px] overflow-y-auto',\n 'bg-surface rounded-xl border p-2 shadow-md',\n 'animate-in fade-in-0 zoom-in-95',\n )}\n style={style}\n role=\"listbox\"\n aria-label={`${triggerChar} suggestions`}>\n {loading ? (\n <div\n role=\"option\"\n aria-selected={false}\n className=\"text-muted-foreground px-3 py-2 text-sm\">\n Loading suggestions...\n </div>\n ) : error ? (\n <div role=\"option\" aria-selected={false} className=\"text-destructive px-3 py-2 text-sm\">\n {error}\n </div>\n ) : suggestions.length === 0 && emptyMessage ? (\n <div\n role=\"option\"\n aria-selected={false}\n className=\"text-muted-foreground px-3 py-2 text-sm\">\n {emptyMessage}\n </div>\n ) : (\n suggestions.map((suggestion, index) => (\n <button\n key={suggestion.value}\n ref={index === selectedIndex ? selectedRef : undefined}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={cn(\n 'text-foreground flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm',\n 'hover:bg-surface-hover cursor-pointer transition-colors',\n index === selectedIndex && 'bg-surface-hover',\n )}\n onMouseDown={(e) => {\n e.preventDefault() // Prevent blur on the editor\n onSelect(suggestion)\n }}>\n {suggestion.icon && <span className=\"mt-0.5 shrink-0\">{suggestion.icon}</span>}\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate font-medium\">{suggestion.label}</div>\n {suggestion.description && (\n <div className=\"text-muted-foreground truncate text-xs\">\n {suggestion.description}\n </div>\n )}\n </div>\n </button>\n ))\n )}\n </div>\n )\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\ntype AnimatedPlaceholderProps = {\n texts: string[]\n interval?: number\n}\n\n/**\n * Cross-fading placeholder that rotates through `texts`.\n *\n * Each text is keyed so React remounts it on change, replaying the\n * `tw-animate-css` enter animation (slide down + fade in). No animation\n * library is required.\n */\nexport function AnimatedPlaceholder({ texts, interval = 3000 }: AnimatedPlaceholderProps) {\n const [index, setIndex] = useState(0)\n\n useEffect(() => {\n if (texts.length <= 1) return\n\n const id = setInterval(() => {\n setIndex((prev) => (prev + 1) % texts.length)\n }, interval)\n\n return () => clearInterval(id)\n }, [texts.length, interval])\n\n return (\n <div\n className=\"pointer-events-none absolute top-0 left-0 overflow-hidden select-none\"\n style={{ color: 'var(--prompt-area-placeholder, var(--muted-foreground))' }}\n aria-hidden=\"true\">\n <div\n key={index}\n className=\"animate-in fade-in-0 slide-in-from-top-4 duration-300 ease-in-out\">\n {texts[index]}\n </div>\n </div>\n )\n}\n","import { cn } from '../../lib/utils.ts'\n\ntype RemoveButtonProps = {\n onClick: () => void\n label: string\n className?: string\n}\n\nexport function RemoveButton({ onClick, label, className }: RemoveButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onClick()\n }}\n className={cn(\n 'absolute top-0.5 right-0.5 grid h-3.5 w-3.5 cursor-pointer place-items-center',\n 'rounded-full bg-black/60 text-white hover:bg-black/80 dark:bg-white/60 dark:text-black dark:hover:bg-white/80',\n 'transition-colors',\n className,\n )}\n aria-label={label}>\n <svg\n width=\"8\"\n height=\"8\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\">\n <line x1=\"2.75\" y1=\"2.75\" x2=\"7.25\" y2=\"7.25\" />\n <line x1=\"7.25\" y1=\"2.75\" x2=\"2.75\" y2=\"7.25\" />\n </svg>\n </button>\n )\n}\n","'use client'\n\nimport { cn } from '../../lib/utils.ts'\nimport { RemoveButton } from './remove-button.tsx'\nimport type { PromptAreaImage } from './types.ts'\n\ntype ImageStripProps = {\n images: PromptAreaImage[]\n onRemove?: (image: PromptAreaImage) => void\n onClick?: (image: PromptAreaImage) => void\n className?: string\n}\n\nexport function ImageStrip({ images, onRemove, onClick, className }: ImageStripProps) {\n if (images.length === 0) return null\n\n return (\n <div className={cn('flex flex-wrap gap-2', className)} role=\"list\" aria-label=\"Attached images\">\n {images.map((image) => (\n <div\n key={image.id}\n role=\"listitem\"\n className={cn(\n 'border-border relative h-16 w-16 flex-shrink-0 overflow-hidden rounded-md border',\n onClick && 'cursor-pointer',\n )}\n onClick={() => onClick?.(image)}>\n {/* eslint-disable-next-line @next/next/no-img-element -- registry component used outside Next.js */}\n <img\n src={image.url}\n alt={image.alt ?? 'Attached image'}\n className=\"h-full w-full object-cover\"\n />\n {image.loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/40\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent\" />\n </div>\n )}\n {onRemove && (\n <RemoveButton\n onClick={() => onRemove(image)}\n label={`Remove ${image.alt ?? 'image'}`}\n />\n )}\n </div>\n ))}\n </div>\n )\n}\n","'use client'\n\nimport { useEffect, useRef, useState } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport { RemoveButton } from './remove-button.tsx'\nimport type { PromptAreaFile } from './types.ts'\n\ntype IconProps = { className?: string }\n\n/** Shared SVG wrapper matching the lucide icon defaults (no dependency). */\nfunction Svg({ className, children }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={className}>\n {children}\n </svg>\n )\n}\n\nconst FileBody = (\n <>\n <path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\" />\n <path d=\"M14 2v5a1 1 0 0 0 1 1h5\" />\n </>\n)\n\nconst File = ({ className }: IconProps) => <Svg className={className}>{FileBody}</Svg>\nconst FileText = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M10 9H8\" />\n <path d=\"M16 13H8\" />\n <path d=\"M16 17H8\" />\n </Svg>\n)\nconst FileSpreadsheet = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M8 13h2\" />\n <path d=\"M14 13h2\" />\n <path d=\"M8 17h2\" />\n <path d=\"M14 17h2\" />\n </Svg>\n)\nconst FileCode = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M10 12.5 8 15l2 2.5\" />\n <path d=\"m14 12.5 2 2.5-2 2.5\" />\n </Svg>\n)\nconst ImageIcon = ({ className }: IconProps) => (\n <Svg className={className}>\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n <circle cx=\"9\" cy=\"9\" r=\"2\" />\n <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />\n </Svg>\n)\n\ntype FileStripProps = {\n files: PromptAreaFile[]\n onRemove?: (file: PromptAreaFile) => void\n onClick?: (file: PromptAreaFile) => void\n className?: string\n}\n\n/** Threshold above which collapse activates automatically. */\nconst COLLAPSE_THRESHOLD = 3\n\n/** Pick a lucide icon key based on MIME type. */\nfunction getFileIconKey(type?: string): 'pdf' | 'spreadsheet' | 'code' | 'image' | 'default' {\n if (!type) return 'default'\n if (type === 'application/pdf') return 'pdf'\n if (type.includes('spreadsheet') || type === 'text/csv') return 'spreadsheet'\n if (\n type.startsWith('text/') ||\n type.includes('javascript') ||\n type.includes('json') ||\n type.includes('xml')\n )\n return 'code'\n if (type.startsWith('image/')) return 'image'\n return 'default'\n}\n\nconst FILE_ICONS = {\n pdf: FileText,\n spreadsheet: FileSpreadsheet,\n code: FileCode,\n image: ImageIcon,\n default: File,\n} as const\n\n/** Format bytes into a human-readable string. */\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n}\n\n/** Extract a short extension label from a filename (e.g., \"PDF\", \"CSV\"). */\nfunction getExtensionLabel(name: string): string | null {\n const dot = name.lastIndexOf('.')\n if (dot === -1 || dot === name.length - 1) return null\n return name.slice(dot + 1).toUpperCase()\n}\n\nfunction FileCard({\n file,\n compact,\n onRemove,\n onClick,\n}: {\n file: PromptAreaFile\n compact: boolean\n onRemove?: (file: PromptAreaFile) => void\n onClick?: (file: PromptAreaFile) => void\n}) {\n const ext = getExtensionLabel(file.name)\n const sizeStr = file.size != null ? formatFileSize(file.size) : null\n const meta = [ext, sizeStr].filter(Boolean).join(' · ')\n\n return (\n <div\n role=\"listitem\"\n className={cn(\n 'border-border relative flex flex-shrink-0 items-center gap-2 overflow-hidden rounded-lg border transition-colors',\n 'hover:bg-surface-hover',\n compact ? 'h-10 w-36 px-2' : 'h-14 w-48 px-3',\n onClick && 'cursor-pointer',\n )}\n onClick={() => onClick?.(file)}>\n {(() => {\n const Icon = FILE_ICONS[getFileIconKey(file.type)]\n return (\n <Icon\n className={cn('text-muted-foreground flex-shrink-0', compact ? 'h-4 w-4' : 'h-5 w-5')}\n />\n )\n })()}\n <div className=\"min-w-0 flex-1\">\n <div\n className={cn('truncate font-medium', compact ? 'text-xs' : 'text-sm')}\n title={file.name}>\n {file.name}\n </div>\n {!compact && meta && <div className=\"text-muted-foreground truncate text-xs\">{meta}</div>}\n </div>\n\n {file.loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/40\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent\" />\n </div>\n )}\n\n {onRemove && <RemoveButton onClick={() => onRemove(file)} label={`Remove ${file.name}`} />}\n </div>\n )\n}\n\nexport function FileStrip({ files, onRemove, onClick, className }: FileStripProps) {\n const [expanded, setExpanded] = useState(false)\n const popoverRef = useRef<HTMLDivElement>(null)\n const toggleRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (!expanded) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target\n if (!(target instanceof Node)) return\n if (\n popoverRef.current &&\n !popoverRef.current.contains(target) &&\n !toggleRef.current?.contains(target)\n ) {\n setExpanded(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [expanded])\n\n if (files.length === 0) return null\n\n const collapsible = files.length > COLLAPSE_THRESHOLD\n const compact = collapsible\n const hiddenCount = files.length - COLLAPSE_THRESHOLD\n const visibleFiles = files.slice(0, COLLAPSE_THRESHOLD)\n\n return (\n <div className={cn('relative', className)}>\n <div className=\"flex flex-wrap gap-2\" role=\"list\" aria-label=\"Attached files\">\n {(collapsible ? visibleFiles : files).map((file) => (\n <FileCard\n key={file.id}\n file={file}\n compact={compact}\n onRemove={onRemove}\n onClick={onClick}\n />\n ))}\n {collapsible && (\n <div role=\"listitem\">\n <button\n ref={toggleRef}\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className={cn(\n 'border-border text-muted-foreground hover:bg-surface-hover flex flex-shrink-0 cursor-pointer items-center justify-center rounded-lg border transition-colors',\n compact ? 'h-10 px-3 text-xs' : 'h-14 px-4 text-sm',\n )}>\n {expanded ? 'Show less' : `+${hiddenCount} more`}\n </button>\n </div>\n )}\n </div>\n\n {expanded && (\n <div\n ref={popoverRef}\n className={cn(\n 'bg-surface border-border absolute bottom-full left-0 z-10 mb-2 max-h-48 overflow-y-auto rounded-lg border p-2 shadow-lg',\n )}>\n <div className=\"flex flex-wrap gap-2\" role=\"list\" aria-label=\"More attached files\">\n {files.slice(COLLAPSE_THRESHOLD).map((file) => (\n <FileCard\n key={file.id}\n file={file}\n compact={compact}\n onRemove={onRemove}\n onClick={onClick}\n />\n ))}\n </div>\n </div>\n )}\n </div>\n )\n}\n","'use client'\n\nimport { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport type { PromptAreaProps, PromptAreaHandle } from './types.ts'\nimport { usePromptArea } from './use-prompt-area.ts'\nimport { BLUR_DELAY_MS } from './use-prompt-area-events.ts'\nimport { TriggerPopover } from './trigger-popover.tsx'\nimport { AnimatedPlaceholder } from './animated-placeholder.tsx'\nimport { ImageStrip } from './image-strip.tsx'\nimport { FileStrip } from './file-strip.tsx'\n\n/**\n * PromptArea - A lightweight rich text input with trigger support.\n *\n * Uses contentEditable to support inline chips (immutable pills) for\n * mentions, commands, and other triggered tokens. Each trigger character\n * can be configured to show a dropdown or fire a callback.\n *\n * @example\n * ```tsx\n * const [segments, setSegments] = useState<Segment[]>([])\n *\n * <PromptArea\n * value={segments}\n * onChange={setSegments}\n * triggers={[\n * { char: '@', position: 'any', mode: 'dropdown', onSearch: searchUsers },\n * { char: '/', position: 'start', mode: 'dropdown', onSearch: searchCommands },\n * { char: '#', position: 'any', mode: 'dropdown', onSearch: searchTags },\n * ]}\n * placeholder=\"Type a message...\"\n * onSubmit={handleSubmit}\n * autoGrow\n * />\n * ```\n */\nexport function PromptArea({\n value,\n onChange,\n triggers,\n placeholder,\n className,\n disabled = false,\n markdown,\n normalizeBullets,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onLinkClick,\n onPaste,\n onUndo,\n onRedo,\n minHeight = 80,\n maxHeight,\n autoFocus = false,\n autoGrow = false,\n 'aria-label': ariaLabel,\n 'data-test-id': dataTestId,\n images = [],\n imagePosition = 'above',\n onImagePaste,\n onImageRemove,\n onImageClick,\n files = [],\n filePosition = 'above',\n onFileRemove,\n onFileClick,\n onKeyDown,\n onBlur,\n onRawPaste,\n submitOnEnter,\n spellCheck,\n maxLength,\n 'aria-describedby': ariaDescribedBy,\n ref,\n}: PromptAreaProps & { ref?: React.Ref<PromptAreaHandle> }) {\n const {\n editorRef,\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n handleInput,\n handleKeyDown,\n handleClick,\n handleMouseDown,\n selectSuggestion,\n dismissTrigger,\n handle,\n triggerRect,\n eventHandlers,\n } = usePromptArea({\n value,\n onChange,\n triggers,\n disabled,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onLinkClick,\n onPaste,\n onRawPaste,\n onUndo,\n onRedo,\n onImagePaste,\n markdown,\n normalizeBullets,\n submitOnEnter,\n maxLength,\n })\n\n // Expose imperative handle via ref\n useImperativeHandle(ref, () => handle, [handle])\n\n // Auto-focus on mount\n useEffect(() => {\n if (autoFocus) {\n editorRef.current?.focus()\n }\n }, [autoFocus, editorRef])\n\n // -----------------------------------------------------------------------\n // Auto-grow: expand on focus/input, shrink on blur\n // -----------------------------------------------------------------------\n\n const [isFocused, setIsFocused] = useState(false)\n const [editorHeight, setEditorHeight] = useState<number | undefined>(undefined)\n\n const syncHeight = useCallback(() => {\n const el = editorRef.current\n if (!el) return\n // Temporarily set height to auto so scrollHeight reflects true content height\n el.style.height = 'auto'\n const contentHeight = el.scrollHeight\n el.style.height = `${contentHeight}px`\n setEditorHeight(contentHeight)\n }, [editorRef])\n\n const handleFocus = useCallback(() => {\n if (!autoGrow) return\n setIsFocused(true)\n syncHeight()\n }, [autoGrow, syncHeight])\n\n const handleBlurWithShrink = useCallback(() => {\n eventHandlers.onBlur()\n if (!autoGrow) return\n setTimeout(() => {\n const editor = editorRef.current\n if (!editor) return\n // Only shrink if focus truly left the component\n const activeEl = document.activeElement\n if (activeEl && editor.parentElement?.contains(activeEl)) return\n setIsFocused(false)\n setEditorHeight(undefined)\n }, BLUR_DELAY_MS)\n }, [eventHandlers, autoGrow, editorRef])\n\n const handleInputWithGrow = useCallback(() => {\n handleInput()\n if (autoGrow && isFocused) {\n syncHeight()\n }\n }, [handleInput, autoGrow, isFocused, syncHeight])\n\n // Re-measure on value changes (chip insertion, undo/redo, programmatic updates)\n useEffect(() => {\n if (autoGrow && isFocused) {\n requestAnimationFrame(() => syncHeight())\n }\n }, [value, autoGrow, isFocused, syncHeight])\n\n // -----------------------------------------------------------------------\n // Overflow indicator: detect when collapsed content is clipped\n // -----------------------------------------------------------------------\n\n const [hasOverflow, setHasOverflow] = useState(false)\n const overflowTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n if (!autoGrow) return\n\n const checkOverflow = () => {\n if (isFocused) {\n setHasOverflow(false)\n return\n }\n const el = editorRef.current\n if (!el) return\n setHasOverflow(el.scrollHeight > el.clientHeight)\n }\n\n // Delay the check so the CSS height transition (150ms) finishes first;\n // on initial mount there is no transition so the check still runs quickly.\n const delay = isFocused ? 0 : 160\n overflowTimerRef.current = setTimeout(checkOverflow, delay)\n return () => {\n if (overflowTimerRef.current !== null) {\n clearTimeout(overflowTimerRef.current)\n }\n }\n }, [autoGrow, isFocused, value, editorRef])\n\n // -----------------------------------------------------------------------\n // Compute editor style\n // -----------------------------------------------------------------------\n\n const editorStyle = useMemo((): React.CSSProperties => {\n if (!autoGrow) {\n const style: React.CSSProperties = { minHeight: `${minHeight}px` }\n if (maxHeight) {\n style.maxHeight = `${maxHeight}px`\n style.overflowY = 'auto'\n }\n return style\n }\n return {\n height: isFocused && editorHeight ? `${editorHeight}px` : `${minHeight}px`,\n minHeight: `${minHeight}px`,\n // Respect an explicit maxHeight; otherwise fall back to a viewport-relative\n // cap so the editor never grows past the screen.\n maxHeight: maxHeight ? `${maxHeight}px` : '70dvh',\n overflowY: isFocused ? 'auto' : 'hidden',\n // `min-height` is eased so consumers that animate it (e.g. the compact\n // prompt area's collapse/expand) morph smoothly instead of snapping.\n transition: 'height 150ms ease-out, min-height 240ms cubic-bezier(0.33, 1, 0.68, 1)',\n }\n }, [autoGrow, minHeight, maxHeight, isFocused, editorHeight])\n\n // Run a consumer's onKeyDown first; if it calls preventDefault, skip all of\n // PromptArea's built-in key handling (submit, trigger nav, etc.).\n const handleKeyDownCombined = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(e)\n if (e.defaultPrevented) return\n handleKeyDown(e)\n },\n [onKeyDown, handleKeyDown],\n )\n\n // Forward blur to the consumer (with relatedTarget) alongside the internal\n // trigger-dismiss / auto-grow-shrink handling.\n const handleBlurCombined = useCallback(\n (e: React.FocusEvent<HTMLDivElement>) => {\n onBlur?.(e)\n // handleBlurWithShrink already calls eventHandlers.onBlur() and only\n // shrinks when autoGrow is on, so it covers both modes.\n handleBlurWithShrink()\n },\n [onBlur, handleBlurWithShrink],\n )\n\n const isEmpty =\n value.length === 0 || (value.length === 1 && value[0].type === 'text' && value[0].text === '')\n\n const imageStrip =\n images.length > 0 ? (\n <ImageStrip\n images={images}\n onRemove={onImageRemove}\n onClick={onImageClick}\n className={imagePosition === 'above' ? 'pb-2' : 'pt-2'}\n />\n ) : null\n\n const fileStrip =\n files.length > 0 ? (\n <FileStrip\n files={files}\n onRemove={onFileRemove}\n onClick={onFileClick}\n className={filePosition === 'above' ? 'pb-2' : 'pt-2'}\n />\n ) : null\n\n // Typography (font-size/line-height) lives on the container, not the editor, so\n // it cascades to the editor AND the placeholder overlays — and a consumer can\n // override all three at once via `className` (e.g. `text-base leading-6`).\n return (\n <div className={cn('prompt-area-container relative text-sm leading-relaxed', className)}>\n {imagePosition === 'above' && imageStrip}\n {filePosition === 'above' && fileStrip}\n\n {/* Editor + placeholder wrapper */}\n <div className=\"relative\">\n <div\n ref={editorRef}\n contentEditable={!disabled}\n suppressContentEditableWarning\n role=\"textbox\"\n aria-label={ariaLabel ?? 'Text input'}\n aria-multiline=\"true\"\n aria-disabled={disabled || undefined}\n aria-describedby={ariaDescribedBy}\n data-test-id={dataTestId}\n spellCheck={spellCheck}\n className={cn(\n 'prompt-area-editor',\n 'w-full min-w-0 break-words whitespace-pre-wrap outline-none',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n style={editorStyle}\n onFocus={handleFocus}\n onInput={autoGrow ? handleInputWithGrow : handleInput}\n onKeyDown={handleKeyDownCombined}\n onMouseDown={handleMouseDown}\n onClick={handleClick}\n onPaste={eventHandlers.onPaste}\n onCopy={eventHandlers.onCopy}\n onCut={eventHandlers.onCut}\n onDrop={eventHandlers.onDrop}\n onDragOver={eventHandlers.onDragOver}\n onCompositionStart={eventHandlers.onCompositionStart}\n onCompositionEnd={eventHandlers.onCompositionEnd}\n onBlur={handleBlurCombined}\n />\n\n {/* Overflow gradient indicator – visible when auto-grow is collapsed and content is clipped */}\n {autoGrow && hasOverflow && !isFocused && (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-auto absolute right-0 bottom-0 left-0 cursor-pointer\"\n style={{ height: '32px' }}\n onClick={() => editorRef.current?.focus()}>\n <div\n className=\"h-full w-full\"\n style={{\n background:\n 'linear-gradient(to bottom, transparent, color-mix(in srgb, var(--prompt-area-surface, var(--background)) 80%, transparent), var(--prompt-area-surface, var(--background)))',\n }}\n />\n </div>\n )}\n\n {/* Placeholder overlay */}\n {isEmpty &&\n placeholder &&\n (Array.isArray(placeholder) ? (\n <AnimatedPlaceholder texts={placeholder} />\n ) : (\n <div\n className=\"pointer-events-none absolute top-0 left-0 select-none\"\n style={{ color: 'var(--prompt-area-placeholder, var(--muted-foreground))' }}\n aria-hidden=\"true\">\n {placeholder}\n </div>\n ))}\n </div>\n\n {filePosition === 'below' && fileStrip}\n {imagePosition === 'below' && imageStrip}\n\n {/* Trigger suggestion popover */}\n {activeTrigger && activeTrigger.config.mode === 'dropdown' && (\n <TriggerPopover\n suggestions={suggestions}\n loading={suggestionsLoading}\n error={suggestionsError}\n emptyMessage={activeTrigger.config.emptyMessage}\n selectedIndex={selectedSuggestionIndex}\n onSelect={selectSuggestion}\n onDismiss={dismissTrigger}\n triggerRect={triggerRect}\n triggerChar={activeTrigger.config.char}\n />\n )}\n </div>\n )\n}\n","/**\n * Convenience hook that wires up all the boilerplate state for a PromptArea.\n *\n * Instead of manually managing `useState<Segment[]>`, `useRef<PromptAreaHandle>`,\n * and computing derived values, call `usePromptAreaState()` once and spread\n * `bind` into your `<PromptArea>`.\n *\n * @example\n * ```tsx\n * function ChatInput() {\n * const { bind, plainText, isEmpty, chips, clear, focus } = usePromptAreaState()\n *\n * return (\n * <PromptArea\n * {...bind}\n * onSubmit={() => {\n * sendMessage(plainText)\n * clear()\n * }}\n * />\n * )\n * }\n * ```\n */\n\n'use client'\n\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport type { Segment, ChipSegment, PromptAreaHandle } from './types.ts'\nimport { segmentsToPlainText } from './prompt-area-engine.ts'\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport type UsePromptAreaStateOptions = {\n /** Initial segment value. Defaults to `[]`. */\n initialValue?: Segment[]\n}\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport type PromptAreaBind = {\n /** Ref to attach to PromptArea — gives access to imperative methods. */\n ref: React.RefObject<PromptAreaHandle | null>\n /** Current segment array — pass as `value` prop. */\n value: Segment[]\n /** Setter — pass as `onChange` prop. */\n onChange: (segments: Segment[]) => void\n}\n\nexport type PromptAreaState = {\n /** Props to spread directly onto `<PromptArea {...bind} />`. Contains ref, value, and onChange. */\n bind: PromptAreaBind\n /** Derived plain text representation of the current value. */\n plainText: string\n /** `true` when the value is empty or whitespace-only. */\n isEmpty: boolean\n /** `true` when the value contains at least one chip. */\n hasChips: boolean\n /** All chip segments in the current value. */\n chips: ChipSegment[]\n /** Clear all content (both state and the editor DOM). */\n clear: () => void\n /** Focus the editor. */\n focus: () => void\n /** Blur the editor. */\n blur: () => void\n /** Insert a chip at the current cursor position. */\n insertChip: (chip: Omit<ChipSegment, 'type'>) => void\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function usePromptAreaState(options: UsePromptAreaStateOptions = {}): PromptAreaState {\n const { initialValue = [] } = options\n\n const [value, setValue] = useState<Segment[]>(initialValue)\n const ref = useRef<PromptAreaHandle>(null)\n\n // Derived\n const plainText = useMemo(() => segmentsToPlainText(value), [value])\n\n const isEmpty = useMemo(() => {\n if (value.length === 0) return true\n return value.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n }, [value])\n\n const hasChips = useMemo(() => value.some((seg) => seg.type === 'chip'), [value])\n\n const chips = useMemo(\n () => value.filter((seg): seg is ChipSegment => seg.type === 'chip'),\n [value],\n )\n\n // Bind object — safe to spread onto <PromptArea>\n const bind = useMemo<PromptAreaBind>(() => ({ ref, value, onChange: setValue }), [value])\n\n // Actions that proxy to the imperative handle\n const clear = useCallback(() => {\n if (ref.current) {\n ref.current.clear()\n } else {\n setValue([])\n }\n }, [])\n\n const focus = useCallback(() => ref.current?.focus(), [])\n const blur = useCallback(() => ref.current?.blur(), [])\n\n const insertChip = useCallback(\n (chip: Omit<ChipSegment, 'type'>) => ref.current?.insertChip(chip),\n [],\n )\n\n return {\n bind,\n plainText,\n isEmpty,\n hasChips,\n chips,\n clear,\n focus,\n blur,\n insertChip,\n }\n}\n","/**\n * Pre-built trigger configuration factories for common AI chat patterns.\n *\n * Each factory returns a full `TriggerConfig` with sensible defaults.\n * Pass only what you need to override.\n *\n * @example\n * ```tsx\n * <PromptArea\n * triggers={[\n * mentionTrigger({ onSearch: searchUsers }),\n * commandTrigger({ onSearch: searchCommands }),\n * hashtagTrigger(),\n * ]}\n * />\n * ```\n */\n\nimport type { TriggerConfig, TriggerPosition } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Shared option type — everything in TriggerConfig except the keys each\n// factory sets by default.\n// ---------------------------------------------------------------------------\n\ntype TriggerPresetOptions = Omit<Partial<TriggerConfig>, 'char' | 'position' | 'mode'>\n\n// ---------------------------------------------------------------------------\n// @mention — dropdown at any position\n// ---------------------------------------------------------------------------\n\nexport type MentionTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'@'`. */\n char?: string\n}\n\n/**\n * Creates a **mention** trigger (`@`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * accessible label `\"mention\"`.\n */\nexport function mentionTrigger(opts: MentionTriggerOptions = {}): TriggerConfig {\n const { char = '@', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n accessibilityLabel: 'mention',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// /command — dropdown anywhere (opt into line-start-only with `position`)\n// ---------------------------------------------------------------------------\n\nexport type CommandTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'/'`. */\n char?: string\n /**\n * Where the command trigger is valid. Defaults to `'any'`, so commands fire\n * anywhere a `/` follows whitespace — not just at the start of a line.\n * Set to `'start'` to restrict the dropdown to the very start of the input\n * or immediately after a newline (the classic slash-command behavior).\n */\n position?: TriggerPosition\n}\n\n/**\n * Creates a **command** trigger (`/`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'inline'`,\n * accessible label `\"command\"`.\n *\n * By default commands work everywhere in the input. Pass `position: 'start'`\n * to limit them to the start of a line.\n */\nexport function commandTrigger(opts: CommandTriggerOptions = {}): TriggerConfig {\n const { char = '/', position = 'any', ...rest } = opts\n return {\n char,\n position,\n mode: 'dropdown',\n chipStyle: 'inline',\n accessibilityLabel: 'command',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// #hashtag — dropdown at any position, auto-resolve on space\n// ---------------------------------------------------------------------------\n\nexport type HashtagTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'#'`. */\n char?: string\n}\n\n/**\n * Creates a **hashtag / tag** trigger (`#`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * `resolveOnSpace: true`, accessible label `\"tag\"`.\n */\nexport function hashtagTrigger(opts: HashtagTriggerOptions = {}): TriggerConfig {\n const { char = '#', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n resolveOnSpace: true,\n accessibilityLabel: 'tag',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Generic callback trigger (e.g., for file pickers, model selectors)\n// ---------------------------------------------------------------------------\n\nexport type CallbackTriggerOptions = Omit<Partial<TriggerConfig>, 'mode'> & {\n /** The trigger character. Required. */\n char: string\n}\n\n/**\n * Creates a **callback** trigger that fires `onActivate` instead of showing\n * a dropdown. Useful for opening file pickers, model selectors, etc.\n *\n * Defaults: `position: 'start'`, `mode: 'callback'`.\n */\nexport function callbackTrigger(opts: CallbackTriggerOptions): TriggerConfig {\n const { char, ...rest } = opts\n return {\n char,\n position: 'start',\n mode: 'callback',\n ...rest,\n }\n}\n","import { useImperativeHandle, useMemo, useRef, useState, type ReactNode, type Ref } from 'react'\nimport type { SkillInfo, SlashCommandInfo } from '@workerdeck/protocol'\nimport type { StagedAttachment, UseAttachmentsResult } from '@workerdeck/react'\nimport {\n ArrowUp,\n FileText,\n Paperclip,\n RotateCw,\n Sparkles,\n Square,\n TriangleAlert,\n X,\n} from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { PromptArea } from '../prompt-area/prompt-area.tsx'\nimport { usePromptAreaState } from '../prompt-area/use-prompt-area-state.ts'\nimport { commandTrigger, mentionTrigger } from '../prompt-area/trigger-presets.ts'\nimport type { TriggerSuggestion } from '../prompt-area/types.ts'\nimport { cn } from '../../lib/utils.ts'\nimport { formatBytes } from '../../lib/format.ts'\n\n/** Files matching an `@` query, for the composer's file trigger. Structural so\n * the ui package doesn't have to reach for the protocol's `HostFileMatch`. */\nexport type ComposerFileMatch = { path: string; relative: string }\n\n/** Imperative surface for panels that draft a message the user then finishes —\n * the skills dialog's \"Use this skill\". Nothing here sends. */\nexport type ComposerHandle = {\n /** Append plain text at the caret's end and focus, separating it from\n * whatever is already there. */\n insertText: (text: string) => void\n /** Put the caret in the field, changing nothing. */\n focus: () => void\n}\n\nexport interface ComposerProps {\n /** `attachmentIds` are the staged uploads, in the order they were picked. */\n onSend: (text: string, attachmentIds: string[]) => void\n onInterrupt: () => void\n busy: boolean\n /** Disable input entirely (session failed/closed). */\n disabled?: boolean\n placeholder?: string\n /** Slash commands offered as autocomplete; picked ones render as chips. */\n commands?: SlashCommandInfo[]\n /**\n * Skills offered under a **`$`** popover of their own — codex's sigil, kept\n * separate from `/` because the two behave differently. A skill is a typing\n * aid, not a command: picking one inserts editable text (the skill's own\n * `defaultPrompt` where it has one, else `$name`) and nothing is sent. No\n * engine parses `$skillname` as syntax, which is exactly why these can never\n * resolve to a chip the way `commands` do.\n */\n skills?: SkillInfo[]\n /** Host-file search behind the `@` trigger. Omit to leave `@` inert — a\n * gateway without host files has nothing to complete. */\n onSearchFiles?: (query: string, options: { signal: AbortSignal }) => Promise<ComposerFileMatch[]>\n /** Attachment staging (see `useAttachments`). Omit for a text-only composer. */\n attachments?: UseAttachmentsResult\n /** Left side of the toolbar row (mode selects, …). */\n toolbar?: ReactNode\n /**\n * `'stacked'` (default) gives the buttons a row of their own under the field —\n * right where a toolbar belongs. `'inline'` puts the field and the buttons on\n * ONE line, growing from a single row as the message does: for a host whose\n * session controls live in its own chrome (VS Code's status bar), where the\n * empty composer would otherwise spend two rows saying nothing.\n */\n layout?: 'stacked' | 'inline'\n className?: string\n ref?: Ref<ComposerHandle>\n}\n\n/** CLI names may carry display annotations (e.g. \"foo (MCP)\") the parser rejects. */\nconst cleanName = (name: string) => name.replace(/\\s*\\(MCP\\)$/i, '')\n\n/**\n * What a picked skill types into the composer.\n *\n * The engine's own `defaultPrompt` when it declared one — it knows what its\n * skill wants to be asked — and otherwise `$name`, which is codex's native way\n * of referring to a skill in prompt text: its `skill-creator` documents the form\n * (`Use $skill-x at /path/to/skill-x to solve problem y`) and its own bundled\n * prompts are written that way (\"Use $pdf to …\"). Spelling it the way the engine\n * spells it beats paraphrasing into \"Use the X skill to\".\n *\n * Either way it ends in a space so the caret lands ready for the rest of the\n * sentence, and either way it is ordinary text: nothing here is submitted, and\n * nothing is parsed back out.\n */\nexport function skillPrompt(skill: SkillInfo): string {\n const base = skill.defaultPrompt?.trim() || `$${skill.name}`\n return /\\s$/.test(base) ? base : base + ' '\n}\n\n/** Ranks a haystack set against the typed query: 2 for a prefix hit, 1 for a\n * substring, 0 for no match. Shared so commands and skills sort as one list\n * rather than two concatenated ones. */\nfunction matchScore(query: string, haystacks: string[]): number {\n const needle = query.toLowerCase()\n const lowered = haystacks.map((s) => s.toLowerCase())\n if (lowered.some((h) => h.startsWith(needle))) return 2\n return lowered.some((h) => h.includes(needle)) ? 1 : 0\n}\n\n/**\n * Framed prompt input built on prompt-area's contentEditable.\n *\n * Three completions ride the same field and behave nothing alike. `/` is the\n * CLI's command list and `$` is the engine's skill list — both local, so they\n * filter completely and instantly; `@` is a search against the host filesystem,\n * debounced and abortable so a fast typist makes one request rather than eight.\n *\n * `/` and `$` are separate keys rather than one merged menu, and that mirrors\n * the engines themselves: codex completes skills on `$` and reserves `/` for\n * commands. The behaviours differ too — a command resolves to a **chip**,\n * because the CLI really does parse `/name` out of the message, while a skill\n * resolves to plain editable **text**, because no engine parses `$name` as\n * syntax; it is prose the model reads. Rendering them alike would promise\n * something that does not happen.\n *\n * Files can arrive three ways — the paperclip, a drop, or a paste — because on a\n * desktop all three are things people already do, and the upload starts the\n * moment one lands rather than at send time.\n */\nexport function Composer({\n onSend,\n onInterrupt,\n busy,\n disabled,\n placeholder = 'Message the agent…',\n commands,\n skills,\n onSearchFiles,\n attachments,\n toolbar,\n layout = 'stacked',\n className,\n ref,\n}: ComposerProps) {\n const inline = layout === 'inline'\n const { bind, plainText, isEmpty, clear, focus } = usePromptAreaState()\n const fileInput = useRef<HTMLInputElement>(null)\n const [dragging, setDragging] = useState(false)\n\n useImperativeHandle(\n ref,\n () => ({\n insertText: (text: string) => {\n // A space in front only when there is something to separate from, so a\n // draft into an empty composer doesn't start with one.\n const prefix = plainText.length > 0 && !/\\s$/.test(plainText) ? ' ' : ''\n bind.ref.current?.appendText(prefix + text)\n focus()\n },\n focus,\n }),\n [bind.ref, plainText, focus],\n )\n\n const triggers = useMemo(() => {\n const configured = []\n const usableSkills = (skills ?? []).filter((s) => s.enabled)\n if (commands && commands.length > 0) {\n // The CLI list can contain the same skill name from several sources — first wins.\n const seen = new Set<string>()\n const unique = commands.flatMap((c) => {\n const name = cleanName(c.name)\n if (seen.has(name)) return []\n seen.add(name)\n return [{ ...c, name }]\n })\n configured.push(\n commandTrigger({\n onSearch: (query: string): TriggerSuggestion[] => {\n const scored: Array<{ score: number; suggestion: TriggerSuggestion }> = []\n for (const c of unique) {\n // \"wrapup\" should find \"dev:wrapup\" — the bare half of a\n // namespaced name is what people type.\n const score = matchScore(query, [c.name, ...(c.aliases ?? []), ...c.name.split(':')])\n if (score === 0) continue\n scored.push({\n score,\n suggestion: {\n value: c.name,\n label: `/${c.name}${c.argumentHint ? ` ${c.argumentHint}` : ''}`,\n description: c.description,\n },\n })\n }\n scored.sort((a, b) => b.score - a.score)\n return scored.map(({ suggestion }) => suggestion)\n },\n // Chip text renders as trigger + displayText — return the bare name so\n // the chip reads \"/name\" (label carries the argument hint for the menu).\n onSelect: (suggestion) => suggestion.value,\n chipClassName: 'font-mono',\n }),\n )\n }\n if (usableSkills.length > 0) {\n // `$`, not `/`, because that is codex's own sigil — its TUI completes\n // skills on `$` and reserves `/` for commands, and its bundled prompts\n // refer to skills that way in prose (\"Use $pdf to …\"). Matching it means\n // muscle memory transfers, and it keeps the two lists from being one\n // ambiguous menu of things that behave differently.\n configured.push(\n commandTrigger({\n char: '$',\n accessibilityLabel: 'skill',\n onSearch: (query: string): TriggerSuggestion[] => {\n const scored: Array<{ score: number; suggestion: TriggerSuggestion }> = []\n for (const skill of usableSkills) {\n const score = matchScore(query, [skill.name, ...skill.name.split(/[-:_]/)])\n if (score === 0) continue\n const summary = skill.shortDescription ?? skill.description\n scored.push({\n score,\n suggestion: {\n value: skill.name,\n label: skill.displayName ?? skill.name,\n description: summary\n ? `Skill · ${summary}`\n : 'Skill · inserts a message you can edit',\n icon: <Sparkles className='size-3.5 text-fg-3' />,\n },\n })\n }\n scored.sort((a, b) => b.score - a.score)\n return scored.map(({ suggestion }) => suggestion)\n },\n // Always text, never a chip: a skill is not wire syntax the engine\n // parses back out, so what lands has to stay ordinary editable prose.\n // Returning a string unconditionally is what makes this trigger's\n // whole list behave that way.\n insertAsText: (suggestion) => {\n const skill = usableSkills.find((s) => s.name === suggestion.value)\n return skill ? skillPrompt(skill) : `$${suggestion.value} `\n },\n }),\n )\n }\n if (onSearchFiles) {\n configured.push(\n mentionTrigger({\n // A round trip per keystroke would be eight requests for one word; the\n // route is cheap but not free.\n searchDebounceMs: 150,\n onSearch: async (query, options) => {\n const matches = await onSearchFiles(query, options)\n return matches.map((match) => ({\n value: match.relative,\n label: match.relative,\n description: match.path,\n }))\n },\n onSelect: (suggestion) => suggestion.value,\n chipStyle: 'inline',\n chipClassName: 'font-mono',\n emptyMessage: 'No matching files',\n }),\n )\n }\n return configured.length > 0 ? configured : undefined\n }, [commands, skills, onSearchFiles])\n\n const staged = attachments?.items ?? []\n // A photo on its own is a message — send doesn't wait for text. It does wait\n // for the upload, since an id that hasn't landed can't be named.\n const canSend =\n !disabled &&\n (!isEmpty || staged.length > 0) &&\n !attachments?.uploading &&\n !attachments?.hasFailure\n\n const submit = () => {\n if (!canSend) return\n onSend(plainText.trim(), attachments?.readyIds ?? [])\n attachments?.clear()\n clear()\n focus()\n }\n\n const pick = (files: FileList | null) => {\n if (files && files.length > 0) attachments?.add(files)\n }\n\n // Built once, placed twice: the stacked layout gives these a toolbar row, the\n // inline one sets them either side of the field. An attach affordance the\n // engine has no meaning for is not a choice — the capability record decides it\n // exists.\n const fileField =\n attachments && !attachments.disabled ? (\n <input\n ref={fileInput}\n type='file'\n multiple\n accept={attachments.accept || undefined}\n className='hidden'\n onChange={(e) => {\n pick(e.target.files)\n // Re-picking the same file must fire `change` again.\n e.target.value = ''\n }}\n />\n ) : null\n const canAttach = !!attachments && !attachments.disabled\n\n const attach = canAttach ? (\n <>\n {fileField}\n {inline ? (\n // The glyph sits in the same 14px gutter the transcript's markers use,\n // so the typed line starts on the column the conversation does.\n <GlyphButton label='Attach files' disabled={disabled} onClick={() => fileInput.current?.click()}>\n +\n </GlyphButton>\n ) : (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Attach files'\n disabled={disabled}\n onClick={() => fileInput.current?.click()}>\n <Paperclip className='size-4' />\n </Button>\n )}\n </>\n ) : null\n\n const interrupting = busy && !canSend\n const submitButton = inline ? (\n // Terminal furniture rather than chat furniture: a glyph that lights up on\n // hover/focus instead of a filled pill. `↵` is what sends, `■` is what stops\n // — the same two symbols the keyboard and a shell already use.\n <GlyphButton\n label={interrupting ? 'Interrupt' : 'Send'}\n disabled={!interrupting && !canSend}\n onClick={interrupting ? onInterrupt : submit}\n className={interrupting ? 'text-warning' : canSend ? 'text-accent' : undefined}>\n {interrupting ? '■' : '↵'}\n </GlyphButton>\n ) : interrupting ? (\n <Button\n variant='outline'\n size='icon-sm'\n aria-label='Interrupt'\n className='rounded-full'\n onClick={onInterrupt}>\n <Square className='size-3' />\n </Button>\n ) : (\n <Button\n size='icon-sm'\n aria-label='Send'\n className='rounded-full'\n disabled={!canSend}\n onClick={submit}>\n <ArrowUp className='size-4' />\n </Button>\n )\n\n return (\n <div data-slot='composer' className={cn('px-3 pb-3', className)}>\n <div\n onDragOver={(e) => {\n if (attachments && !attachments.disabled) {\n e.preventDefault()\n setDragging(true)\n }\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n if (!attachments || attachments.disabled) return\n e.preventDefault()\n setDragging(false)\n pick(e.dataTransfer.files)\n }}\n className={cn(\n 'mx-auto w-full max-w-[var(--wd-content-max-w,48rem)] overflow-hidden rounded-lg border border-border bg-bg shadow-(--shadow-xs)',\n 'transition-colors focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30',\n dragging && 'border-ring ring-2 ring-ring/30',\n disabled && 'opacity-60',\n )}>\n {/* Above the field, like the picture you are talking about should be. */}\n {staged.length > 0 && attachments ? (\n <AttachmentStrip attachments={attachments} />\n ) : null}\n {inline ? (\n // One row until the message needs more: the field grows into the space\n // rather than the frame reserving it, and the buttons stay bottom-\n // aligned as it does. 4px of padding and gap all round against 24px\n // buttons and a 24px line box (20px of text, 2px either side) — on a\n // single line everything centres without anything being nudged.\n <div className='flex items-end gap-1 p-1'>\n {attach}\n <PromptArea\n {...bind}\n triggers={triggers}\n onSubmit={submit}\n disabled={disabled}\n placeholder={disabled ? 'Session ended' : placeholder}\n minHeight={20}\n maxHeight={192}\n aria-label='Message the agent'\n className='min-w-0 flex-1 py-0.5 text-body-sm text-text'\n onImagePaste={(file) => attachments?.add([file])}\n />\n {submitButton}\n </div>\n ) : (\n <>\n <PromptArea\n {...bind}\n triggers={triggers}\n onSubmit={submit}\n disabled={disabled}\n placeholder={disabled ? 'Session ended' : placeholder}\n minHeight={28}\n maxHeight={192}\n aria-label='Message the agent'\n className='px-3 pt-2.5 pb-1 text-body-sm text-text'\n onImagePaste={(file) => attachments?.add([file])}\n />\n <div className='flex items-center justify-between gap-2 px-2 pb-2'>\n <div className='flex min-w-0 items-center gap-1'>\n {attach}\n {toolbar}\n </div>\n {submitButton}\n </div>\n </>\n )}\n </div>\n {attachments?.error ? (\n <div className='mx-auto mt-1 flex w-full max-w-[var(--wd-content-max-w,48rem)] items-center gap-2 text-label text-danger'>\n <TriangleAlert className='size-3 shrink-0' />\n <span className='min-w-0 flex-1'>{attachments.error}</span>\n <button\n type='button'\n onClick={attachments.dismissError}\n aria-label='Dismiss'\n className='shrink-0 opacity-70 hover:opacity-100'>\n <X className='size-3' />\n </button>\n </div>\n ) : (\n <div\n data-slot='composer-hint'\n className='mx-auto mt-1 w-full max-w-[var(--wd-content-max-w,48rem)] text-center text-label text-fg-4'>\n Enter to send · Shift+Enter for a new line\n </div>\n )}\n </div>\n )\n}\n\n/**\n * A composer action as a **character**, for the inline (terminal) layout: no\n * pill, no border, nothing drawn until you reach for it — the surface only\n * appears on hover/focus/press, which is how a terminal's own affordances\n * behave. Sized to the transcript's gutter glyph (14px) so the row's furniture\n * lines up with the conversation above it rather than towering over it.\n */\nfunction GlyphButton({\n label,\n disabled,\n onClick,\n className,\n children,\n}: {\n label: string\n disabled?: boolean\n onClick: () => void\n className?: string\n children: ReactNode\n}) {\n return (\n <button\n type='button'\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n 'flex size-6 shrink-0 items-center justify-center rounded-sm font-mono text-body-sm leading-none',\n 'text-fg-3 transition-colors outline-none select-none',\n 'hover:bg-surface-hover hover:text-fg-1 focus-visible:bg-surface-hover focus-visible:text-fg-1',\n 'active:bg-accent-bg disabled:pointer-events-none disabled:opacity-40',\n className,\n )}>\n {children}\n </button>\n )\n}\n\n/** Staged files as a scrolling row of chips above the field. The thumbnail is\n * the local blob, so nothing here waits on the network; the upload's state rides\n * on top of it and the ✕ takes it back off. */\nfunction AttachmentStrip({ attachments }: { attachments: UseAttachmentsResult }) {\n return (\n <div className='flex gap-2 overflow-x-auto border-b border-border px-2 py-2'>\n {attachments.items.map((item) => (\n <AttachmentChip\n key={item.key}\n item={item}\n onRetry={() => attachments.retry(item.key)}\n onRemove={() => attachments.remove(item.key)}\n />\n ))}\n </div>\n )\n}\n\nfunction AttachmentChip({\n item,\n onRetry,\n onRemove,\n}: {\n item: StagedAttachment\n onRetry: () => void\n onRemove: () => void\n}) {\n const failed = item.status === 'failed'\n return (\n <div\n className='group relative shrink-0'\n title={failed ? `${item.name} — ${item.error}` : `${item.name} · ${formatBytes(item.bytes)}`}>\n <div\n className={cn(\n 'flex size-14 items-center justify-center overflow-hidden rounded-md border border-border bg-surface',\n failed && 'border-danger/50',\n )}>\n {item.previewUrl ? (\n <img src={item.previewUrl} alt={item.name} className='size-full object-cover' />\n ) : (\n <div className='flex flex-col items-center gap-0.5 text-fg-3'>\n <FileText className='size-4' />\n <span className='max-w-12 truncate text-[9px] font-semibold uppercase'>\n {extensionOf(item.name)}\n </span>\n </div>\n )}\n </div>\n {item.status === 'uploading' ? (\n <div className='absolute inset-0 flex items-center justify-center rounded-md bg-black/35'>\n <Spinner className='size-4 text-white' />\n </div>\n ) : null}\n {failed ? (\n <button\n type='button'\n onClick={onRetry}\n aria-label={`Retry ${item.name}`}\n className='absolute inset-0 flex items-center justify-center rounded-md bg-black/45 text-warning'>\n <RotateCw className='size-4' />\n </button>\n ) : null}\n <button\n type='button'\n onClick={onRemove}\n aria-label={`Remove ${item.name}`}\n className='absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full border border-border bg-surface text-fg-3 shadow-(--shadow-xs) hover:text-fg-1'>\n <X className='size-2.5' />\n </button>\n </div>\n )\n}\n\nconst extensionOf = (name: string) => {\n const dot = name.lastIndexOf('.')\n return dot > 0 ? name.slice(dot + 1).toUpperCase() : 'FILE'\n}\n","import type { ContextUsage } from '@workerdeck/protocol'\nimport { Dialog, DialogBody, DialogContent, DialogHeader } from '../ui/Dialog.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatTokens } from '../../lib/format.ts'\n\nexport interface ContextDialogProps {\n usage?: ContextUsage\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/** The CLI reports category colors as its own theme token names ('inactive',\n * 'promptBorder', ...), not CSS colors — only pass through what CSS can render. */\nconst cssColor = (color: string): string | undefined =>\n typeof CSS !== 'undefined' && CSS.supports('color', color) ? color : undefined\n\nconst usageTint = (pct: number) => (pct >= 90 ? 'bg-danger' : pct >= 70 ? 'bg-warning' : 'bg-accent')\n\n/**\n * What is in the model's context window right now, category by category.\n *\n * One of the three panels the status bar opens. Context, usage and session info\n * are different questions asked at different moments, so they are different\n * screens rather than one \"details\" list you scroll past two answers to reach.\n */\nexport function ContextDialog({ usage, open, onOpenChange }: ContextDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader title='Context' description={usage?.model} />\n <DialogBody>\n {!usage ? (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n No reading yet — the context window is measured after a turn completes.\n </p>\n ) : (\n <>\n <div className='flex items-baseline justify-between gap-4'>\n <span className='text-label text-fg-3'>Used</span>\n <span className='font-mono text-body-sm text-fg-1'>\n {formatTokens(usage.totalTokens)} / {formatTokens(usage.maxTokens)} ·{' '}\n {usage.percentage.toFixed(0)}%\n </span>\n </div>\n <div className='mt-2 h-2 overflow-hidden rounded-full bg-border'>\n <div\n className={cn('h-full rounded-full', usageTint(usage.percentage))}\n style={{ width: `${Math.min(100, Math.max(2, usage.percentage))}%` }}\n />\n </div>\n {/* Codex reports occupancy with no breakdown, so an engine can send\n a real reading and an empty `categories`. A \"Breakdown\" heading\n over nothing reads as a failed load; the row above already says\n everything that is known. */}\n {usage.categories.length > 0 ? (\n <div className='mt-5'>\n <h3 className='text-label font-medium text-fg-3'>Breakdown</h3>\n <div className='mt-2 flex flex-col gap-3'>\n {usage.categories.map((category) => {\n const share = (category.tokens / Math.max(usage.maxTokens, 1)) * 100\n const color = cssColor(category.color)\n return (\n <div key={category.name}>\n <div className='flex items-baseline justify-between gap-3'>\n <span className='flex min-w-0 items-center gap-2'>\n <span\n className='size-2 shrink-0 rounded-full bg-fg-4'\n style={color ? { backgroundColor: color } : undefined}\n />\n <span className='truncate text-body-sm text-fg-1'>\n {category.name}\n </span>\n </span>\n <span className='shrink-0 font-mono text-label text-fg-3'>\n {formatTokens(category.tokens)}\n </span>\n </div>\n <div className='mt-1 h-1 overflow-hidden rounded-full bg-border'>\n <div\n className='h-full rounded-full bg-fg-4'\n style={{\n width: `${Math.min(100, share)}%`,\n backgroundColor: color,\n }}\n />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n ) : null}\n </>\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n","import { useCallback, useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { HostDirEntry, HostFileMatch } from '@workerdeck/protocol'\nimport { ChevronLeft, File, Folder, Link2, Search } from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { CodeBlock } from '../ui/CodeBlock.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader } from '../ui/Dialog.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { formatBytes } from '../../lib/format.ts'\n\nexport interface HostFilesDialogProps {\n client: WorkerDeckClient\n /** The session's working directory — the browser is rooted here. */\n cwd: string | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * Browse the project the session is working in.\n *\n * Deliberately rooted at the session's cwd rather than at the server's\n * `hostFiles.roots`: the roots are the *security* boundary (the server enforces\n * them on every request), but what someone wants while watching an agent is this\n * project's tree. Read-only — writing is a separate server opt-in and not\n * something a session viewer should be doing behind the agent's back.\n */\nexport function HostFilesDialog({ client, cwd, open, onOpenChange }: HostFilesDialogProps) {\n const [path, setPath] = useState<string | undefined>(cwd)\n const [entries, setEntries] = useState<HostDirEntry[]>([])\n const [truncated, setTruncated] = useState(false)\n const [query, setQuery] = useState('')\n const [matches, setMatches] = useState<HostFileMatch[] | undefined>()\n const [file, setFile] = useState<{ path: string; content: string; bytes: number } | undefined>()\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | undefined>()\n\n const list = useCallback(\n async (target: string) => {\n setLoading(true)\n setError(undefined)\n try {\n const response = await client.listHostDir(target)\n setPath(response.path)\n setEntries(response.entries)\n setTruncated(response.truncated ?? false)\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not read that directory')\n } finally {\n setLoading(false)\n }\n },\n [client],\n )\n\n // Opening resets the browser to the session's directory. Navigation from here\n // is explicit (`list`) rather than an effect on `path`, so walking into a\n // folder is one request and not two.\n useEffect(() => {\n if (!open) return\n setFile(undefined)\n setQuery('')\n setMatches(undefined)\n setError(undefined)\n if (cwd) void list(cwd)\n }, [open, cwd, list])\n\n // Debounced, and only while there is something to search for; an empty box is\n // \"show me the directory again\", not \"search for everything\".\n useEffect(() => {\n if (!open || !cwd) return\n const q = query.trim()\n if (!q) {\n setMatches(undefined)\n return\n }\n const timer = setTimeout(() => {\n client\n .findHostFiles(cwd, q, 40)\n .then((response) => setMatches(response.matches))\n .catch(() => setMatches([]))\n }, 150)\n return () => clearTimeout(timer)\n }, [client, cwd, query, open])\n\n const openFile = async (target: string) => {\n setLoading(true)\n setError(undefined)\n try {\n const response = await client.readHostFile(target)\n setFile({\n path: response.path,\n bytes: response.bytes,\n content:\n response.encoding === 'utf8'\n ? response.content\n : '(binary file — not shown)',\n })\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not read that file')\n } finally {\n setLoading(false)\n }\n }\n\n const parent = path && cwd && path !== cwd ? path.slice(0, path.lastIndexOf('/')) || '/' : undefined\n const shown = matches ?? entries\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent size='lg'>\n <DialogHeader\n title={file ? file.path.split('/').pop()! : 'Files'}\n description={file ? file.path : path}\n actions={\n file ? (\n <Button variant='ghost' size='xs' onClick={() => setFile(undefined)}>\n <ChevronLeft className='size-3.5' />\n Back\n </Button>\n ) : parent ? (\n <Button variant='ghost' size='xs' onClick={() => void list(parent)}>\n <ChevronLeft className='size-3.5' />\n Up\n </Button>\n ) : null\n }\n />\n <DialogBody className='flex flex-col gap-3'>\n {error ? (\n <div className='rounded-md bg-danger-bg px-3 py-2 text-body-sm text-danger'>{error}</div>\n ) : null}\n\n {file ? (\n <>\n <p className='text-label text-fg-4'>{formatBytes(file.bytes)}</p>\n <CodeBlock code={file.content} label={file.path.split('/').pop()} />\n </>\n ) : (\n <>\n <div className='relative'>\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 this project…'\n className='pl-7'\n spellCheck={false}\n />\n </div>\n {loading && shown.length === 0 ? (\n <div className='py-6 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n ) : shown.length === 0 ? (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n {matches ? 'No matching files.' : 'This directory is empty.'}\n </p>\n ) : (\n <ul className='flex flex-col'>\n {matches\n ? matches.map((match) => (\n <li key={match.path}>\n <button\n type='button'\n onClick={() => void openFile(match.path)}\n className='flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-surface-hover'>\n <File className='size-3.5 shrink-0 text-fg-4' />\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {match.relative}\n </span>\n </button>\n </li>\n ))\n : entries.map((entry) => (\n <li key={entry.path}>\n <button\n type='button'\n onClick={() =>\n entry.type === 'dir' ? void list(entry.path) : void openFile(entry.path)\n }\n className='flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-surface-hover'>\n <EntryIcon type={entry.type} />\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {entry.name}\n {entry.type === 'dir' ? '/' : ''}\n </span>\n {entry.bytes !== undefined ? (\n <span className='shrink-0 text-label text-fg-4'>\n {formatBytes(entry.bytes)}\n </span>\n ) : null}\n </button>\n </li>\n ))}\n </ul>\n )}\n {truncated && !matches ? (\n <p className='text-label text-fg-4'>\n More entries than the server will return — use the search box.\n </p>\n ) : null}\n </>\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\n/** A symlink is reported as itself and never silently resolved — following it is\n * the next request's problem, and that request is refused if it escapes the roots. */\nfunction EntryIcon({ type }: { type: HostDirEntry['type'] }) {\n if (type === 'dir') return <Folder className='size-3.5 shrink-0 text-accent' />\n if (type === 'symlink') return <Link2 className='size-3.5 shrink-0 text-fg-4' />\n return <File className='size-3.5 shrink-0 text-fg-4' />\n}\n","import { useCallback, useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type {\n McpServerActionRequest,\n McpServerStatusInfo,\n McpServerToolInfo,\n} from '@workerdeck/protocol'\nimport { ChevronLeft, ChevronRight, Power, PowerOff, RotateCw } from 'lucide-react'\nimport { Badge, type BadgeProps } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader, DialogRow } from '../ui/Dialog.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface McpDialogProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n /**\n * Whether this engine can reconnect/enable/disable a server\n * (`EngineCapabilities.mcpServerActions`). False renders the panel read-only:\n * codex reports rich status but exposes no per-server action, and buttons\n * that 501 are worse than buttons that aren't there.\n */\n canManageServers?: boolean\n}\n\n/** The engine's status vocabulary is open — anything unrecognised renders\n * neutrally rather than being forced into one of these. */\nconst STATUS_VARIANT: Record<string, NonNullable<BadgeProps['variant']>> = {\n connected: 'success',\n failed: 'danger',\n 'needs-auth': 'warning',\n pending: 'info',\n disabled: 'neutral',\n}\n\n/**\n * The session's MCP servers, at the CLI's own `/mcp` depth: servers → one server\n * → its tools → one tool, with Reconnect / Enable / Disable where they apply.\n *\n * Two things vary by engine rather than being fixed here. **The actions** exist\n * only where the engine has them (`canManageServers`): codex reports rich status\n * but has no per-server reconnect or toggle, so its panel is read-only. And\n * **tool parameters** appear only where the engine reports a schema — codex\n * returns each tool's full JSON Schema, the Agent SDK returns none at all, so\n * the tool view either renders it or says why it can't, rather than leaving a\n * silent gap or claiming the absence is universal.\n */\nexport function McpDialog({\n client,\n sessionId,\n open,\n onOpenChange,\n canManageServers = true,\n}: McpDialogProps) {\n const [servers, setServers] = useState<McpServerStatusInfo[] | undefined>()\n const [error, setError] = useState<string | undefined>()\n const [loading, setLoading] = useState(false)\n const [busyServer, setBusyServer] = useState<string | undefined>()\n // The drill-down, by name rather than by object — an action replaces the whole\n // list, and a held reference would go stale on the first Reconnect.\n const [selectedServer, setSelectedServer] = useState<string | undefined>()\n const [selectedTool, setSelectedTool] = useState<string | undefined>()\n\n const load = useCallback(async () => {\n if (!sessionId) return\n setLoading(true)\n setError(undefined)\n try {\n setServers(await client.listMcpServers(sessionId))\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not read MCP status')\n } finally {\n setLoading(false)\n }\n }, [client, sessionId])\n\n useEffect(() => {\n if (!open) return\n setSelectedServer(undefined)\n setSelectedTool(undefined)\n void load()\n }, [open, load])\n\n const act = async (name: string, action: McpServerActionRequest['action']) => {\n if (!sessionId) return\n setBusyServer(name)\n setError(undefined)\n try {\n setServers(await client.mcpServerAction(sessionId, name, action))\n } catch (e) {\n setError(e instanceof Error ? e.message : `Could not ${action} ${name}`)\n } finally {\n setBusyServer(undefined)\n }\n }\n\n const server = servers?.find((s) => s.name === selectedServer)\n const tool = server?.tools?.find((t) => t.name === selectedTool)\n const title = tool?.name ?? server?.name ?? 'MCP servers'\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader\n title={title}\n description={\n tool ? `${server?.name} tool` : server ? server.serverInfo?.name : undefined\n }\n actions={\n selectedServer ? (\n <Button\n variant='ghost'\n size='xs'\n onClick={() => (selectedTool ? setSelectedTool(undefined) : setSelectedServer(undefined))}>\n <ChevronLeft className='size-3.5' />\n Back\n </Button>\n ) : (\n <Button variant='ghost' size='xs' onClick={() => void load()} disabled={loading}>\n {loading ? <Spinner className='size-3 text-current' /> : <RotateCw className='size-3' />}\n Refresh\n </Button>\n )\n }\n />\n <DialogBody>\n {error ? (\n <div className='mb-3 rounded-md bg-danger-bg px-3 py-2 text-body-sm text-danger'>\n {error}\n </div>\n ) : null}\n {tool ? (\n <ToolView tool={tool} />\n ) : server ? (\n <ServerView\n server={server}\n busy={busyServer === server.name}\n canManage={canManageServers}\n onAct={(action) => void act(server.name, action)}\n onSelectTool={setSelectedTool}\n />\n ) : error && !servers ? (\n // The strip above already says what went wrong. Falling through to\n // the list here would print \"No MCP servers configured\" underneath\n // it — a claim about the operator's config that a failed request\n // gives us no standing to make.\n null\n ) : (\n <ServerList\n servers={servers}\n loading={loading}\n onSelect={setSelectedServer}\n />\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction ServerList({\n servers,\n loading,\n onSelect,\n}: {\n servers: McpServerStatusInfo[] | undefined\n loading: boolean\n onSelect: (name: string) => void\n}) {\n if (loading && !servers) {\n return (\n <div className='py-6 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n )\n }\n if (!servers?.length) {\n return (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n No MCP servers configured for this session.\n </p>\n )\n }\n // Grouped by where they were configured, like the CLI's own screen.\n const scopes = [...new Set(servers.map((s) => s.scope ?? 'other'))]\n return (\n <div className='flex flex-col gap-4'>\n {scopes.map((scope) => (\n <div key={scope}>\n <h3 className='text-label font-medium text-fg-3 capitalize'>{scope}</h3>\n <ul className='mt-1 flex flex-col'>\n {servers\n .filter((s) => (s.scope ?? 'other') === scope)\n .map((s) => (\n <li key={s.name}>\n <button\n type='button'\n onClick={() => onSelect(s.name)}\n className='flex w-full items-center gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-surface-hover'>\n <span className='min-w-0 flex-1 truncate text-body-sm text-fg-1'>{s.name}</span>\n {s.tools?.length ? (\n <span className='shrink-0 text-label text-fg-4'>{s.tools.length} tools</span>\n ) : null}\n <Badge variant={STATUS_VARIANT[s.status] ?? 'neutral'} dot className='shrink-0'>\n {s.status}\n </Badge>\n <ChevronRight className='size-3.5 shrink-0 text-fg-4' />\n </button>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n )\n}\n\nfunction ServerView({\n server,\n busy,\n canManage,\n onAct,\n onSelectTool,\n}: {\n server: McpServerStatusInfo\n busy: boolean\n canManage: boolean\n onAct: (action: McpServerActionRequest['action']) => void\n onSelectTool: (name: string) => void\n}) {\n const disabled = server.status === 'disabled'\n return (\n <div className='flex flex-col gap-4'>\n <div>\n <DialogRow label='Status'>\n <Badge variant={STATUS_VARIANT[server.status] ?? 'neutral'} dot>\n {server.status}\n </Badge>\n </DialogRow>\n {server.transport ? <DialogRow label='Transport'>{server.transport}</DialogRow> : null}\n {server.scope ? <DialogRow label='Scope'>{server.scope}</DialogRow> : null}\n {server.serverInfo ? (\n <DialogRow label='Server'>\n {server.serverInfo.name} {server.serverInfo.version}\n </DialogRow>\n ) : null}\n {server.command ? (\n <DialogRow label='Command' mono>\n {[server.command, ...(server.args ?? [])].join(' ')}\n </DialogRow>\n ) : null}\n {server.url ? (\n <DialogRow label='URL' mono>\n {server.url}\n </DialogRow>\n ) : null}\n </div>\n\n {server.error ? (\n <div className='rounded-md bg-danger-bg px-3 py-2 text-body-sm break-words text-danger'>\n {server.error}\n </div>\n ) : null}\n\n {/* Absent, not disabled, when the engine has no per-server action: a\n greyed-out Reconnect invites the question \"why can't I?\" on every\n visit, where nothing at all reads as \"this engine works differently\". */}\n {canManage ? (\n <div className='flex gap-2'>\n <Button variant='outline' size='sm' disabled={busy} onClick={() => onAct('reconnect')}>\n {busy ? <Spinner className='size-3 text-current' /> : <RotateCw className='size-3' />}\n Reconnect\n </Button>\n <Button\n variant='outline'\n size='sm'\n disabled={busy}\n onClick={() => onAct(disabled ? 'enable' : 'disable')}>\n {disabled ? <Power className='size-3' /> : <PowerOff className='size-3' />}\n {disabled ? 'Enable' : 'Disable'}\n </Button>\n </div>\n ) : null}\n\n <div>\n <h3 className='text-label font-medium text-fg-3'>Tools</h3>\n {!server.tools?.length ? (\n <p className='py-2 text-body-sm text-fg-4'>\n {server.status === 'connected'\n ? 'This server exposes no tools.'\n : 'Tools are listed once the server connects.'}\n </p>\n ) : (\n <ul className='mt-1 flex flex-col'>\n {server.tools.map((t) => (\n <li key={t.name}>\n <button\n type='button'\n onClick={() => onSelectTool(t.name)}\n className='flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-surface-hover'>\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {t.name}\n </span>\n <ChevronRight className='size-3.5 shrink-0 text-fg-4' />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n )\n}\n\nfunction ToolView({ tool }: { tool: McpServerToolInfo }) {\n const annotations = tool.annotations\n return (\n <div className='flex flex-col gap-3'>\n {tool.description ? (\n <p className='text-body-sm whitespace-pre-wrap text-fg-2'>{tool.description}</p>\n ) : (\n <p className='text-body-sm text-fg-4'>This tool carries no description.</p>\n )}\n {annotations ? (\n <div className='flex flex-wrap gap-1.5'>\n {annotations.readOnly ? <Badge variant='success'>read-only</Badge> : null}\n {annotations.destructive ? <Badge variant='danger'>destructive</Badge> : null}\n {annotations.openWorld ? <Badge variant='warning'>open world</Badge> : null}\n </div>\n ) : null}\n {/* Engine-dependent, and said as such: codex returns each tool's full\n JSON Schema, the Agent SDK returns none at all. So this is a real\n section where one exists and an explanation where it doesn't — never a\n silent gap, and never a claim that no engine has them. */}\n {tool.inputSchema !== undefined ? (\n <div>\n <h3 className='text-label font-medium text-fg-3'>Parameters</h3>\n <pre className='mt-1 max-h-64 overflow-auto rounded-md bg-surface px-3 py-2 text-label text-fg-2'>\n {safeSchema(tool.inputSchema)}\n </pre>\n </div>\n ) : (\n <p className={cn('text-label text-fg-4')}>\n Parameters aren’t available: this engine names and describes each tool but reports no\n input schema.\n </p>\n )}\n </div>\n )\n}\n\n/** The schema is an opaque JSON document from another process — pretty-print it,\n * and never let an unserializable value take the dialog down with it. */\nfunction safeSchema(schema: unknown): string {\n try {\n return JSON.stringify(schema, null, 2)\n } catch {\n return String(schema)\n }\n}\n","import { useState } from 'react'\nimport type { SkillInfo } from '@workerdeck/protocol'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader, DialogRow } from '../ui/Dialog.tsx'\n\nexport interface SkillsDialogProps {\n skills: SkillInfo[] | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Insert a skill's opening message into the composer, if the host offers\n * that. Omit and the dialog is read-only. */\n onUse?: (skill: SkillInfo) => void\n}\n\n/** Where the skill came from. The engine's set is open, so an unrecognised\n * scope renders as itself rather than being forced into a bucket. */\nconst SCOPE_LABEL: Record<string, string> = {\n user: 'Personal',\n repo: 'This project',\n system: 'System',\n admin: 'Managed',\n}\n\n/**\n * What this session's engine can do beyond its own tools: the skills it found,\n * grouped by where they came from, with one drilled-down view each.\n *\n * The framing matters more here than in most panels. A skill is **not** a\n * command — the model decides to use one by reading its description, and there\n * is no wire syntax that invokes it. So this screen is a *discovery* surface,\n * and the one action it offers (\"Use this skill\") is honest about being a\n * drafting aid: it types a message for the operator to edit and send.\n *\n * Fed from the session's `skills` event rather than a REST route, because that\n * is the channel the engine refreshes on its own when a skill changes on disk.\n */\nexport function SkillsDialog({ skills, open, onOpenChange, onUse }: SkillsDialogProps) {\n const [selected, setSelected] = useState<string | undefined>()\n const skill = skills?.find((s) => s.name === selected)\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) setSelected(undefined)\n onOpenChange(next)\n }}>\n <DialogContent>\n <DialogHeader\n title={skill ? (skill.displayName ?? skill.name) : 'Skills'}\n description={\n skill\n ? skill.name !== (skill.displayName ?? skill.name)\n ? skill.name\n : undefined\n : 'Capabilities the agent can choose to use. Not commands — the model picks them from their descriptions.'\n }\n actions={\n skill ? (\n <Button variant='ghost' size='xs' onClick={() => setSelected(undefined)}>\n <ChevronLeft className='size-3.5' />\n Back\n </Button>\n ) : undefined\n }\n />\n <DialogBody>\n {skill ? (\n <SkillView skill={skill} onUse={onUse} onUsed={() => onOpenChange(false)} />\n ) : (\n <SkillList skills={skills} onSelect={setSelected} />\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction SkillList({\n skills,\n onSelect,\n}: {\n skills: SkillInfo[] | undefined\n onSelect: (name: string) => void\n}) {\n if (!skills) {\n // Not \"none\" — codex can only list skills over a live child, which it does\n // not spawn until the session has something to do. Saying so beats an empty\n // list that reads as \"you have no skills\".\n return (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n Skills are listed once the session connects — send a message first.\n </p>\n )\n }\n if (skills.length === 0) {\n return <p className='py-6 text-center text-body-sm text-fg-4'>This session found no skills.</p>\n }\n const scopes = [...new Set(skills.map((s) => s.scope ?? 'other'))]\n return (\n <div className='flex flex-col gap-4'>\n {scopes.map((scope) => (\n <div key={scope}>\n <h3 className='text-label font-medium text-fg-3'>{SCOPE_LABEL[scope] ?? scope}</h3>\n <ul className='mt-1 flex flex-col'>\n {skills\n .filter((s) => (s.scope ?? 'other') === scope)\n .map((s) => (\n <li key={s.name}>\n <button\n type='button'\n onClick={() => onSelect(s.name)}\n className='flex w-full items-start gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-surface-hover'>\n <span className='min-w-0 flex-1'>\n <span className='block truncate text-body-sm text-fg-1'>\n {s.displayName ?? s.name}\n </span>\n {s.shortDescription ?? s.description ? (\n <span className='block truncate text-label text-fg-4'>\n {s.shortDescription ?? s.description}\n </span>\n ) : null}\n </span>\n {/* Listed but switched off: a different answer from absent,\n and the one an operator hunting for a missing skill needs. */}\n {!s.enabled ? (\n <Badge variant='neutral' className='mt-0.5 shrink-0'>\n off\n </Badge>\n ) : null}\n <ChevronRight className='mt-0.5 size-3.5 shrink-0 text-fg-4' />\n </button>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n )\n}\n\nfunction SkillView({\n skill,\n onUse,\n onUsed,\n}: {\n skill: SkillInfo\n onUse?: (skill: SkillInfo) => void\n onUsed: () => void\n}) {\n return (\n <div className='flex flex-col gap-4'>\n <div>\n {skill.scope ? (\n <DialogRow label='Scope'>{SCOPE_LABEL[skill.scope] ?? skill.scope}</DialogRow>\n ) : null}\n <DialogRow label='Status'>\n <Badge variant={skill.enabled ? 'success' : 'neutral'} dot>\n {skill.enabled ? 'enabled' : 'disabled'}\n </Badge>\n </DialogRow>\n </div>\n {skill.description ? (\n <div>\n <h3 className='text-label font-medium text-fg-3'>Description</h3>\n {/* Shown verbatim because this is the text the MODEL selects on —\n paraphrasing it here would describe a different skill than the one\n the agent is reading about. */}\n <p className='mt-1 text-body-sm whitespace-pre-wrap text-fg-2'>{skill.description}</p>\n </div>\n ) : (\n <p className='text-body-sm text-fg-4'>This skill carries no description.</p>\n )}\n {onUse && skill.enabled ? (\n <div>\n <Button\n variant='outline'\n size='sm'\n onClick={() => {\n onUse(skill)\n onUsed()\n }}>\n Use this skill\n </Button>\n <p className='mt-1.5 text-label text-fg-4'>\n Writes an opening message into the composer for you to edit — it isn’t sent, and there\n is no command that runs a skill directly.\n </p>\n </div>\n ) : null}\n </div>\n )\n}\n","import type { ModelOption } from '@workerdeck/protocol'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectItemText,\n SelectTrigger,\n SelectValue,\n} from '../ui/Select.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface ModelSelectProps {\n /** Models the session can switch to (TranscriptState.models). */\n models: ModelOption[]\n /** The session's current model id (TranscriptState.model), possibly decorated\n * (e.g. \"claude-fable-5[1m]\") — matched leniently against the options.\n * `undefined` selects the list's default row, if it has one. */\n model?: string\n /** `undefined` = back to the CLI's default model. */\n onModelChange: (model?: string) => void\n /** 'toolbar' (default) is the composer's compact borderless trigger;\n * 'form' is a standard field-sized Select for create/settings forms. */\n variant?: 'toolbar' | 'form'\n disabled?: boolean\n className?: string\n}\n\n/** The CLI's supportedModels list leads with a \"Default (recommended)\" row whose value\n * is a sentinel, not a model id — selecting it means \"clear the override\". */\nconst isDefaultOption = (value: string) => value === 'default'\n\n/** Everything before a '[1m]'-style context-window suffix. */\nconst dropVariant = (id: string) => id.replace(/\\[.*\\]$/, '')\n\n/** 'claude-opus-4-8' → \"opus\", 'sonnet' → \"sonnet\". The vendor prefix and the\n * version tail are dropped; what is left is the name a person would say. */\nfunction family(id: string): string {\n const parts = id.toLowerCase().split('-')\n if (parts[0] === 'claude') parts.shift()\n return parts[0] ?? ''\n}\n\n/**\n * Whether a row is the one naming `model`.\n *\n * Three passes, narrowest first, because the rows and the id a session *reports*\n * are written differently: the rows are aliases ('opus[1m]', 'sonnet',\n * 'claude-fable-5[1m]') and a session reports a resolved wire id\n * ('claude-opus-5[1m]'). `resolvedModel` is the server's own answer to this and\n * wins when present; the family fallback covers a server that doesn't send it,\n * which is the difference between the chip reading \"Opus 5\" and reading\n * `claude-opus-5[1m]`. Kept identical to the iOS client's `ModelOption.matches`.\n */\nfunction optionMatches(option: ModelOption, model: string): boolean {\n if (model === option.value || model === option.resolvedModel) return true\n const stripped = dropVariant(model)\n // A row that declares what it resolves to is *authoritative*, including when\n // it disagrees: two rows of the same family (\"Opus 5\" and \"Opus 4.8\") differ\n // only here, so falling through to the family would match both.\n if (option.resolvedModel) return stripped === dropVariant(option.resolvedModel)\n const token = family(stripped)\n return token !== '' && token === family(dropVariant(option.value))\n}\n\n/** Find the option matching a (possibly decorated/aliased) session model id. */\nfunction matchModel(models: ModelOption[], model?: string): ModelOption | undefined {\n if (!model) return undefined\n return models.filter((m) => !isDefaultOption(m.value)).find((m) => optionMatches(m, model))\n}\n\n/** Compact model switcher for the composer toolbar; fed by the `capabilities` event.\n * Rows render CLI-style: bold display name with the model's description beneath. */\nexport function ModelSelect({\n models,\n model,\n onModelChange,\n variant = 'toolbar',\n disabled,\n className,\n}: ModelSelectProps) {\n const selected =\n matchModel(models, model) ?? (model ? undefined : models.find((m) => isDefaultOption(m.value)))\n return (\n <Select\n items={models.map((m) => ({ value: m.value, label: m.displayName }))}\n value={selected?.value ?? null}\n onValueChange={(value) => {\n if (typeof value !== 'string' || value === selected?.value) return\n onModelChange(isDefaultOption(value) ? undefined : value)\n }}\n disabled={disabled}>\n <SelectTrigger\n aria-label='Model'\n className={cn(\n variant === 'toolbar' &&\n 'h-6 max-w-56 border-transparent bg-transparent text-fg-3 hover:bg-surface-hover',\n className,\n )}>\n <span className={cn('truncate', variant === 'toolbar' && 'font-mono text-label')}>\n <SelectValue placeholder={model ?? 'model'} />\n </span>\n </SelectTrigger>\n <SelectContent className='min-w-72'>\n {models.map((m) => (\n <SelectItem key={m.value} value={m.value}>\n <SelectItemText>\n <span className='font-medium'>{m.displayName}</span>\n </SelectItemText>\n {m.description ? <span className='text-label text-fg-4'>{m.description}</span> : null}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n","import type { LucideIcon } from 'lucide-react'\nimport type { PermissionMode } from '@workerdeck/protocol'\nimport { ClipboardList, Code, Hand, ShieldCheck, TriangleAlert, Zap } from 'lucide-react'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectItemText,\n SelectTrigger,\n SelectValue,\n} from '../ui/Select.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport type PermissionModeMeta = {\n value: PermissionMode\n /** The name Claude Code itself uses. */\n label: string\n /** The chip form, for bars where the label shares a line with three other\n * things and \"Bypass permissions\" would eat half of it. */\n shortLabel: string\n /** What the mode actually does — the CLI's own one-liners. */\n description: string\n icon: LucideIcon\n dangerous?: boolean\n}\n\n/**\n * The modes surfaced across UI surfaces (session creation, in-session switcher),\n * ordered by how much of the approval gate they give away.\n *\n * Notably `default` is **\"Manual\"**: the wire value is `default`, but calling it\n * that in the UI conflates a real mode (ask me every time) with \"whatever the\n * server picked\", which is the one confusion a mode chip exists to avoid. The\n * naming, the icons and the summaries are shared with the iOS app on purpose —\n * the two surfaces should read as the same list.\n */\nexport const PERMISSION_MODES: PermissionModeMeta[] = [\n {\n value: 'default',\n label: 'Manual',\n shortLabel: 'Manual',\n description: 'Always ask before making changes',\n icon: Hand,\n },\n {\n value: 'acceptEdits',\n label: 'Accept edits',\n shortLabel: 'Edits',\n description: 'Automatically accept all file edits',\n icon: Code,\n },\n {\n value: 'plan',\n label: 'Plan',\n shortLabel: 'Plan',\n description: 'Create a plan before making changes',\n icon: ClipboardList,\n },\n {\n value: 'auto',\n label: 'Auto',\n shortLabel: 'Auto',\n description: 'Claude handles permission decisions',\n icon: Zap,\n },\n {\n value: 'dontAsk',\n label: \"Don't ask\",\n shortLabel: \"Don't ask\",\n // The CLI's own definition, and the opposite of bypass: it never prompts,\n // and anything not already permitted is denied rather than allowed.\n description: 'Never ask — deny anything not pre-approved',\n icon: ShieldCheck,\n },\n {\n value: 'bypassPermissions',\n label: 'Bypass permissions',\n shortLabel: 'Bypass',\n description: 'Skip every approval — the agent is unsupervised',\n icon: TriangleAlert,\n dangerous: true,\n },\n]\n\nexport const permissionModeMeta = (mode: PermissionMode): PermissionModeMeta | undefined =>\n PERMISSION_MODES.find((m) => m.value === mode)\n\n/** One offered mode as plain data — no icon, no React. What a host chrome\n * outside the panel (a VS Code QuickPick) needs to draw the same list. */\nexport type PermissionModeChoice = {\n value: PermissionMode\n label: string\n description: string\n dangerous?: boolean\n /** Offered but unreachable: a session not started for bypass can never gain\n * it, and saying so beats omitting the row. */\n disabled?: boolean\n}\n\n/**\n * The modes this session may actually be switched into, with the reasons baked\n * in — the same filtering {@link PermissionModeSelect} applies, so an embedder\n * rendering its own picker cannot drift from the panel's.\n */\nexport function permissionModeChoices(\n modes?: readonly PermissionMode[],\n canBypass?: boolean,\n): PermissionModeChoice[] {\n const offered = modes ? PERMISSION_MODES.filter((m) => modes.includes(m.value)) : PERMISSION_MODES\n return offered.map((m) => ({\n value: m.value,\n label: m.label,\n description:\n m.value === 'bypassPermissions' && canBypass === false\n ? 'Only available to a session started in this mode'\n : m.description,\n dangerous: m.dangerous,\n disabled: m.value === 'bypassPermissions' && canBypass === false,\n }))\n}\n\nexport interface PermissionModeSelectProps {\n /** The session's current mode (TranscriptState.permissionMode). */\n mode?: PermissionMode\n onModeChange: (mode: PermissionMode) => void\n /** Restrict what is offered — most of {@link PERMISSION_MODES} is Claude Code\n * vocabulary the other engines have no meaning for. Defaults to all of them;\n * pass the session's `capabilities.permissionModes`. */\n modes?: readonly PermissionMode[]\n /**\n * Whether this session may be switched into `bypassPermissions` at all. The\n * CLI refuses unless the process was spawned for it, so a session that didn't\n * ask up front can never gain it. The row is shown disabled rather than hidden\n * — \"you can't have this here\" is a more useful answer than a row that\n * silently isn't there. `undefined` (an older server) offers it.\n */\n canBypass?: boolean\n /** 'toolbar' (default) is the composer's compact borderless trigger;\n * 'form' is a standard field-sized Select for create/settings forms. */\n variant?: 'toolbar' | 'form'\n disabled?: boolean\n className?: string\n}\n\n/** Permission-mode switcher: compact in the composer toolbar, field-sized in forms. */\nexport function PermissionModeSelect({\n mode,\n onModeChange,\n modes,\n canBypass,\n variant = 'toolbar',\n disabled,\n className,\n}: PermissionModeSelectProps) {\n const dangerous = mode === 'bypassPermissions'\n const offered = modes ? PERMISSION_MODES.filter((m) => modes.includes(m.value)) : PERMISSION_MODES\n const unavailable = (meta: PermissionModeMeta) =>\n meta.value === 'bypassPermissions' && canBypass === false\n return (\n <Select\n items={offered.map((m) => ({ value: m.value, label: m.label }))}\n value={mode ?? null}\n onValueChange={(value) => {\n if (typeof value === 'string' && value !== mode) onModeChange(value as PermissionMode)\n }}\n disabled={disabled}>\n <SelectTrigger\n aria-label='Permission mode'\n className={cn(\n variant === 'toolbar' &&\n 'h-6 max-w-44 border-transparent bg-transparent hover:bg-surface-hover',\n dangerous ? 'text-danger' : variant === 'toolbar' ? 'text-fg-3' : undefined,\n className,\n )}>\n <span className={cn('truncate', variant === 'toolbar' && 'text-label')}>\n <SelectValue placeholder='permissions' />\n </span>\n </SelectTrigger>\n <SelectContent className='min-w-64'>\n {offered.map((m) => {\n const blocked = unavailable(m)\n return (\n <SelectItem key={m.value} value={m.value} disabled={blocked}>\n <SelectItemText>\n <span className='flex items-center gap-2'>\n <m.icon\n className={cn('size-3.5 shrink-0', m.dangerous ? 'text-danger' : 'text-fg-3')}\n />\n <span className={cn('font-medium', m.dangerous && 'text-danger')}>{m.label}</span>\n </span>\n </SelectItemText>\n <span\n className={cn(\n 'pl-5.5 text-label',\n blocked ? 'text-fg-4' : m.dangerous ? 'text-danger/80' : 'text-fg-4',\n )}>\n {blocked ? 'Only available to a session started in this mode' : m.description}\n </span>\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n )\n}\n","import {\n ArrowDownCircle,\n CheckSquare,\n FileDiff,\n FileText,\n FolderSearch,\n Globe,\n Image,\n type LucideIcon,\n MessageCircleQuestion,\n PencilLine,\n Puzzle,\n Search,\n Sparkles,\n SquarePen,\n Terminal,\n UsersRound,\n Wrench,\n} from 'lucide-react'\n\n/**\n * An icon per tool, so a transcript can be skimmed by shape rather than read.\n *\n * The same mapping the iOS app makes, in lucide's vocabulary rather than SF\n * Symbols — the two clients should be recognisably showing the same thing. An\n * unknown tool falls back to a wrench, and an MCP tool (`mcp__server__name`) to\n * the puzzle piece the MCP screens use, because \"which server is this from\" is\n * the useful thing to see at a glance.\n */\nexport function toolIcon(toolName: string): LucideIcon {\n switch (toolName) {\n case 'Bash':\n case 'BashOutput':\n case 'KillShell':\n return Terminal\n case 'Read':\n return FileText\n case 'Write':\n return SquarePen\n case 'Edit':\n case 'MultiEdit':\n case 'NotebookEdit':\n return PencilLine\n case 'Glob':\n return FolderSearch\n case 'Grep':\n return Search\n case 'WebFetch':\n return ArrowDownCircle\n case 'WebSearch':\n return Globe\n case 'Task':\n case 'Agent':\n return UsersRound\n case 'TodoWrite':\n return CheckSquare\n case 'Skill':\n return Sparkles\n case 'AskUserQuestion':\n return MessageCircleQuestion\n // The codex engine's own tool names (see its runner's item mapping).\n case 'CodexCommand':\n return Terminal\n case 'CodexFileChange':\n return FileDiff\n case 'CodexWebSearch':\n return Globe\n case 'CodexImageGeneration':\n case 'CodexImageView':\n return Image\n default:\n return toolName.startsWith('mcp__') ? Puzzle : Wrench\n }\n}\n\n/**\n * Does this tool *change* the workspace?\n *\n * Worth its own colour in a transcript: skimming a run, \"what did it edit\" is a\n * different question from \"what did it look at\", and a write is the one you\n * might need to undo. Names from both first-party engines; an MCP tool is\n * unknowable from its name, so it reads as neutral rather than guessed.\n */\nexport function isMutatingTool(toolName: string): boolean {\n switch (toolName) {\n case 'Write':\n case 'Edit':\n case 'MultiEdit':\n case 'NotebookEdit':\n case 'Update':\n case 'CodexFileChange':\n return true\n default:\n return false\n }\n}\n","import { createContext, useContext, type ReactNode } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * How the transcript draws a turn.\n *\n * - `cards` — the chat convention: bubbles, bordered tool cards, generous gaps.\n * Right for a wide dashboard where the transcript is the page.\n * - `lines` — one full-width line item per event, transparent, hover-highlit,\n * with a glyph in a fixed left gutter. Right where vertical space is the\n * scarce resource (a VS Code dock) and the terminal is the reference UX:\n * nothing is boxed, the content and its marker carry the comprehension.\n *\n * A context rather than a prop chain because every row component needs it and\n * only the transcript root knows it — and because `Message`/`ToolCallCard` are\n * exported on their own, so an embedder composing them by hand gets the right\n * treatment for free.\n */\nexport type TranscriptVariant = 'cards' | 'lines'\n\nconst VariantContext = createContext<TranscriptVariant>('cards')\n\nexport function TranscriptVariantProvider({\n value,\n children,\n}: {\n value: TranscriptVariant\n children: ReactNode\n}) {\n return <VariantContext.Provider value={value}>{children}</VariantContext.Provider>\n}\n\nexport function useTranscriptVariant(): TranscriptVariant {\n return useContext(VariantContext)\n}\n\n/** True in `lines`, for the many `cond ? a : b` reads in the row components. */\nexport function useLines(): boolean {\n return useTranscriptVariant() === 'lines'\n}\n\n/**\n * The left gutter of a line item: one glyph, fixed width, so every row's text\n * starts on the same column no matter which kind of event it is. Decorative —\n * the row's own text says what it is.\n */\nexport function LineGlyph({ children, className }: { children: ReactNode; className?: string }) {\n return (\n <span\n aria-hidden\n className={cn(\n 'w-3.5 shrink-0 select-none text-center font-mono text-label leading-5 text-fg-4',\n className,\n )}>\n {children}\n </span>\n )\n}\n\n/** Body text metrics for a line item — tighter than the card variant's. */\nexport const LINE_TEXT = 'text-body-sm leading-5'\n","import { memo } from 'react'\nimport { Streamdown } from 'streamdown'\nimport { cn } from '../../lib/utils.ts'\nimport { useLines } from './transcript-variant.tsx'\n\n/**\n * Markdown on a terminal's grid.\n *\n * Prose defaults give every block its own rhythm — a heading is bigger, a list\n * item is looser, an inline `code` chip is padded taller than the line it sits\n * in — and the result is text that visibly breathes at different rates as you\n * scroll. A terminal has exactly one line height and one type size, and that\n * uniformity is most of what makes it readable at a glance.\n *\n * So: every line 1.25rem, every block flush, and one blank line (1.25rem, i.e.\n * exactly one row) between blocks. Headings keep their weight but lose their\n * size. Inline code keeps a background — it still has to be distinguishable —\n * but no vertical padding, because that is what pushes a line off the grid.\n */\nconst TERMINAL_PROSE = [\n 'text-body-sm leading-5',\n '[&_*]:leading-5',\n // Block rhythm: nothing has its own margins, and siblings are one row apart.\n // `!` throughout because these fight the renderer's own utilities, and some of\n // those (`space-y-*`, which is a `> * + *` rule) outrank a plain descendant\n // selector — a list would otherwise keep its half-row gaps while every other\n // block snapped to the grid.\n '[&_p]:my-0! [&_pre]:my-0! [&_blockquote]:my-0! [&_table]:my-0!',\n '[&_h1]:my-0! [&_h2]:my-0! [&_h3]:my-0! [&_h4]:my-0! [&_h5]:my-0! [&_h6]:my-0!',\n '[&>*+*]:mt-5',\n // Lists are rows, not paragraphs: no margin anywhere in them, and no\n // inter-item spacing from a `space-y` on the list itself.\n '[&_ul]:my-0! [&_ol]:my-0! [&_li]:my-0! [&_li>p]:my-0!',\n '[&_li+li]:mt-0! [&_ul_ul]:mt-0! [&_ul_ol]:mt-0! [&_ol_ul]:mt-0! [&_ol_ol]:mt-0!',\n '[&_li]:py-0! [&_ul]:py-0! [&_ol]:py-0!',\n // Markers on their own column, at the width the marker actually is — the\n // indent a terminal (and the markdown source) uses, not a typographic one.\n // `ch` is the unit because this variant runs in a monospace face: `• ` is two\n // cells, `1. ` is three, so the text starts exactly where it would in the raw\n // document and a wrapped line hangs under it instead of under the bullet.\n // Nesting then costs one marker width per level, which is the 2-space step.\n // `list-outside` is the load-bearing half: the renderer marks lists `inside`,\n // where the marker joins the text flow — so the indent moves the bullet too\n // (the list sits a marker-width right of the paragraph above it) and a wrapped\n // line runs back under the bullet instead of hanging under its own text.\n '[&_ul]:list-outside! [&_ol]:list-outside!',\n '[&_ul]:pl-[2ch]! [&_ol]:pl-[3ch]! [&_li]:pl-0!',\n // Headings are weight, not size — a terminal has one type size.\n '[&_h1]:text-body-sm [&_h2]:text-body-sm [&_h3]:text-body-sm',\n '[&_h4]:text-body-sm [&_h5]:text-body-sm [&_h6]:text-body-sm',\n '[&_h1]:font-semibold [&_h2]:font-semibold [&_h3]:font-semibold',\n '[&_h4]:font-semibold [&_h5]:font-semibold [&_h6]:font-semibold',\n '[&_blockquote]:border-l [&_blockquote]:border-border [&_blockquote]:pl-2',\n // Inline code only (`:not(pre) > code`) — fenced blocks keep their own box.\n // Horizontal padding is free; vertical padding is what pushes a line off grid.\n '[&_:not(pre)>code]:rounded-none [&_:not(pre)>code]:px-1! [&_:not(pre)>code]:py-0!',\n '[&_:not(pre)>code]:text-body-sm [&_:not(pre)>code]:leading-5! [&_:not(pre)>code]:text-fg-1',\n '[&_th]:py-0! [&_td]:py-0!',\n // The renderer's fenced-code card: rounded frame, its own header strip, a\n // second rounded frame inside it, and a floating pill for the buttons. Four\n // boxes around what is, in a terminal, a band of dim text. Flattened to\n // exactly that — the band keeps a background so a block still reads as code,\n // and the actions keep their top-right corner but lose the pill.\n '[&_[data-streamdown=code-block]]:my-0! [&_[data-streamdown=code-block]]:gap-0!',\n '[&_[data-streamdown=code-block]]:rounded-none! [&_[data-streamdown=code-block]]:border-0!',\n '[&_[data-streamdown=code-block]]:bg-transparent! [&_[data-streamdown=code-block]]:p-0!',\n // The language strip costs a whole row to say what the code already shows, and\n // the actions cost another. Both go: the block is the band, and the buttons\n // float over its top-right corner, appearing on hover or keyboard focus.\n '[&_[data-streamdown=code-block]]:relative!',\n '[&_[data-streamdown=code-block-header]]:hidden!',\n '[&_div:has(>[data-streamdown=code-block-actions])]:absolute! [&_div:has(>[data-streamdown=code-block-actions])]:top-0!',\n '[&_div:has(>[data-streamdown=code-block-actions])]:right-0! [&_div:has(>[data-streamdown=code-block-actions])]:mt-0!',\n '[&_div:has(>[data-streamdown=code-block-actions])]:h-5! [&_div:has(>[data-streamdown=code-block-actions])]:opacity-0!',\n '[&_div:has(>[data-streamdown=code-block-actions])]:transition-opacity',\n '[&_[data-streamdown=code-block]:hover_div:has(>[data-streamdown=code-block-actions])]:opacity-100!',\n '[&_[data-streamdown=code-block]:focus-within_div:has(>[data-streamdown=code-block-actions])]:opacity-100!',\n '[&_[data-streamdown=code-block-actions]]:rounded-none! [&_[data-streamdown=code-block-actions]]:border-0!',\n '[&_[data-streamdown=code-block-actions]]:bg-code-bg! [&_[data-streamdown=code-block-actions]]:px-1!',\n '[&_[data-streamdown=code-block-actions]]:py-0! [&_[data-streamdown=code-block-actions]]:gap-1!',\n '[&_[data-streamdown=code-block-actions]]:backdrop-blur-none!',\n '[&_[data-streamdown=code-block-body]]:rounded-none! [&_[data-streamdown=code-block-body]]:border-0!',\n '[&_[data-streamdown=code-block-body]]:bg-code-bg! [&_[data-streamdown=code-block-body]]:px-2!',\n '[&_[data-streamdown=code-block-body]]:py-0! [&_[data-streamdown=code-block-body]]:text-body-sm!',\n // The action buttons' hit areas, on the grid like everything else.\n '[&_[data-streamdown=code-block-copy-button]]:p-0! [&_[data-streamdown=code-block-download-button]]:p-0!',\n // Tables come in a frame of their own, plus a controls row above them and a\n // second frame around the scroller — three boxes and a blank line to hold\n // three buttons. Same treatment: the table is the content, the buttons float\n // over its top-right on hover. The controls row is the child that has no\n // `<table>` in it, which is also how it knows to disappear when the renderer\n // draws no controls at all.\n '[&_[data-streamdown=table-wrapper]]:rounded-none! [&_[data-streamdown=table-wrapper]]:border-0!',\n '[&_[data-streamdown=table-wrapper]]:my-0! [&_[data-streamdown=table-wrapper]]:bg-transparent!',\n '[&_[data-streamdown=table-wrapper]]:relative! [&_[data-streamdown=table-wrapper]]:gap-0!',\n '[&_[data-streamdown=table-wrapper]]:p-0!',\n '[&_[data-streamdown=table-wrapper]>div:not(:has(table))]:absolute! [&_[data-streamdown=table-wrapper]>div:not(:has(table))]:top-0!',\n '[&_[data-streamdown=table-wrapper]>div:not(:has(table))]:right-0! [&_[data-streamdown=table-wrapper]>div:not(:has(table))]:z-10!',\n '[&_[data-streamdown=table-wrapper]>div:not(:has(table))]:opacity-0! [&_[data-streamdown=table-wrapper]>div:not(:has(table))]:bg-code-bg!',\n '[&_[data-streamdown=table-wrapper]>div:not(:has(table))]:px-1! [&_[data-streamdown=table-wrapper]>div:not(:has(table))]:transition-opacity',\n '[&_[data-streamdown=table-wrapper]:hover>div:not(:has(table))]:opacity-100!',\n '[&_[data-streamdown=table-wrapper]:focus-within>div:not(:has(table))]:opacity-100!',\n '[&_[data-streamdown=table-wrapper]>div:has(table)]:rounded-none! [&_[data-streamdown=table-wrapper]>div:has(table)]:border-0!',\n '[&_[data-streamdown=table-wrapper]>div:has(table)]:bg-transparent!',\n].join(' ')\n\n/**\n * The controls' icons as characters.\n *\n * The renderer draws its copy/download affordances as line-art SVGs, which next\n * to monospace text read as buttons from another application. These are the\n * glyphs a terminal would use — and they inherit the surrounding font, so they\n * sit on the same grid as the code they belong to.\n */\nconst glyphIcon = (glyph: string) =>\n function GlyphIcon({ className }: { className?: string }) {\n return (\n <span aria-hidden className={cn('font-mono text-body-sm leading-5', className)}>\n {glyph}\n </span>\n )\n }\n\nconst TERMINAL_ICONS = {\n DownloadIcon: glyphIcon('⤓'),\n CopyIcon: glyphIcon('⧉'),\n CheckIcon: glyphIcon('✓'),\n}\n\nexport interface ResponseProps {\n children: string\n /** Streaming text: tolerate incomplete markdown (unclosed fences, half links). */\n streaming?: boolean\n className?: string\n}\n\n/** Markdown renderer for assistant output — streaming-safe via streamdown, code\n * highlighted with shiki (dual theme follows [data-theme] through the dark: variant). */\nexport const Response = memo(\n function Response({ children, streaming, className }: ResponseProps) {\n const lines = useLines()\n return (\n <Streamdown\n mode={streaming ? 'streaming' : 'static'}\n parseIncompleteMarkdown={streaming}\n shikiTheme={['github-light', 'github-dark']}\n icons={lines ? TERMINAL_ICONS : undefined}\n className={cn(\n 'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',\n lines && TERMINAL_PROSE,\n className,\n )}>\n {children}\n </Streamdown>\n )\n },\n (prev, next) =>\n prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className,\n)\n","import { useEffect, useRef, type ReactNode } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport { Response } from './Response.tsx'\nimport { LineGlyph } from './transcript-variant.tsx'\n\n/**\n * The parts a terminal-shaped prompt is built from.\n *\n * The `lines` transcript answers a question the `cards` one does not have to:\n * an approval and a question are *interactive*, so \"no boxes\" has to be paid for\n * by something else carrying the affordance. That something is the keyboard —\n * a roving `❯` marker, numbered rows, and a hint line — which is what a terminal\n * would do anyway, and what makes the prompt answerable without reaching for the\n * mouse in a dock where the rows are two lines tall.\n *\n * Everything here indents to the same gutter as every other line item, so an\n * approval reads as one more row in the run rather than a dialog dropped on top\n * of it.\n */\n\n/** The left inset that lines up body text with a `LineGlyph`'d row above it. */\nexport const LINE_INDENT = 'pl-[calc(0.875rem+0.5rem)]'\n\nexport type LineChoice = {\n key: string\n label: string\n description?: string\n /**\n * Rendered under the row, outside the button — a preview, an input. The caller\n * decides when it exists (focused, checked); a button may not contain one.\n */\n detail?: ReactNode\n /**\n * Present → the row carries a selection state and draws it. `marker` says in\n * which idiom: a checkbox for a multi-select, a radio for a one-of. Absent →\n * the row is an action (allow, deny), which has no state to show.\n */\n checked?: boolean\n marker?: 'check' | 'radio'\n danger?: boolean\n}\n\n/** The two-state glyph pairs, in the character forms a terminal would use. */\nconst MARKERS = {\n check: ['[ ]', '[x]'],\n radio: ['( )', '(•)'],\n} as const\n\nexport interface LineOptionListProps {\n options: LineChoice[]\n /** Roving index: the one row that is tab-reachable and wears the marker. */\n focused: number\n onFocus: (index: number) => void\n onChoose: (index: number) => void\n /**\n * Own the DOM focus, moving it with the roving index. False while something\n * else inside the prompt holds it (a reason input, another question's list) —\n * two lists both chasing `focused` would tear the caret back and forth.\n */\n active?: boolean\n label: string\n className?: string\n}\n\n/**\n * A keyboard-first list of choices as line items: `↑`/`↓` move, `1`–`9` pick\n * directly, `Enter`/`Space` take the focused one (the button does that itself).\n */\nexport function LineOptionList({\n options,\n focused,\n onFocus,\n onChoose,\n active = true,\n label,\n className,\n}: LineOptionListProps) {\n const refs = useRef<Array<HTMLButtonElement | null>>([])\n\n useEffect(() => {\n if (active) refs.current[focused]?.focus()\n }, [active, focused])\n\n const move = (delta: number) => {\n if (options.length > 0) onFocus((focused + delta + options.length) % options.length)\n }\n\n return (\n <div\n role='group'\n aria-label={label}\n className={cn('flex flex-col', className)}\n onKeyDown={(event) => {\n if (event.key === 'ArrowDown') {\n move(1)\n event.preventDefault()\n return\n }\n if (event.key === 'ArrowUp') {\n move(-1)\n event.preventDefault()\n return\n }\n // Digits are the whole point of numbering the rows — but only up to the\n // rows that exist, so `9` on a three-option prompt stays a no-op rather\n // than a silent miss.\n const digit = Number(event.key)\n if (Number.isInteger(digit) && digit >= 1 && digit <= Math.min(options.length, 9)) {\n onFocus(digit - 1)\n onChoose(digit - 1)\n event.preventDefault()\n }\n }}>\n {options.map((option, index) => {\n const isFocused = index === focused\n return (\n <div key={option.key} className='flex flex-col'>\n <button\n ref={(element) => {\n refs.current[index] = element\n }}\n type='button'\n tabIndex={isFocused ? 0 : -1}\n aria-pressed={option.checked}\n onFocus={() => onFocus(index)}\n onClick={() => onChoose(index)}\n className={cn(\n 'flex w-full items-baseline gap-2 text-left outline-none',\n isFocused ? 'bg-surface-hover' : 'hover:bg-surface-hover/60',\n )}>\n <LineGlyph className={isFocused ? 'text-accent' : undefined}>\n {isFocused ? '❯' : ' '}\n </LineGlyph>\n <span className='shrink-0 font-mono text-label leading-5 text-fg-4'>{index + 1}</span>\n {option.checked !== undefined ? (\n <span\n className={cn(\n 'shrink-0 font-mono text-label leading-5',\n option.checked ? 'text-accent' : 'text-fg-4',\n )}>\n {MARKERS[option.marker ?? 'check'][option.checked ? 1 : 0]}\n </span>\n ) : null}\n <span\n className={cn(\n 'min-w-0 flex-1 text-body-sm leading-5',\n option.danger ? 'text-danger' : 'text-fg-1',\n )}>\n {option.label}\n {option.description ? (\n <span className='text-fg-4'> — {option.description}</span>\n ) : null}\n </span>\n </button>\n {option.detail ? <div className={LINE_INDENT}>{option.detail}</div> : null}\n </div>\n )\n })}\n </div>\n )\n}\n\n/** The dim key legend under a prompt. Separated by `·`, like a status line. */\nexport function LineHint({ children }: { children: ReactNode }) {\n return (\n <div className={cn(LINE_INDENT, 'text-label leading-5 text-fg-4')}>{children}</div>\n )\n}\n\n/** A single-line text field in the terminal idiom: a caret glyph and a rule,\n * no box. `Enter` commits, `Escape` backs out — the caller says what those mean. */\nexport function LineInput({\n value,\n onChange,\n onSubmit,\n onCancel,\n placeholder,\n}: {\n value: string\n onChange: (value: string) => void\n onSubmit: () => void\n onCancel: () => void\n placeholder?: string\n}) {\n return (\n <div className='flex items-baseline gap-2'>\n <LineGlyph className='text-accent'>›</LineGlyph>\n <input\n autoFocus\n value={value}\n placeholder={placeholder}\n onChange={(event) => onChange(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n onSubmit()\n }\n if (event.key === 'Escape') {\n // The prompt's own Escape means \"deny\"/\"dismiss\"; inside the field it\n // only closes the field, so it must not travel further.\n event.stopPropagation()\n onCancel()\n }\n }}\n className='min-w-0 flex-1 border-b border-border bg-transparent text-body-sm leading-5 text-fg-1 outline-none placeholder:text-fg-4 focus:border-accent'\n />\n </div>\n )\n}\n\n/**\n * A payload in the terminal idiom: a dim label line over a **highlighted** band.\n *\n * Highlighting goes through `Response` — the markdown renderer already carries\n * shiki, so wrapping the text in a fence costs no new dependency, no second\n * highlighter and no second theme to keep in sync, and it inherits the terminal\n * flattening plus the hover copy/download the renderer draws for every code\n * block. Text with no language still goes through it, as an unlabelled fence:\n * same band, same grid, no grammar guessed.\n */\nexport function LinePayload({\n code,\n label,\n language,\n className,\n}: {\n code: string\n label: string\n language?: string\n className?: string\n}) {\n return (\n <div className={cn('min-w-0', className)}>\n <span className='block truncate text-label leading-5 text-fg-4'>{label}</span>\n <Response>{fence(code, language)}</Response>\n </div>\n )\n}\n\n/**\n * Wrap text as a markdown code fence, with a fence long enough to survive the\n * content: a payload containing ``` would otherwise close the block early and\n * spill the rest of it into the transcript as markdown.\n */\nexport function fence(code: string, language?: string): string {\n const longest = Math.max(0, ...[...code.matchAll(/`+/g)].map((match) => match[0].length))\n const ticks = '`'.repeat(Math.max(3, longest + 1))\n return `${ticks}${language ?? ''}\\n${code}\\n${ticks}`\n}\n","import { useState } from 'react'\nimport type { PermissionRequest } from '@workerdeck/protocol'\nimport { Hand } from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { toolInputPreview } from '../../lib/format.ts'\nimport { toolIcon } from '../../lib/tool-icon.ts'\nimport {\n LINE_INDENT,\n LineHint,\n LineInput,\n LineOptionList,\n LinePayload,\n} from './line-prompt.tsx'\nimport { LineGlyph, useLines } from './transcript-variant.tsx'\n\nexport interface PermissionPromptProps {\n request: PermissionRequest\n onApprove: (requestId: string) => void\n /** `message` is fed back to the agent, which can then try something else;\n * `interrupt` also stops the turn. */\n onDeny: (requestId: string, message?: string, interrupt?: boolean) => void\n className?: string\n}\n\n/**\n * Generic allow/deny prompt for a pending permission request.\n *\n * Three outcomes, not two: denying usually means \"not that, try something else\",\n * so plain Deny lets the turn continue (with an optional reason the agent reads)\n * while \"Deny & stop\" also interrupts.\n *\n * The heading is whatever the engine authored — `title`, else `displayName`.\n * Composing \"wants to use {tool}\" instead would be wrong for codex, where an\n * approval is usually an *escalation after a sandbox refusal* and the runner has\n * already written the sentence that says so.\n */\nexport function PermissionPrompt({ request, onApprove, onDeny, className }: PermissionPromptProps) {\n const lines = useLines()\n const [showInput, setShowInput] = useState(false)\n const [denying, setDenying] = useState(false)\n const [reason, setReason] = useState('')\n const [focused, setFocused] = useState(0)\n\n const deny = (interrupt: boolean) => {\n const message = reason.trim()\n onDeny(request.id, message || undefined, interrupt || undefined)\n setReason('')\n setDenying(false)\n }\n\n const summary = toolInputPreview(request.input)\n const ToolIcon = toolIcon(request.toolName)\n const heading = request.title ?? request.displayName ?? 'Permission needed'\n\n if (lines) {\n // The same three outcomes as the card, as rows. \"Deny\" opens the reason\n // field rather than acting, because a denial the agent can learn from is the\n // one worth defaulting to — an empty field still just denies.\n const options = [\n { key: 'allow', label: 'Allow' },\n {\n key: 'deny',\n label: 'Deny',\n description: 'the turn continues',\n detail: denying ? (\n <LineInput\n value={reason}\n onChange={setReason}\n onSubmit={() => deny(false)}\n onCancel={() => setDenying(false)}\n placeholder='Reason (optional) — the agent reads this and can try something else'\n />\n ) : undefined,\n },\n { key: 'stop', label: 'Deny & stop', description: 'interrupt the turn', danger: true },\n ]\n\n return (\n <div\n data-slot='permission-prompt'\n className={cn('w-full', className)}\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n deny(false)\n }\n }}>\n <div className='flex items-baseline gap-2'>\n <LineGlyph className='text-warning'>!</LineGlyph>\n <span className='min-w-0 flex-1 text-body-sm leading-5 font-medium text-fg-1'>\n {heading}\n </span>\n </div>\n {request.description ? (\n <div className={cn(LINE_INDENT, 'text-label leading-5 text-fg-3')}>\n {request.description}\n </div>\n ) : null}\n {request.decisionReason ? (\n <div className={cn(LINE_INDENT, 'text-label leading-5 text-fg-4')}>\n {request.decisionReason}\n </div>\n ) : null}\n <button\n type='button'\n onClick={() => setShowInput((v) => !v)}\n className='flex w-full items-baseline gap-2 text-left outline-none hover:bg-surface-hover/60'>\n <LineGlyph className='text-fg-4'>⎿</LineGlyph>\n <span className='min-w-0 flex-1 truncate text-label leading-5 text-fg-4'>\n <span className='font-medium text-fg-2'>{request.toolName}</span>\n {summary ? `(${summary})` : null}\n </span>\n </button>\n {showInput ? (\n <div className={LINE_INDENT}>\n <LinePayload code={JSON.stringify(request.input, null, 2)} language='json' label='Input' />\n </div>\n ) : null}\n <LineOptionList\n label={heading}\n options={options}\n focused={focused}\n onFocus={setFocused}\n active={!denying}\n onChoose={(index) => {\n if (index === 0) onApprove(request.id)\n else if (index === 1) setDenying(true)\n else deny(true)\n }}\n />\n <LineHint>↑↓ move · 1–3 choose · esc deny</LineHint>\n </div>\n )\n }\n\n return (\n <div\n data-slot='permission-prompt'\n className={cn('rounded-lg border border-warning/40 bg-warning-bg p-3', className)}>\n <div className='flex items-start gap-2.5'>\n <Hand className='mt-0.5 size-4 shrink-0 text-warning' />\n <div className='min-w-0 flex-1'>\n <div className='text-body-sm font-medium text-fg-1'>\n {request.title ?? request.displayName ?? 'Permission needed'}\n </div>\n {request.description ? (\n <div className='mt-0.5 text-label text-fg-3'>{request.description}</div>\n ) : null}\n {request.decisionReason ? (\n <div className='mt-0.5 text-label text-fg-4'>{request.decisionReason}</div>\n ) : null}\n <div className='mt-1.5 flex min-w-0 items-center gap-2'>\n <ToolIcon className='size-3 shrink-0 text-fg-3' />\n <span className='shrink-0 font-mono text-label font-medium text-fg-2'>\n {request.toolName}\n </span>\n {summary ? (\n <span className='min-w-0 truncate font-mono text-label text-fg-4'>{summary}</span>\n ) : null}\n </div>\n <button\n type='button'\n className='mt-1 font-mono text-label text-fg-3 underline-offset-2 hover:underline'\n onClick={() => setShowInput((v) => !v)}>\n {showInput ? 'Hide' : 'Show'} {request.toolName} input\n </button>\n {showInput ? (\n <pre className='mt-1.5 max-h-48 overflow-auto rounded-md bg-code-bg px-2.5 py-1.5 font-mono text-label whitespace-pre-wrap text-fg-2'>\n {JSON.stringify(request.input, null, 2)}\n </pre>\n ) : null}\n </div>\n <div className='flex shrink-0 flex-wrap justify-end gap-1.5'>\n <Button size='sm' onClick={() => onApprove(request.id)}>\n Allow\n </Button>\n <Button size='sm' variant='outline' onClick={() => setDenying((v) => !v)}>\n Deny\n </Button>\n <Button size='sm' variant='outline' onClick={() => deny(true)}>\n Deny & stop\n </Button>\n </div>\n </div>\n {denying ? (\n <div className='mt-2.5 flex items-center gap-2 border-t border-warning/30 pt-2.5'>\n <Input\n autoFocus\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') deny(false)\n if (e.key === 'Escape') setDenying(false)\n }}\n placeholder='Reason (optional) — the agent reads this and can try something else'\n className='h-7 flex-1'\n />\n <Button size='sm' variant='outline' onClick={() => setDenying(false)}>\n Cancel\n </Button>\n <Button size='sm' variant='destructive' onClick={() => deny(false)}>\n Deny\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n","import { useState } from 'react'\nimport type {\n PermissionRequest,\n QuestionBehavior,\n UserQuestion,\n UserQuestionOption,\n} from '@workerdeck/protocol'\nimport { MessageCircleQuestion, X } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { LINE_INDENT, LineHint, LineInput, LineOptionList, LinePayload } from './line-prompt.tsx'\nimport { LineGlyph, useLines } from './transcript-variant.tsx'\n\nexport type QuestionBehaviorMeta = {\n value: QuestionBehavior\n label: string\n description: string\n}\n\n/** The AskUserQuestion policies surfaced on job/session creation forms. */\nexport const QUESTION_BEHAVIORS: QuestionBehaviorMeta[] = [\n { value: 'auto', label: 'Auto-answer', description: 'pick each question’s recommended option' },\n { value: 'ask', label: 'Ask', description: 'wait for a watcher or webhook controller to answer' },\n { value: 'deny', label: 'Disabled', description: 'the agent is told to decide on its own' },\n]\n\n/** Extract well-formed questions from an AskUserQuestion permission request's input. */\nexport function parseUserQuestions(input: Record<string, unknown>): UserQuestion[] {\n const raw = Array.isArray(input.questions) ? input.questions : []\n return raw.flatMap((entry): UserQuestion[] => {\n const q = entry as Partial<UserQuestion>\n if (typeof q.question !== 'string' || !Array.isArray(q.options)) return []\n const options = q.options.filter(\n (o): o is UserQuestionOption => typeof (o as UserQuestionOption | undefined)?.label === 'string',\n )\n if (options.length === 0) return []\n return [\n {\n question: q.question,\n header: typeof q.header === 'string' ? q.header : '',\n options,\n multiSelect: q.multiSelect === true,\n },\n ]\n })\n}\n\ntype Selection = { labels: string[]; other: string; otherActive: boolean }\n\n/** Whether the key hint should say \"toggle\" rather than \"choose\". */\nconst multiSelectAnywhere = (questions: UserQuestion[]) => questions.some((q) => q.multiSelect === true)\n\nconst EMPTY_SELECTION: Selection = { labels: [], other: '', otherActive: false }\n\n/** A question's answer string: chosen label(s) (multi-select comma-joined), with any\n * free-text \"Other\" appended — the value the CLI expects in `updatedInput.answers`. */\nfunction answerFor(selection: Selection): string {\n const parts = [...selection.labels]\n if (selection.otherActive && selection.other.trim()) parts.push(selection.other.trim())\n return parts.join(', ')\n}\n\nexport interface QuestionPromptProps {\n /** A pending permission whose toolName is 'AskUserQuestion'. */\n request: PermissionRequest\n /** Allow the tool with `updatedInput` (the original input plus `answers`). */\n onAnswer: (requestId: string, updatedInput: Record<string, unknown>) => void\n /** Deny the tool — the model proceeds without an answer. */\n onDismiss: (requestId: string, message?: string) => void\n className?: string\n}\n\n/** Interactive form for the AskUserQuestion tool: option buttons per question\n * (multi-select where the question allows it), a free-text \"Other\" escape hatch,\n * and the focused option's preview. Falls back to nothing renderable → the caller\n * should show a generic PermissionPrompt if `parseUserQuestions` finds no questions. */\nexport function QuestionPrompt({ request, onAnswer, onDismiss, className }: QuestionPromptProps) {\n const lines = useLines()\n const questions = parseUserQuestions(request.input)\n const [selections, setSelections] = useState<Selection[]>(() =>\n questions.map(() => EMPTY_SELECTION),\n )\n // `lines` only: the roving cursor per question, and which question's list owns\n // the DOM focus — two lists both chasing their own index would tear the caret\n // between them on every render.\n const [cursors, setCursors] = useState<number[]>(() => questions.map(() => 0))\n const [activeQuestion, setActiveQuestion] = useState(0)\n\n const update = (index: number, patch: Partial<Selection>) => {\n setSelections((prev) => prev.map((s, i) => (i === index ? { ...s, ...patch } : s)))\n }\n\n const toggle = (index: number, label: string, multiSelect: boolean) => {\n const current = selections[index] ?? EMPTY_SELECTION\n if (multiSelect) {\n update(index, {\n labels: current.labels.includes(label)\n ? current.labels.filter((l) => l !== label)\n : [...current.labels, label],\n })\n } else {\n update(index, { labels: current.labels[0] === label ? [] : [label], otherActive: false })\n }\n }\n\n const complete = questions.every((_, i) => answerFor(selections[i] ?? EMPTY_SELECTION) !== '')\n\n const submit = () => {\n const answers: Record<string, string> = {}\n questions.forEach((q, i) => {\n answers[q.question] = answerFor(selections[i] ?? EMPTY_SELECTION)\n })\n onAnswer(request.id, { ...request.input, answers })\n }\n\n if (lines) {\n const dismiss = () => onDismiss(request.id, 'Question dismissed by user')\n return (\n <div\n data-slot='question-prompt'\n className={cn('flex w-full flex-col gap-2', className)}\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n dismiss()\n return\n }\n // Enter belongs to the focused row (it is a button), so committing the\n // whole form needs the modifier — the composer's own convention.\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey) && complete) {\n event.preventDefault()\n submit()\n }\n }}>\n {questions.map((q, index) => {\n const selection = selections[index] ?? EMPTY_SELECTION\n const cursor = cursors[index] ?? 0\n const multiSelect = q.multiSelect === true\n const options = [\n ...q.options.map((option, optionIndex) => {\n const selected = selection.labels.includes(option.label)\n return {\n key: option.label,\n label: option.label,\n description: option.description,\n // A preview shows on focus, not only on selection: in a list you\n // walk with the arrow keys, \"what does this one look like\" is the\n // question the cursor is asking.\n detail:\n option.preview && (selected || cursor === optionIndex) ? (\n <LinePayload code={option.preview} label='Preview' />\n ) : undefined,\n // Both kinds draw their state: without a marker a chosen\n // one-of is invisible the moment the cursor moves off it.\n checked: selected,\n marker: multiSelect ? ('check' as const) : ('radio' as const),\n }\n }),\n {\n key: '__other',\n label: 'Other…',\n checked: selection.otherActive,\n marker: multiSelect ? ('check' as const) : ('radio' as const),\n detail: selection.otherActive ? (\n <LineInput\n value={selection.other}\n onChange={(value) => update(index, { other: value })}\n onSubmit={() => {\n // The typed text is already in state, so `complete` is\n // current: Enter finishes the form when this was the last\n // question outstanding, and is a no-op when it wasn't.\n if (complete) submit()\n }}\n onCancel={() => update(index, { otherActive: false, other: '' })}\n placeholder='Type your own answer'\n />\n ) : undefined,\n },\n ]\n return (\n <div key={index} className='flex flex-col'>\n <div className='flex items-baseline gap-2'>\n <LineGlyph className='text-info'>?</LineGlyph>\n <span className='min-w-0 flex-1 text-body-sm leading-5 text-fg-1'>\n {q.header ? <span className='text-fg-4'>{q.header} · </span> : null}\n <span className='font-medium'>{q.question}</span>\n </span>\n </div>\n <LineOptionList\n label={q.question}\n options={options}\n focused={cursor}\n active={activeQuestion === index && !selection.otherActive}\n onFocus={(next) => {\n setActiveQuestion(index)\n setCursors((prev) => prev.map((c, i) => (i === index ? next : c)))\n }}\n onChoose={(next) => {\n if (next === q.options.length) {\n update(index, { otherActive: !selection.otherActive })\n return\n }\n const option = q.options[next]\n if (option) toggle(index, option.label, multiSelect)\n }}\n />\n </div>\n )\n })}\n <div className={cn(LINE_INDENT, 'flex flex-col')}>\n <button\n type='button'\n disabled={!complete}\n onClick={submit}\n className='w-fit text-body-sm leading-5 text-accent underline-offset-2 outline-none hover:underline disabled:text-fg-4 disabled:no-underline'>\n ⏎ Answer\n </button>\n </div>\n <LineHint>\n ↑↓ move · {multiSelectAnywhere(questions) ? 'space/1–9 toggle' : '1–9 choose'} · ⌘⏎ answer\n · esc dismiss\n </LineHint>\n </div>\n )\n }\n\n return (\n <div\n data-slot='question-prompt'\n className={cn('rounded-lg border border-info/40 bg-info-bg p-3', className)}>\n <div className='flex items-start gap-2.5'>\n <MessageCircleQuestion className='mt-0.5 size-4 shrink-0 text-info' />\n <div className='flex min-w-0 flex-1 flex-col gap-3'>\n {questions.map((q, index) => {\n const selection = selections[index] ?? EMPTY_SELECTION\n return (\n <div key={index} className='flex flex-col gap-1.5'>\n <div className='flex items-center gap-2'>\n {q.header ? <Badge variant='info'>{q.header}</Badge> : null}\n <span className='text-body-sm font-medium text-fg-1'>{q.question}</span>\n </div>\n <div className='flex flex-col gap-1'>\n {q.options.map((option) => {\n const selected = selection.labels.includes(option.label)\n return (\n <button\n key={option.label}\n type='button'\n onClick={() => toggle(index, option.label, q.multiSelect === true)}\n className={cn(\n 'rounded-md border px-2.5 py-1.5 text-left transition-colors',\n selected\n ? 'border-info bg-bg'\n : 'border-border bg-bg/50 hover:border-border-strong hover:bg-bg',\n )}>\n <span className='block text-body-sm font-medium text-fg-1'>\n {option.label}\n </span>\n {option.description ? (\n <span className='block text-label text-fg-4'>{option.description}</span>\n ) : null}\n {selected && option.preview ? (\n <pre className='mt-1.5 max-h-40 overflow-auto rounded-md bg-code-bg px-2.5 py-1.5 font-mono text-label whitespace-pre-wrap text-fg-2'>\n {option.preview}\n </pre>\n ) : null}\n </button>\n )\n })}\n <div className='flex items-center gap-2'>\n <button\n type='button'\n onClick={() => update(index, { otherActive: !selection.otherActive })}\n className={cn(\n 'shrink-0 rounded-md border px-2.5 py-1.5 text-body-sm font-medium transition-colors',\n selection.otherActive\n ? 'border-info bg-bg text-fg-1'\n : 'border-border bg-bg/50 text-fg-3 hover:border-border-strong hover:bg-bg',\n )}>\n Other…\n </button>\n {selection.otherActive ? (\n <Input\n autoFocus\n value={selection.other}\n onChange={(e) => update(index, { other: e.target.value })}\n placeholder='Type your own answer'\n className='flex-1'\n />\n ) : null}\n </div>\n </div>\n </div>\n )\n })}\n <div>\n <Button size='sm' onClick={submit} disabled={!complete}>\n Answer\n </Button>\n </div>\n </div>\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Dismiss question'\n className='shrink-0'\n onClick={() => onDismiss(request.id, 'Question dismissed by user')}>\n <X className='size-3.5' />\n </Button>\n </div>\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { SessionFileInfo } from '@workerdeck/protocol'\nimport type { TranscriptState } from '@workerdeck/react'\nimport { Download } from 'lucide-react'\nimport { CopyButton } from '../ui/CopyButton.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader, DialogRow } from '../ui/Dialog.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { formatBytes, formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { permissionModeMeta } from './PermissionModeSelect.tsx'\n\nexport interface SessionInfoDialogProps {\n state: TranscriptState\n client: WorkerDeckClient\n sessionId: string | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * What this session *is*: engine, profile, model, mode, where it runs, which\n * credentials it found, and the files it has handed over.\n *\n * The identity half of the session's facts. Context and usage have their own\n * panels — they change every turn and are consulted mid-run, while everything\n * here is fixed at creation and looked up once.\n */\nexport function SessionInfoDialog({\n state,\n client,\n sessionId,\n open,\n onOpenChange,\n}: SessionInfoDialogProps) {\n const session = state.session\n const mode = state.permissionMode ? permissionModeMeta(state.permissionMode) : undefined\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader title='Session info' description={session?.title} />\n <DialogBody>\n <div className='flex flex-col divide-y divide-border'>\n <div className='pb-2'>\n <DialogRow label='Engine'>{state.engine ?? 'claude'}</DialogRow>\n {session?.profile ? (\n <DialogRow label='Profile'>{session.profile}</DialogRow>\n ) : null}\n {state.model ? (\n <DialogRow label='Model' mono>\n {state.model}\n </DialogRow>\n ) : null}\n {mode ? <DialogRow label='Permission mode'>{mode.label}</DialogRow> : null}\n {session?.apiKeySource ? (\n <DialogRow label='Credentials'>{session.apiKeySource}</DialogRow>\n ) : null}\n </div>\n <div className='py-2'>\n {state.cwd ? <CopyRow label='Working directory' value={state.cwd} /> : null}\n {state.sdkSessionId ? (\n <CopyRow label='Engine session id' value={state.sdkSessionId} />\n ) : null}\n {session ? <CopyRow label='Gateway session id' value={session.id} /> : null}\n </div>\n <div className='py-2'>\n {session?.createdAt ? (\n <DialogRow label='Started'>{formatRelativeTime(session.createdAt)}</DialogRow>\n ) : null}\n {session?.numTurns !== undefined ? (\n <DialogRow label='Turns'>{session.numTurns}</DialogRow>\n ) : null}\n <DialogRow label='Cost' mono>\n {formatCost(state.totalCostUsd)}\n </DialogRow>\n </div>\n {/* Only for an engine that has a scratch VFS — for the rest the route\n 404s, and a \"Files\" heading over nothing reads as a failed load. */}\n {state.capabilities.vfs ? (\n <SessionFiles client={client} sessionId={sessionId} open={open} />\n ) : null}\n </div>\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\n/** Long ids are for pasting elsewhere, so give them a copy target. */\nfunction CopyRow({ label, value }: { label: string; value: string }) {\n return (\n <div className='flex items-start justify-between gap-2 py-1.5'>\n <div className='min-w-0 flex-1'>\n <div className='text-label text-fg-3'>{label}</div>\n <div className='mt-0.5 font-mono text-label break-all text-fg-1'>{value}</div>\n </div>\n <CopyButton value={value} aria-label={`Copy ${label.toLowerCase()}`} />\n </div>\n )\n}\n\n/** Files the agent produced inside the session's VFS. Fetched when the panel\n * opens — this is not a live list and nothing pushes changes to it. */\nfunction SessionFiles({\n client,\n sessionId,\n open,\n}: {\n client: WorkerDeckClient\n sessionId: string | undefined\n open: boolean\n}) {\n const [files, setFiles] = useState<SessionFileInfo[] | undefined>()\n const [loading, setLoading] = useState(false)\n\n useEffect(() => {\n if (!open || !sessionId) return\n let cancelled = false\n setLoading(true)\n client\n .listSessionFiles(sessionId)\n .then((list) => {\n if (!cancelled) setFiles(list)\n })\n .catch(() => {\n if (!cancelled) setFiles([])\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [client, sessionId, open])\n\n return (\n <div className='pt-3'>\n <h3 className='text-label font-medium text-fg-3'>Files</h3>\n {loading ? (\n <div className='py-3 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n ) : !files?.length ? (\n <p className='py-2 text-body-sm text-fg-4'>Nothing delivered yet.</p>\n ) : (\n <ul className='mt-1 flex flex-col'>\n {files.map((file) => (\n <li key={file.path}>\n <a\n href={sessionId ? client.sessionFileUrl(sessionId, file.path) : undefined}\n className='flex items-center gap-2 rounded-md px-1 py-1.5 hover:bg-surface-hover'>\n <Download className='size-3.5 shrink-0 text-fg-4' />\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {file.path}\n </span>\n <span className='shrink-0 text-label text-fg-4'>{formatBytes(file.bytes)}</span>\n </a>\n </li>\n ))}\n </ul>\n )}\n </div>\n )\n}\n","import type { SessionStatus } from '@workerdeck/protocol'\nimport type { BadgeProps } from '../ui/Badge.tsx'\n\nexport const STATUS_META: Record<\n SessionStatus,\n { label: string; variant: NonNullable<BadgeProps['variant']>; busy: boolean }\n> = {\n starting: { label: 'Starting', variant: 'info', busy: true },\n running: { label: 'Running', variant: 'info', busy: true },\n awaiting_approval: { label: 'Needs approval', variant: 'warning', busy: true },\n idle: { label: 'Idle', variant: 'success', busy: false },\n // Waiting on a deferred execution: the run is alive but nothing is burning.\n parked: { label: 'Parked', variant: 'accent', busy: false },\n failed: { label: 'Failed', variant: 'danger', busy: false },\n closed: { label: 'Closed', variant: 'neutral', busy: false },\n}\n","import { useEffect, useState, type ReactNode } from 'react'\nimport type { ConnectionState, TranscriptState } from '@workerdeck/react'\nimport type { ContextUsage, RateLimitInfo } from '@workerdeck/protocol'\nimport { RefreshCw, WifiOff } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { ProgressRing } from '../ui/ProgressRing.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { Tip } from '../ui/Tooltip.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatCountdown, formatTokens } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface StatusBarProps {\n state: TranscriptState\n /** @deprecated Pass {@link StatusBarProps.connection}; kept so an embedder\n * still handing over a boolean keeps working. */\n connected?: boolean\n /** How the client is doing at reaching the gateway. A dropped socket wins the\n * status slot: the session status held over a dead socket is a stale reading,\n * and presenting it as a live one is the thing worth avoiding. */\n connection?: ConnectionState\n /**\n * Where the gauges lead. Each one opens the panel that answers *its* question\n * — the two meters measure different things, so sending both to one \"details\"\n * list would be a detour every time. Omit a handler and that gauge stays a\n * read-only tooltip.\n */\n onOpenStatus?: () => void\n onOpenContext?: () => void\n onOpenUsage?: () => void\n /** Trailing slot — the session-actions menu, in the panel's top-right. */\n actions?: ReactNode\n className?: string\n}\n\n/** Ticking clock for reset countdowns — rate_limit events are sparse, so tick locally. */\nfunction useNow(intervalMs = 30_000): number {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), intervalMs)\n return () => clearInterval(timer)\n }, [intervalMs])\n return now\n}\n\nconst utilizationColor = (pct: number) =>\n pct >= 95 ? 'text-danger' : pct >= 80 ? 'text-warning' : 'text-fg-3'\n\n/** The CLI reports category colors as its own theme token names ('inactive',\n * 'promptBorder', ...), not CSS colors — only pass through what CSS can render. */\nconst cssColor = (color: string): string | undefined =>\n typeof CSS !== 'undefined' && CSS.supports('color', color) ? color : undefined\n\nfunction ContextMeter({ usage }: { usage: ContextUsage }) {\n return (\n <Tip\n content={\n <div className='flex min-w-44 flex-col gap-1 py-0.5'>\n {usage.categories.map((c) => (\n <div key={c.name} className='flex items-center gap-2'>\n <span\n className='size-2 shrink-0 rounded-full bg-fg-4'\n style={cssColor(c.color) ? { backgroundColor: c.color } : undefined}\n />\n <span className='flex-1'>{c.name}</span>\n <span className='font-mono text-fg-3'>{formatTokens(c.tokens)}</span>\n </div>\n ))}\n <div className='mt-0.5 flex items-center justify-between gap-2 border-t border-border pt-1'>\n <span>Total</span>\n <span className='font-mono text-fg-3'>\n {formatTokens(usage.totalTokens)} / {formatTokens(usage.maxTokens)} (\n {usage.percentage.toFixed(0)}%)\n </span>\n </div>\n </div>\n }>\n <span\n className={cn(\n 'inline-flex cursor-default items-center gap-1 font-mono text-label',\n utilizationColor(usage.percentage),\n )}>\n Ctx {formatTokens(usage.totalTokens)}\n </span>\n </Tip>\n )\n}\n\nfunction RateLimitMeter({ label, info, now }: { label: string; info: RateLimitInfo; now: number }) {\n // The CLI omits utilization on some updates — show the window without a made-up 0%.\n const pct = info.utilization\n const resetsAtMs = info.resetsAt !== undefined ? info.resetsAt * 1000 : undefined\n return (\n <Tip\n content={\n <div className='flex min-w-36 flex-col gap-1 py-0.5'>\n <div className='flex items-center justify-between gap-2'>\n <span>{label} usage</span>\n <span className='font-mono text-fg-3'>\n {pct !== undefined ? `${pct.toFixed(1)}%` : '—'}\n </span>\n </div>\n {resetsAtMs !== undefined ? (\n <div className='flex items-center justify-between gap-2'>\n <span>Resets in</span>\n <span className='font-mono text-fg-3'>{formatCountdown(resetsAtMs, now)}</span>\n </div>\n ) : null}\n {info.isUsingOverage ? <div className='text-warning'>Using overage</div> : null}\n {info.status === 'rejected' ? <div className='text-danger'>Limit reached</div> : null}\n </div>\n }>\n <span\n className={cn(\n 'inline-flex cursor-default items-center gap-1 font-mono text-label',\n info.status === 'rejected' ? 'text-danger' : utilizationColor(pct ?? 0),\n )}>\n <ProgressRing value={pct ?? 0} />\n {label}\n {pct !== undefined ? ` ${pct.toFixed(0)}%` : ''}\n {resetsAtMs !== undefined ? (\n <span className='text-fg-4'>· {formatCountdown(resetsAtMs, now)}</span>\n ) : null}\n </span>\n </Tip>\n )\n}\n\n/** A gauge that leads somewhere. `undefined` handler leaves it inert, so an\n * embedder that mounts no panels doesn't get buttons that do nothing. */\nfunction Slot({ onClick, hint, children }: { onClick?: () => void; hint: string; children: ReactNode }) {\n if (!onClick) return <>{children}</>\n return (\n <button\n type='button'\n onClick={onClick}\n aria-label={hint}\n className='rounded-md px-1 py-0.5 transition-colors outline-none hover:bg-surface-hover focus-visible:bg-surface-hover'>\n {children}\n </button>\n )\n}\n\nexport function StatusBar({\n state,\n connected,\n connection,\n onOpenStatus,\n onOpenContext,\n onOpenUsage,\n actions,\n className,\n}: StatusBarProps) {\n const meta = STATUS_META[state.status]\n const now = useNow()\n const session = state.rateLimits?.five_hour\n const weekly = state.rateLimits?.seven_day\n const link: ConnectionState = connection ?? (connected === false ? 'reconnecting' : 'live')\n return (\n <div\n data-slot='status-bar'\n className={cn(\n 'flex items-center gap-2 border-b border-border bg-surface px-3 py-1.5',\n className,\n )}>\n {/* One slot, two meanings: connection trouble wins it, because a session\n status shown over a dead socket is a stale reading presented as a live\n one. Tapping it opens the session's own facts — where it runs, on what,\n with which credentials — which is the question a status prompts. */}\n <Slot onClick={onOpenStatus} hint='Session info'>\n {link === 'live' ? (\n <Badge variant={meta.variant} dot={!meta.busy}>\n {meta.busy ? <Spinner className='size-3 text-current' /> : null}\n {meta.label}\n </Badge>\n ) : (\n <Badge variant={link === 'offline' ? 'danger' : 'warning'} dot={false}>\n {link === 'offline' ? (\n <WifiOff className='size-3 text-current' />\n ) : (\n <RefreshCw className='size-3 animate-spin text-current' />\n )}\n {link === 'offline' ? 'Offline' : 'Reconnecting…'}\n </Badge>\n )}\n </Slot>\n {/* Never a 0% meter for an engine that doesn't measure: an absent reading\n and a full window are not the same claim. */}\n {state.capabilities.contextUsage && state.contextUsage ? (\n <Slot onClick={onOpenContext} hint='Context breakdown'>\n <ContextMeter usage={state.contextUsage} />\n </Slot>\n ) : null}\n {session || weekly ? (\n <Slot onClick={onOpenUsage} hint='Plan usage'>\n <span className='inline-flex items-center gap-2'>\n {session ? <RateLimitMeter label='Session' info={session} now={now} /> : null}\n {weekly ? <RateLimitMeter label='Weekly' info={weekly} now={now} /> : null}\n </span>\n </Slot>\n ) : null}\n <span className='flex-1' />\n <span className='font-mono text-label text-fg-3'>{formatCost(state.totalCostUsd)}</span>\n {actions}\n </div>\n )\n}\n","import type { HTMLAttributes, ReactNode } from 'react'\nimport { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom'\nimport { ArrowDown } from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface ConversationProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n children: ReactNode\n /**\n * How the view follows content that grows. `'smooth'` is right for a live\n * turn — the text scrolls as it is written. `'instant'` is right while a\n * transcript is *filling* (an attach replaying a whole session), where\n * animating each arrival makes opening a session a several-second journey\n * from its first row to its last.\n */\n resize?: 'smooth' | 'instant'\n}\n\n/** Scroll container that stays pinned to the bottom while streaming, unless the user\n * scrolls up — plus a floating scroll-to-bottom button. */\nexport function Conversation({ className, children, resize = 'smooth', ...props }: ConversationProps) {\n return (\n <StickToBottom\n data-slot='conversation'\n className={cn('relative flex-1 overflow-y-auto', className)}\n initial='instant'\n resize={resize}\n role='log'\n {...props}>\n {children}\n </StickToBottom>\n )\n}\n\nexport function ConversationContent({\n className,\n children,\n ...props\n}: HTMLAttributes<HTMLDivElement> & { children: ReactNode }) {\n return (\n <StickToBottom.Content\n data-slot='conversation-content'\n className={cn('mx-auto flex w-full max-w-[var(--wd-content-max-w,48rem)] flex-col gap-4 px-4 py-4', className)}\n {...props}>\n {children}\n </StickToBottom.Content>\n )\n}\n\nexport function ConversationScrollButton({ className }: { className?: string }) {\n const { isAtBottom, scrollToBottom } = useStickToBottomContext()\n if (isAtBottom) return null\n return (\n <Button\n variant='outline'\n size='icon'\n aria-label='Scroll to bottom'\n data-slot='conversation-scroll-button'\n className={cn(\n 'absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-(--shadow-md)',\n className,\n )}\n onClick={() => void scrollToBottom()}>\n <ArrowDown className='size-4' />\n </Button>\n )\n}\n","import type { TranscriptItem } from '@workerdeck/react'\nimport { Download, FileText } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatBytes } from '../../lib/format.ts'\nimport { LineGlyph, useLines } from './transcript-variant.tsx'\n\nexport type FileDeliveredItem = Extract<TranscriptItem, { kind: 'file_delivered' }>\n\nexport interface FileCardProps {\n item: FileDeliveredItem\n /** Download URL for the delivered path (the server's session file route).\n * Without it the card renders informational only. */\n href?: string\n className?: string\n}\n\n/** A file the agent handed over (`file_delivered`): name, size, download link.\n * The file lives in the session's in-memory VFS — the link works while the\n * session lives. */\nexport function FileCard({ item, href, className }: FileCardProps) {\n const lines = useLines()\n const name = item.path.split('/').pop() || item.path\n if (lines) {\n // One row: what arrived, how big, and the link — no box around it.\n return (\n <div data-slot='file-delivered' className={cn('flex w-full items-baseline gap-2', className)}>\n <LineGlyph className='text-fg-4'>◇</LineGlyph>\n <span className='min-w-0 flex-1 truncate text-body-sm leading-5'>\n <span className='font-medium text-fg-1'>{name}</span>\n <span className='text-fg-4'>\n {' · '}\n {formatBytes(item.bytes)}\n {item.description ? ` · ${item.description}` : ''}\n </span>\n </span>\n {href ? (\n <a\n href={href}\n download={name}\n className='shrink-0 text-label text-fg-3 underline-offset-2 hover:text-fg-1 hover:underline'>\n download\n </a>\n ) : null}\n </div>\n )\n }\n return (\n <div\n data-slot='file-delivered'\n className={cn(\n 'flex w-full items-center gap-3 rounded-lg border border-border bg-surface px-3 py-2.5',\n className,\n )}>\n <FileText className='size-4 shrink-0 text-fg-3' />\n <div className='min-w-0 flex-1'>\n <div className='truncate font-mono text-body-sm font-medium text-fg-1'>{name}</div>\n <div className='truncate text-label text-fg-4'>\n {formatBytes(item.bytes)}\n {item.description ? ` · ${item.description}` : ''}\n </div>\n </div>\n {href ? (\n <a\n href={href}\n download={name}\n className={cn(\n 'flex shrink-0 items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5',\n 'text-body-sm text-fg-2 transition-colors hover:bg-surface-hover',\n )}>\n <Download className='size-3.5' />\n Download\n </a>\n ) : null}\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatDuration, formatTokens } from '../../lib/format.ts'\nimport { LineGlyph, useLines } from './transcript-variant.tsx'\n\nexport interface LoaderProps {\n /** Overrides the cycling verb — for a state that has one true name\n * (\"Starting session…\"). */\n label?: string\n /** When the current run began, for the elapsed clock. Absent = no clock. */\n startedAt?: number\n /** Context tokens in play, shown beside the clock. */\n tokens?: number\n className?: string\n}\n\n/**\n * The frames of the working marker, and the rate they turn over.\n *\n * A four-pointed star growing and shrinking — it reads as *activity* at a glance\n * without any of the pixel-fitting a braille or block spinner needs, and it is\n * the same shape a terminal agent uses because a terminal is where this\n * vocabulary comes from. ~8fps: fast enough to be alive, slow enough not to\n * strobe next to streaming text.\n */\nconst FRAMES = ['✢', '✳', '✶', '✻', '✽', '✻', '✶', '✳']\nconst FRAME_MS = 120\n\n/**\n * What it says it is doing while it hasn't said anything yet. Cycled on a slow\n * clock so a long turn doesn't sit under one frozen word — a still label reads\n * as a hung process, which is exactly what this is meant to disprove.\n */\nconst VERBS = [\n 'Working',\n 'Thinking',\n 'Churning',\n 'Pondering',\n 'Whirring',\n 'Computing',\n 'Percolating',\n 'Tinkering',\n 'Deliberating',\n 'Simmering',\n 'Crunching',\n 'Noodling',\n]\nconst VERB_MS = 4000\n\n/** Ticks while mounted, at the spinner's rate. Mounted only while a turn is in\n * flight, so nothing here runs on an idle session. */\nfunction useFrames(animated: boolean): number {\n const [frame, setFrame] = useState(0)\n useEffect(() => {\n if (!animated) return\n const timer = setInterval(() => setFrame((f) => f + 1), FRAME_MS)\n return () => clearInterval(timer)\n }, [animated])\n return frame\n}\n\n/** The OS-level \"stop moving things\" setting. A spinner is decoration; the word\n * beside it carries the meaning, so honouring this costs nothing. */\nfunction usePrefersReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n useEffect(() => {\n const query = window.matchMedia?.('(prefers-reduced-motion: reduce)')\n if (!query) return\n setReduced(query.matches)\n const onChange = () => setReduced(query.matches)\n query.addEventListener('change', onChange)\n return () => query.removeEventListener('change', onChange)\n }, [])\n return reduced\n}\n\n/**\n * \"The agent is working and hasn't produced output yet.\"\n *\n * `lines`: a terminal working line — animated glyph in the gutter, a verb, and\n * the readings that answer \"should I still be waiting?\" in one parenthesis.\n * `cards`: the three-dot pulse, unchanged.\n */\nexport function Loader({ label, startedAt, tokens, className }: LoaderProps) {\n const lines = useLines()\n const reducedMotion = usePrefersReducedMotion()\n const frame = useFrames(lines && !reducedMotion)\n\n if (!lines) {\n return (\n <div\n data-slot='loader'\n className={cn('flex items-center gap-2 py-1 text-body-sm text-fg-4', className)}>\n <span className='flex items-center gap-1'>\n {[0, 1, 2].map((i) => (\n <span\n key={i}\n className='size-1.5 animate-pulse rounded-full bg-fg-4'\n style={{ animationDelay: `${i * 160}ms` }}\n />\n ))}\n </span>\n {label ? <span>{label}</span> : null}\n </div>\n )\n }\n\n // Derived from the clock rather than kept in state: the frame tick is already\n // re-rendering, so the seconds and the verb come along for free.\n const elapsed = startedAt === undefined ? undefined : Date.now() - startedAt\n const verb =\n label ?? VERBS[Math.floor((elapsed ?? 0) / VERB_MS) % VERBS.length] ?? VERBS[0]\n const readings = [\n elapsed !== undefined ? formatDuration(elapsed) : undefined,\n tokens !== undefined ? `↓ ${formatTokens(tokens)}` : undefined,\n ].filter(Boolean)\n\n return (\n <div data-slot='loader' className={cn('flex items-baseline gap-2', className)}>\n <LineGlyph className='text-accent'>{FRAMES[frame % FRAMES.length]}</LineGlyph>\n <span className='min-w-0 flex-1 text-body-sm leading-5 text-fg-3'>\n {verb}…{' '}\n {readings.length ? <span className='text-label text-fg-4'>({readings.join(' · ')})</span> : null}\n </span>\n </div>\n )\n}\n","import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport { LINE_TEXT, LineGlyph, useLines } from './transcript-variant.tsx'\n\nexport interface MessageProps extends HTMLAttributes<HTMLDivElement> {\n from: 'user' | 'assistant'\n}\n\n/**\n * One chat turn row.\n *\n * `cards`: user messages sit right in a bubble; assistant content is flat,\n * full-width (the AI-chat convention — assistant output is the page, user input\n * is quoted).\n *\n * `lines`: both are left-aligned full-width line items behind a gutter glyph —\n * `❯` for what was typed, `●` for what the model said. No bubble: a prompt is\n * already distinguishable by its marker, and the bubble's padding is vertical\n * space the terminal treatment refuses to spend.\n */\nexport function Message({ from, className, children, ...props }: MessageProps) {\n const lines = useLines()\n return (\n <div\n data-slot='message'\n data-from={from}\n className={cn(\n 'flex w-full',\n lines\n ? cn(\n 'flex-row gap-2',\n // What YOU said, on a band of its own — the CLI's own treatment.\n // Full-bleed (the negative margin cancels the row's padding) so\n // the band lines up with the hover highlight rather than sitting\n // inside it, and square-ish so it reads as a strip, not a bubble.\n from === 'user' && '-mx-1 rounded-sm bg-surface px-1',\n )\n : cn('flex-col gap-1', from === 'user' ? 'items-end' : 'items-start'),\n className,\n )}\n {...props}>\n {lines ? (\n <>\n <LineGlyph className={from === 'user' ? 'text-accent' : 'text-fg-3'}>\n {from === 'user' ? '❯' : '●'}\n </LineGlyph>\n <div className='flex min-w-0 flex-1 flex-col gap-1'>{children}</div>\n </>\n ) : (\n children\n )}\n </div>\n )\n}\n\nexport function MessageContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n const lines = useLines()\n return (\n <div\n data-slot='message-content'\n className={cn(\n 'min-w-0',\n lines\n ? cn('w-full text-fg-1', LINE_TEXT, 'in-data-[from=user]:whitespace-pre-wrap')\n : cn(\n 'text-body-sm leading-6 text-fg-1',\n // Bubble treatment only within a user message row.\n 'in-data-[from=user]:max-w-[85%] in-data-[from=user]:rounded-lg in-data-[from=user]:rounded-br-sm',\n 'in-data-[from=user]:bg-accent-bg in-data-[from=user]:px-3 in-data-[from=user]:py-2',\n 'in-data-[from=user]:whitespace-pre-wrap',\n 'in-data-[from=assistant]:w-full',\n ),\n className,\n )}\n {...props}\n />\n )\n}\n","import { Fragment } from 'react'\nimport { scanPromptTokens } from '@workerdeck/react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * A sent message, with its `@file` and `/command` tokens styled the way the CLI\n * writes them: monospace and tinted, no background — the bubble already has one,\n * and a second fill inside it reads as a button.\n *\n * Literal text, never markdown: what was typed is what was sent. The one pass\n * over it is this, so a message reads the same after sending as it did in the\n * composer. Two tokens, two meanings — a file is a reference, a command is an\n * action — so they are told apart by hue rather than by shape alone.\n */\nexport function PromptTokenText({ text, className }: { text: string; className?: string }) {\n const tokens = scanPromptTokens(text)\n if (tokens.length === 0) return <span className={className}>{text}</span>\n const parts: React.ReactNode[] = []\n let cursor = 0\n for (const [index, token] of tokens.entries()) {\n if (token.start > cursor) parts.push(text.slice(cursor, token.start))\n parts.push(\n <span\n key={`${token.start}-${index}`}\n className={cn('font-mono', token.kind === 'file' ? 'text-accent' : 'text-info')}>\n {token.text}\n </span>,\n )\n cursor = token.end\n }\n if (cursor < text.length) parts.push(text.slice(cursor))\n return (\n <span className={className}>\n {parts.map((part, index) => (\n <Fragment key={index}>{part}</Fragment>\n ))}\n </span>\n )\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { Brain, ChevronDown } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Response } from './Response.tsx'\nimport { LineGlyph, useLines } from './transcript-variant.tsx'\n\nexport interface ReasoningProps {\n children: string\n /** Auto-opens while true, auto-closes shortly after it flips false. */\n isStreaming?: boolean\n defaultOpen?: boolean\n className?: string\n}\n\n/** Collapsible extended-thinking block: open while the model is thinking, tucks itself\n * away once the thought is finished (unless the user toggled it manually). */\nexport function Reasoning({ children, isStreaming = false, defaultOpen, className }: ReasoningProps) {\n const lines = useLines()\n const [open, setOpen] = useState(defaultOpen ?? isStreaming)\n const userToggled = useRef(false)\n\n useEffect(() => {\n if (userToggled.current) return\n if (isStreaming) {\n setOpen(true)\n } else {\n const timer = setTimeout(() => setOpen(false), 600)\n return () => clearTimeout(timer)\n }\n }, [isStreaming])\n\n // Models with encrypted thinking emit the blocks but never the summary text. Then there is\n // nothing to expand: show a bare \"Thinking…\" marker live, and nothing at all once it's done.\n const hasText = children.trim() !== ''\n if (!hasText && !isStreaming) return null\n\n return (\n <div data-slot='reasoning' data-streaming={isStreaming || undefined} className={cn('w-full', className)}>\n <button\n type='button'\n disabled={!hasText}\n onClick={() => {\n userToggled.current = true\n setOpen((v) => !v)\n }}\n className={cn(\n 'flex items-baseline text-label text-fg-3 transition-colors outline-none disabled:cursor-default hover:text-fg-1 disabled:hover:text-fg-3',\n lines ? 'w-full gap-2' : 'items-center gap-1.5',\n )}>\n {lines ? (\n <LineGlyph className='text-fg-4'>✻</LineGlyph>\n ) : (\n <Brain className='size-3.5' />\n )}\n <span className={cn(lines && 'leading-5')}>\n {isStreaming ? 'Thinking…' : 'Thought process'}\n </span>\n {hasText && !lines ? (\n <ChevronDown className={cn('size-3.5 transition-transform', open && 'rotate-180')} />\n ) : null}\n </button>\n {open && hasText ? (\n <div\n className={cn(\n 'border-l border-border text-body-sm text-fg-3 [&_*]:text-fg-3',\n lines ? 'ml-[0.4375rem] mt-0.5 pl-2 leading-5' : 'mt-2 border-l-2 pl-3',\n )}>\n <Response streaming={isStreaming}>{children}</Response>\n </div>\n ) : null}\n </div>\n )\n}\n","import { AtSign, MessageSquareText, SlashSquare, Sparkles, Terminal } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface SessionEmptyStateProps {\n cwd?: string\n /** Whether `/command` completion is live yet — the CLI reports its commands a\n * beat after the session starts, and promising a feature that isn't wired up\n * yet is worse than not mentioning it. */\n hasCommands?: boolean\n /** Whether the engine has reported skills the `/` popover can offer. Its own\n * flag, not a variant of `hasCommands`: what `/` does differs — a command is\n * submitted, a skill is typed for you to edit — and an engine can have one\n * without the other. */\n hasSkills?: boolean\n /** Whether this gateway serves `@file` search for the session's directory. */\n canBrowseFiles?: boolean\n className?: string\n}\n\n/**\n * What a session shows before it has said anything: where the agent is sitting,\n * and what the composer accepts beyond prose.\n *\n * Every hint is conditional on the affordance actually existing — an engine\n * without slash commands, or a gateway without host files, is not told about\n * them. Deliberately no project name (the header already carries it) and no\n * brand mark (its geometry is inlined in several places already and they are\n * meant to stay identical).\n */\nexport function SessionEmptyState({\n cwd,\n hasCommands,\n hasSkills,\n canBrowseFiles,\n className,\n}: SessionEmptyStateProps) {\n const hints = [\n { icon: MessageSquareText, text: 'Tell the agent what to do.' },\n // Two keys, two hints — they are different features, not two spellings of\n // one. `$` is codex's own sigil for skills; `/` stays the CLI's commands.\n ...(hasCommands ? [{ icon: SlashSquare, text: 'Type / for the CLI’s slash commands.' }] : []),\n ...(hasSkills\n ? [{ icon: Sparkles, text: 'Type $ to draft a message for one of the agent’s skills.' }]\n : []),\n ...(canBrowseFiles\n ? [{ icon: AtSign, text: 'Type @ to search this project’s files.' }]\n : []),\n ]\n return (\n <div className={cn('flex flex-col items-center gap-4 py-10', className)}>\n <div className='flex size-12 items-center justify-center rounded-xl bg-surface text-fg-3'>\n <Terminal className='size-5' />\n </div>\n {cwd ? <p className='max-w-full truncate font-mono text-label text-fg-4'>{cwd}</p> : null}\n <ul className='w-full max-w-sm divide-y divide-border overflow-hidden rounded-lg bg-surface'>\n {hints.map((hint) => (\n <li key={hint.text} className='flex items-center gap-3 px-3.5 py-2.5'>\n <hint.icon className='size-4 shrink-0 text-fg-4' />\n <span className='text-body-sm text-fg-3'>{hint.text}</span>\n </li>\n ))}\n </ul>\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { ChevronDown, Clock } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { CodeBlock } from '../ui/CodeBlock.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { toolInputPreview } from '../../lib/format.ts'\nimport { isMutatingTool, toolIcon } from '../../lib/tool-icon.ts'\nimport { LINE_INDENT, LinePayload } from './line-prompt.tsx'\nimport { LineGlyph, useLines } from './transcript-variant.tsx'\n\nexport type ToolCallItem = Extract<TranscriptItem, { kind: 'tool_call' }>\n\nconst RESULT_PREVIEW_CHARS = 2000\n\n/** Codex's built-in image tools name a host path rather than sending bytes —\n * an event log carries references, never base64. Rendering the picture means\n * reading that path back through the gateway's host-file route. */\nconst IMAGE_TOOLS = new Set(['CodexImageGeneration', 'CodexImageView'])\n\nconst imagePathOf = (item: ToolCallItem): string | undefined => {\n if (!IMAGE_TOOLS.has(item.name)) return undefined\n const input = item.input as { savedPath?: unknown; path?: unknown } | null\n const path = input?.savedPath ?? input?.path\n return typeof path === 'string' ? path : undefined\n}\n\nexport interface ToolCallCardProps {\n item: ToolCallItem\n /**\n * Reads a host file as a data URL, for tools whose output is a picture on the\n * host. Resolves `undefined` when the gateway won't serve that path — a\n * generated image saved outside the allowed roots (codex's default\n * `$CODEX_HOME/generated_images/`) is one, and the card then names the path\n * instead of showing it.\n */\n hostImage?: (path: string) => Promise<string | undefined>\n className?: string\n}\n\n/** Badge per execution state. `pending`/`deferred` mean the work is happening\n * somewhere else (this tab's sandbox, a queue) — distinct from the model still\n * running the call itself. */\nconst STATE_BADGE = {\n running: { label: 'Running', variant: 'info', busy: true },\n pending: { label: 'Executing', variant: 'info', busy: true },\n deferred: { label: 'Deferred', variant: 'accent', busy: false },\n settled: { label: 'Done', variant: 'success', busy: false },\n failed: { label: 'Error', variant: 'danger', busy: false },\n} as const\n\n/** The gutter dot's colour in `lines`: the state, said without a badge. */\nconst STATE_GLYPH = {\n running: 'text-info',\n pending: 'text-info',\n deferred: 'text-accent',\n settled: 'text-fg-4',\n failed: 'text-danger',\n} as const\n\n/**\n * The language to highlight a payload as.\n *\n * Parameters are always JSON. A result is whatever file the call was about — so\n * the extension in `file_path`/`path` is the best evidence there is, and a call\n * that names no file gets no guess (plain text renders fine and a wrong grammar\n * is worse than none).\n */\nconst EXTENSION_LANGUAGE: Record<string, string> = {\n ts: 'ts', tsx: 'tsx', js: 'js', jsx: 'jsx', mjs: 'js', cjs: 'js',\n json: 'json', jsonc: 'json', md: 'md', mdx: 'md', css: 'css', scss: 'scss',\n html: 'html', xml: 'xml', yml: 'yaml', yaml: 'yaml', toml: 'toml',\n py: 'python', rb: 'ruby', go: 'go', rs: 'rust', java: 'java', kt: 'kotlin',\n swift: 'swift', c: 'c', h: 'c', cpp: 'cpp', hpp: 'cpp', cs: 'csharp',\n php: 'php', sh: 'bash', bash: 'bash', zsh: 'bash', fish: 'fish', sql: 'sql',\n graphql: 'graphql', dockerfile: 'dockerfile', diff: 'diff', patch: 'diff',\n}\n\nfunction resultLanguage(item: ToolCallItem): string | undefined {\n if (item.name === 'Bash' || item.name === 'CodexCommand') return 'bash'\n if (item.name === 'CodexFileChange') return 'diff'\n const input = item.input as { file_path?: unknown; path?: unknown } | null\n const path = input?.file_path ?? input?.path\n if (typeof path !== 'string') return undefined\n const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase()\n return EXTENSION_LANGUAGE[extension]\n}\n\ntype Status = keyof typeof STATE_BADGE\n\nexport function ToolCallCard({ item, hostImage, className }: ToolCallCardProps) {\n const lines = useLines()\n const [open, setOpen] = useState(false)\n const [fullResult, setFullResult] = useState(false)\n const imagePath = imagePathOf(item)\n const status: Status = item.status ?? (item.result === undefined ? 'running' : 'settled')\n const badge = STATE_BADGE[status]\n const isError = status === 'failed' || item.result?.isError === true\n const Icon = toolIcon(item.name)\n\n const resultText = item.result?.text ?? ''\n const truncated = !fullResult && resultText.length > RESULT_PREVIEW_CHARS\n const shownResult = truncated ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText\n\n // In `lines` the payloads are dim label + highlighted band, not framed cards:\n // a tool call is already one row, and boxing what it expands into puts back\n // the chrome the variant exists to remove.\n const blockVariant = lines ? 'plain' : 'panel'\n const Payload = lines ? HighlightedPayload : PlainPayload\n const details = open ? (\n <div className={cn('flex flex-col', lines ? 'gap-1 py-1' : 'gap-2 border-t border-border p-2.5')}>\n <Payload\n code={JSON.stringify(item.input, null, 2)}\n language='json'\n label='Parameters'\n variant={blockVariant}\n />\n {item.logs?.length ? (\n <Payload code={item.logs.join('\\n')} label='Logs' variant={blockVariant} />\n ) : null}\n {item.result !== undefined ? (\n <div>\n <Payload\n code={shownResult || '(empty result)'}\n language={resultLanguage(item)}\n label={isError ? 'Error' : 'Result'}\n variant={blockVariant}\n className={cn(isError && (lines ? '[&_pre]:text-danger' : 'border-danger/40 [&_pre]:text-danger'))}\n />\n {truncated ? (\n <button\n type='button'\n className='mt-1 text-label text-fg-3 underline-offset-2 hover:underline'\n onClick={() => setFullResult(true)}>\n Show all {resultText.length.toLocaleString()} chars\n </button>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null\n\n // The picture is the point of the call — shown without expanding, the way the\n // tool's own output would be if the engine had sent bytes.\n const image = imagePath && hostImage ? <HostImage path={imagePath} load={hostImage} lines={lines} /> : null\n\n if (lines) {\n return (\n <div data-slot='tool-call' data-state={status} className={cn('w-full', className)}>\n <button\n type='button'\n onClick={() => setOpen((v) => !v)}\n className='flex w-full items-baseline gap-2 text-left outline-none'>\n <LineGlyph\n className={\n // A settled write is green: skimming a run, \"what did it change\"\n // is the question you come back to, and it is the one you might\n // need to undo. Every other state keeps its own colour — a failed\n // write is a failure first.\n status === 'settled' && !isError && isMutatingTool(item.name)\n ? 'text-success'\n : STATE_GLYPH[status]\n }>\n {badge.busy ? '◐' : '●'}\n </LineGlyph>\n <span className='min-w-0 flex-1 truncate text-body-sm leading-5 text-fg-3'>\n <span className='font-medium text-fg-1'>{item.name}</span>\n <span className='text-fg-4'>({toolInputPreview(item.input)})</span>\n </span>\n {item.backend && item.backend !== 'server' ? (\n <span className='shrink-0 text-label text-fg-4'>{item.backend}</span>\n ) : null}\n {badge.busy ? <Spinner className='size-3 shrink-0 self-center text-fg-4' /> : null}\n {status === 'deferred' ? <Clock className='size-3 shrink-0 self-center text-fg-4' /> : null}\n {isError && !badge.busy ? (\n <span className='shrink-0 text-label text-danger'>error</span>\n ) : null}\n </button>\n {/* Collapsed, the first line of the output is the whole story most of\n the time — a summary line costs one row and saves an expand. */}\n {!open && resultText ? (\n <div className='flex items-baseline gap-2'>\n <LineGlyph className='text-fg-4'>⎿</LineGlyph>\n <span\n className={cn(\n 'min-w-0 flex-1 truncate text-label leading-5',\n isError ? 'text-danger' : 'text-fg-4',\n )}>\n {resultSummary(resultText)}\n </span>\n </div>\n ) : null}\n {image}\n {details ? <div className={LINE_INDENT}>{details}</div> : null}\n </div>\n )\n }\n\n return (\n <div\n data-slot='tool-call'\n data-state={status}\n className={cn('w-full overflow-hidden rounded-lg border border-border bg-surface', className)}>\n <button\n type='button'\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'flex w-full items-center gap-2 px-3 py-2 text-left transition-colors outline-none',\n 'hover:bg-surface-hover focus-visible:bg-surface-hover',\n )}>\n <Icon className='size-3.5 shrink-0 text-fg-3' />\n <span className='shrink-0 font-mono text-body-sm font-medium text-fg-1'>{item.name}</span>\n {item.backend && item.backend !== 'server' ? (\n <Badge variant='neutral' className='shrink-0'>\n {item.backend}\n </Badge>\n ) : null}\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-4'>\n {toolInputPreview(item.input)}\n </span>\n <Badge variant={badge.variant} dot={!badge.busy} className='shrink-0 gap-1'>\n {badge.busy ? <Spinner className='size-3 text-current' /> : null}\n {status === 'deferred' ? <Clock className='size-3 text-current' /> : null}\n {badge.label}\n </Badge>\n <ChevronDown\n className={cn('size-3.5 shrink-0 text-fg-4 transition-transform', open && 'rotate-180')}\n />\n </button>\n {image}\n {details}\n </div>\n )\n}\n\n/** The first line worth showing of a tool result, for the collapsed summary. */\nfunction resultSummary(text: string): string {\n const first = text.split('\\n').find((line) => line.trim() !== '') ?? ''\n const rest = text.trimEnd().split('\\n').length - 1\n const trimmed = first.trim()\n return rest > 0 ? `${trimmed} (+${rest} lines)` : trimmed\n}\n\ntype PayloadProps = {\n code: string\n label: string\n language?: string\n variant: 'panel' | 'plain'\n className?: string\n}\n\n/** The framed card, for the `cards` transcript. Unhighlighted by design: it is\n * structured data in a panel, not a file. */\nfunction PlainPayload({ code, label, variant, className }: PayloadProps) {\n return <CodeBlock code={code} label={label} variant={variant} className={className} />\n}\n\n/** The terminal payload — shared with the line-shaped prompts. */\nfunction HighlightedPayload({ code, label, language, className }: PayloadProps) {\n return <LinePayload code={code} label={label} language={language} className={className} />\n}\n\n/**\n * A picture that lives on the host, fetched through the gateway's host-file\n * route and shown inline.\n *\n * Silent on failure by design: a path outside the server's allowed roots is the\n * *expected* case for codex's default save location, and the card's result text\n * already names where the file went. An error banner over that would be noise\n * about a thing the operator can fix in one line of config.\n */\nfunction HostImage({\n path,\n load,\n lines,\n}: {\n path: string\n load: (path: string) => Promise<string | undefined>\n lines?: boolean\n}) {\n const [src, setSrc] = useState<string | undefined>()\n useEffect(() => {\n let cancelled = false\n setSrc(undefined)\n load(path)\n .then((url) => {\n if (!cancelled) setSrc(url)\n })\n .catch(() => {\n // Not readable from here — the path in the result is the answer.\n })\n return () => {\n cancelled = true\n }\n }, [path, load])\n if (!src) return null\n return (\n <div className={cn(lines ? cn('py-1', LINE_INDENT) : 'border-t border-border p-2.5')}>\n <img\n src={src}\n alt={path.split('/').pop() ?? 'Generated image'}\n className='max-h-96 w-auto max-w-full rounded-md border border-border'\n />\n </div>\n )\n}\n","import { useEffect, useMemo, useRef, useState, type RefObject } from 'react'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport { useStickToBottomContext } from 'use-stick-to-bottom'\nimport type { MessageAttachment } from '@workerdeck/protocol'\nimport { recapLine, summarizeSince, type TranscriptItem, type TranscriptState } from '@workerdeck/react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatDuration, formatRelativeTime } from '../../lib/format.ts'\nimport { Conversation, ConversationContent, ConversationScrollButton } from './Conversation.tsx'\nimport { FileCard } from './FileCard.tsx'\nimport { Loader } from './Loader.tsx'\nimport { Message, MessageContent } from './Message.tsx'\nimport { PromptTokenText } from './PromptTokenText.tsx'\nimport { Reasoning } from './Reasoning.tsx'\nimport { Response } from './Response.tsx'\nimport { SessionEmptyState } from './SessionEmptyState.tsx'\nimport { ToolCallCard } from './ToolCallCard.tsx'\nimport {\n LineGlyph,\n TranscriptVariantProvider,\n useLines,\n type TranscriptVariant,\n} from './transcript-variant.tsx'\n\nfunction TurnResultRow({ item }: { item: Extract<TranscriptItem, { kind: 'turn_result' }> }) {\n const lines = useLines()\n if (lines) {\n // One dim line, no rules: the turn's end is a footnote, not a divider that\n // costs three rows of vertical space.\n return (\n <div data-slot='turn-result' className='flex items-baseline gap-2'>\n <LineGlyph className='text-fg-4'>·</LineGlyph>\n <div className='min-w-0 flex-1'>\n <span className={cn('text-label leading-5', item.isError ? 'text-danger' : 'text-fg-4')}>\n {item.isError ? item.subtype : 'turn done'} · {formatDuration(item.durationMs)} ·{' '}\n {formatCost(item.totalCostUsd)}\n </span>\n {item.errors?.length ? (\n <ul className='flex flex-col'>\n {item.errors.map((message, index) => (\n <li key={index} className='text-label leading-5 break-words text-danger'>\n {message}\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n </div>\n )\n }\n return (\n <div data-slot='turn-result' className='py-1'>\n <div className='flex items-center gap-2'>\n <div className='h-px flex-1 bg-border' />\n <span className={cn('font-mono text-label', item.isError ? 'text-danger' : 'text-fg-4')}>\n {item.isError ? item.subtype : 'turn done'} · {formatDuration(item.durationMs)} ·{' '}\n {formatCost(item.totalCostUsd)}\n </span>\n <div className='h-px flex-1 bg-border' />\n </div>\n {/* A failed turn's reasons are the whole point of the row — dropping them\n leaves \"error_during_execution\" and nothing to act on. */}\n {item.errors?.length ? (\n <ul className='mt-1 flex flex-col gap-0.5 text-center'>\n {item.errors.map((message, index) => (\n <li key={index} className='text-label break-words text-danger'>\n {message}\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n )\n}\n\nfunction NoticeRow({ item }: { item: Extract<TranscriptItem, { kind: 'notice' }> }) {\n const lines = useLines()\n if (lines) {\n return (\n <div data-slot='notice' className='flex items-baseline gap-2'>\n <LineGlyph className={item.level === 'error' ? 'text-danger' : 'text-fg-4'}>!</LineGlyph>\n <span\n className={cn(\n 'min-w-0 flex-1 text-body-sm leading-5',\n item.level === 'error' ? 'text-danger' : 'text-fg-3',\n )}>\n {item.text}\n </span>\n </div>\n )\n }\n return (\n <div\n data-slot='notice'\n className={cn(\n 'rounded-md border px-3 py-2 text-body-sm',\n item.level === 'error'\n ? 'border-transparent bg-danger-bg text-danger'\n : 'border-border bg-surface text-fg-3',\n )}>\n {item.text}\n </div>\n )\n}\n\nfunction TranscriptItemView({\n item,\n fileUrl,\n attachmentUrl,\n hostImage,\n}: {\n item: TranscriptItem\n fileUrl?: (path: string) => string\n attachmentUrl?: (attachmentId: string) => string\n hostImage?: (path: string) => Promise<string | undefined>\n}) {\n switch (item.kind) {\n case 'user':\n return (\n <Message from='user'>\n {item.attachments?.length ? (\n <SentAttachments attachments={item.attachments} attachmentUrl={attachmentUrl} />\n ) : null}\n {/* A photo can be the whole message — an empty bubble under it says nothing. */}\n {item.text ? (\n <MessageContent>\n <PromptTokenText text={item.text} />\n </MessageContent>\n ) : null}\n </Message>\n )\n case 'assistant_text':\n return (\n <Message from='assistant'>\n <MessageContent>\n <Response streaming={item.streaming}>{item.text}</Response>\n </MessageContent>\n </Message>\n )\n case 'thinking':\n return <Reasoning isStreaming={item.id === 'streaming-thinking'}>{item.text}</Reasoning>\n case 'tool_call':\n return <ToolCallCard item={item} hostImage={hostImage} />\n case 'turn_result':\n return <TurnResultRow item={item} />\n case 'notice':\n return <NoticeRow item={item} />\n case 'file_delivered':\n return <FileCard item={item} href={fileUrl?.(item.path)} />\n default:\n return null\n }\n}\n\n/**\n * The \"you were here\" line: what happened since the session was last looked at,\n * counted from the transcript rather than written by the model (see\n * `summarizeSince`). Everything above it is dimmed while catch-up is on, so the\n * boundary is visible from anywhere in the scrollback, not just at the mark.\n *\n * The line itself is computed in `Transcript`, not here: the recap is a row of\n * the virtual list, and a boundary with nothing to say must contribute no row\n * at all rather than an empty slot that still costs its gap.\n */\nfunction RecapRow({ line, since }: { line: string; since?: number }) {\n const lines = useLines()\n const away = since === undefined ? undefined : formatRelativeTime(since)\n const text = away ? `${line} · last here ${away}` : line\n\n if (lines) {\n return (\n <div data-slot='recap' className='flex items-baseline gap-2 py-0.5'>\n <LineGlyph className='text-accent'>※</LineGlyph>\n <span className='min-w-0 flex-1 text-label leading-5 text-fg-3'>\n <span className='text-fg-2'>recap:</span> {text}\n </span>\n </div>\n )\n }\n return (\n <div data-slot='recap' className='flex items-center gap-2 py-1'>\n <div className='h-px flex-1 bg-border' />\n <span className='font-mono text-label text-fg-3'>※ recap: {text}</span>\n <div className='h-px flex-1 bg-border' />\n </div>\n )\n}\n\n/**\n * Has the transcript stopped *filling* and started *streaming*?\n *\n * Attaching replays the whole session as a burst of events, so the content grows\n * by hundreds of rows over a few hundred milliseconds. Animating that — which is\n * the right behaviour for a live turn — turns opening a session into a\n * several-second scroll from its first row to its last, which is precisely wrong\n * when you are skimming sessions to see where each agent got to.\n *\n * So: instant until the arrivals stop for a beat, smooth from then on. Latched,\n * because a live turn is bursty too and nobody wants the follow behaviour to\n * flicker between modes mid-answer.\n *\n * Two guards, both load-bearing, both learned from this going wrong:\n *\n * - **Silence before the first row is not quiet, it is waiting.** The attach\n * that fills this transcript is a round trip — and in a VS Code webview it is\n * webview → extension host → gateway and back — so the first replayed row can\n * land well after the quiet window. A timer started at mount then latches\n * *before the transcript exists*, and the whole replay animates: exactly the\n * symptom the latch was added to prevent.\n * - **Only a live turn earns smooth.** An idle session has nothing to animate.\n * Skimming finished sessions is therefore instant no matter what the timing\n * did, which is the case that has to be right.\n */\nfunction useSettled(count: number, status: TranscriptState['status'], quietMs = 400): boolean {\n const [settled, setSettled] = useState(false)\n useEffect(() => {\n if (settled || count === 0) return\n const timer = setTimeout(() => setSettled(true), quietMs)\n return () => clearTimeout(timer)\n }, [count, settled, quietMs])\n return settled && (status === 'running' || status === 'starting')\n}\n\n/**\n * When the current run began — the clock the working line counts from.\n *\n * Taken here rather than in the loader because the loader comes and goes within\n * a single turn (it hides the moment text starts streaming), and a clock that\n * restarted every time the model paused for a tool would be measuring the wrong\n * thing. Held as state, not a ref: the value has to survive re-renders and reset\n * exactly once, when the session goes back to idle.\n */\nfunction useRunStart(status: TranscriptState['status']): number | undefined {\n const running = status === 'running' || status === 'starting'\n const [startedAt, setStartedAt] = useState<number | undefined>(undefined)\n useEffect(() => {\n setStartedAt((previous) => (running ? (previous ?? Date.now()) : undefined))\n }, [running])\n return running ? startedAt : undefined\n}\n\n/** Should the \"waiting for output\" loader show? Only while running with no in-flight\n * streamed content at the tail of the transcript. */\nfunction showLoader(state: TranscriptState): boolean {\n if (state.status !== 'running' && state.status !== 'starting') return false\n const last = state.items.at(-1)\n if (!last) return true\n if (last.kind === 'assistant_text' && last.streaming) return false\n if (last.kind === 'thinking' && last.id === 'streaming-thinking') return false\n return last.kind !== 'turn_result' || state.status === 'running'\n}\n\n/** Files sent with a message: thumbnails for images, named chips for the rest.\n * References only — the bytes are fetched from the gateway. */\nfunction SentAttachments({\n attachments,\n attachmentUrl,\n}: {\n attachments: MessageAttachment[]\n attachmentUrl?: (attachmentId: string) => string\n}) {\n const lines = useLines()\n return (\n <div className={cn('mb-1 flex flex-wrap gap-1.5', lines ? 'justify-start' : 'justify-end')}>\n {attachments.map((attachment) => {\n const href = attachmentUrl?.(attachment.id)\n return attachment.mediaType.startsWith('image/') && href ? (\n <img\n key={attachment.id}\n src={href}\n alt={attachment.name}\n className='size-20 rounded-md border border-border object-cover'\n />\n ) : (\n <span\n key={attachment.id}\n className='rounded-full border border-border bg-surface px-2.5 py-1 text-body-xs text-fg-3'>\n {attachment.name}\n </span>\n )\n })}\n </div>\n )\n}\n\n/** Rows produced inside a subagent (`parentToolUseId != null`) are stepped in\n * behind a rule, so a Task's own output reads as belonging to the tool call\n * above it rather than as the main thread carrying on. */\nfunction nestedClass(item: TranscriptItem, lines: boolean): string | undefined {\n const nested = 'parentToolUseId' in item && item.parentToolUseId != null\n if (!nested) return undefined\n return lines ? 'ml-3.5 border-l border-border pl-2' : 'border-l-2 border-border pl-3'\n}\n\n/** One row of the virtual list: a transcript item, or the recap boundary line\n * spliced in at `catchUp.from`. One flat array so the virtualizer sees stable\n * indices, and each row carries the key the item was already React-keyed by —\n * measurements are cached per key, so a row keeps its measured height when the\n * recap splice shifts every index after it. */\ntype TranscriptRow =\n | { key: string; item: TranscriptItem; index: number }\n | { key: 'recap'; line: string }\n\n/**\n * The virtualized row window. Only rows near the viewport are mounted, so\n * opening a thousand-row session commits a screenful of DOM rather than the\n * whole session in one go.\n *\n * The delicate part is that two parties want to write `scrollTop`.\n * `use-stick-to-bottom` owns *following*: its spring animates toward the\n * bottom whenever the content grows, recomputing the target from the live\n * `scrollHeight` every frame. The virtualizer wants to *correct* `scrollTop`\n * whenever a row measures differently from its estimate, so that what the\n * reader is looking at doesn't shift. Letting both write at once is jitter at\n * best — and a correction that moves the viewport *up* (a row measuring\n * smaller than estimated) reads as a user scroll to the follow logic, which\n * escapes the bottom lock mid-stream.\n *\n * The resolution is that pinned and escaped are different regimes:\n *\n * - Pinned (`state.isAtBottom`), corrections are suppressed. \"At the bottom\"\n * is the entire scroll position; the offsets of rows above are moot. The\n * height change a mismeasured row causes still re-fires the follow spring —\n * through the same content resize observer that follows streaming — so the\n * view converges on the bottom through the one writer that knows how to\n * distinguish its own writes from the user's.\n * - Escaped, the virtualizer corrects (its stock rules, restated below) so\n * the scrollback holds still under the reader while rows above it measure.\n *\n * `anchorTo`/`followOnAppend` stay at their defaults for the same reason:\n * the virtualizer must never become a second follow implementation.\n *\n * Accepted costs of virtualizing, so they are a decision and not a surprise:\n * browser find-in-page and select-all reach only the mounted rows, and a row's\n * transient UI state (an expanded tool card, an opened reasoning block) resets\n * once the row scrolls far enough away to unmount.\n */\nfunction TranscriptRows({\n rows,\n boundary,\n since,\n lines,\n fileUrl,\n attachmentUrl,\n hostImage,\n jumpToRecapRef,\n}: {\n rows: TranscriptRow[]\n boundary: number | undefined\n since: number | undefined\n lines: boolean\n fileUrl?: (path: string) => string\n attachmentUrl?: (attachmentId: string) => string\n hostImage?: (path: string) => Promise<string | undefined>\n jumpToRecapRef?: RefObject<(() => void) | null>\n}) {\n const stick = useStickToBottomContext()\n // The scroll element belongs to an ancestor — `StickToBottom.Content`\n // renders it — so when this component's layout effects run at mount, the\n // ancestor's ref is not attached yet and the virtualizer would see null.\n // Handing it over from a passive effect (which runs after every ref in the\n // commit is attached) also guarantees the re-render that lets the\n // virtualizer adopt it promptly: without one, a transcript short enough to\n // never fire a scroll event could sit renderless indefinitely.\n const [scrollElement, setScrollElement] = useState<HTMLElement | null>(null)\n useEffect(() => {\n setScrollElement(stick.scrollRef.current)\n }, [stick.scrollRef])\n const virtualizer = useVirtualizer<HTMLElement, HTMLDivElement>({\n count: rows.length,\n // On adopting a scroll element the virtualizer *replays* its remembered\n // offset into the DOM (that is how `initialOffset` is honored). By then\n // the pin has usually scrolled the element already, so the remembered 0\n // would yank a pinned transcript silently back to the top — the observed\n // race was exactly that, the follow jump at ~180ms and the replay undoing\n // it at ~590ms. Syncing the remembered offset to the DOM at the adoption\n // boundary makes every replay a no-op; from then on the scroll observer\n // owns the field.\n // Annotated because the body reads `virtualizer` back: without a declared\n // return type the inference is circular and tsgo gives up on the whole hook.\n getScrollElement: (): HTMLElement | null => {\n if (scrollElement && virtualizer.scrollElement !== scrollElement) {\n virtualizer.scrollOffset = scrollElement.scrollTop\n }\n return scrollElement\n },\n // Estimates only shape the scrollbar and the span of never-mounted rows;\n // a measurement replaces them the moment a row mounts. A lines row is one\n // text line more often than not; cards vary too much for any constant to\n // be right, so that one is merely the order of magnitude.\n estimateSize: () => (lines ? 32 : 100),\n overscan: 8,\n getItemKey: (index) => rows[index].key,\n // Explicit, and left at the default, because the obvious cleanup here is\n // wrong. A correction fires from `measureElement`'s ref callback — inside\n // React's commit — so the core's synchronous flush draws a \"flushSync was\n // called from inside a lifecycle method\" error, which in an embedder's\n // console reads as our bug. Turning it off silences that and costs\n // anchoring: over the same six-step walk up through unmeasured rows, `true`\n // holds the scrollback to the pixel and `false` let one step slide 112px\n // under the reader. Holding still is the entire point of the correction,\n // so the noise stays.\n useFlushSync: true,\n // The list sits below the content div's top padding, so row offsets are a\n // few px shy of true scroll offsets. `scrollMargin` exists for exactly\n // this, but feeding it means measuring the spacer's offsetTop into state;\n // the error is smaller than one overscan row, so it is deliberately left.\n })\n // (See the component comment.) Supplying the callback at all replaces the\n // core's default rules, so the escaped branch restates them: on a first\n // measurement compensate any row whose top sits above the fold; on a\n // re-measurement only a row entirely above it — a row *spanning* the fold\n // grows below the reader's anchor point — and never while scrolling up,\n // where corrections cascade.\n virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (item, _delta, instance) => {\n if (stick.state.isAtBottom) return false\n const fold = (instance.scrollOffset ?? 0) + instance.scrollAdjustments\n return instance.itemSizeCache.has(item.key)\n ? item.end <= fold && instance.scrollDirection !== 'backward'\n : item.start < fold\n }\n\n // The catch-up strip's \"jump\" lives outside this scroll container, and the\n // recap row it targets is usually unmounted — only the virtualizer knows\n // where it would be, so the strip is handed this closure instead of a DOM\n // query. Smooth on purpose: the jump IS a journey, and watching it travel\n // is what tells you how far back the boundary was. `stopScroll()` first,\n // because the pin spring is the other `scrollTop` writer and this is the\n // library's own switch for \"the user is leaving the bottom\".\n //\n // And it has to *re-aim*. Every row between here and the boundary is\n // unmeasured, so the offset the virtualizer scrolls to is the sum of a few\n // hundred estimates; the real one only exists once those rows mount. A\n // single smooth scroll therefore lands a screen or two short — measured at\n // ~3300px off over 600 rows — and it cannot self-correct, because the core\n // suppresses size-change adjustments outright while a smooth scroll is in\n // flight. So: aim, let the rows it crossed measure, aim again from the\n // better estimate. Each pass overshoots by less, and the moment the row is\n // actually mounted the DOM can finish the job exactly.\n //\n // The pending re-aim lives in a ref, and this is the whole reason: the effect\n // that publishes the closure has to re-run every render to keep `rows` fresh,\n // so anything held in its scope is torn down every render too — and a jump in\n // flight re-renders constantly, because that is what rows mounting *is*. A\n // timer in the closure would be cancelled by the very work it is waiting for.\n const aimTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n useEffect(() => () => clearTimeout(aimTimer.current), [])\n useEffect(() => {\n if (!jumpToRecapRef) return\n jumpToRecapRef.current = () => {\n const index = rows.findIndex((row) => row.key === 'recap')\n if (index < 0) return\n stick.stopScroll()\n clearTimeout(aimTimer.current)\n const aim = (attempt: number) => {\n const row = scrollElement?.querySelector('[data-slot=\"recap\"]')\n if (row) {\n row.scrollIntoView({ behavior: 'smooth', block: 'center' })\n return\n }\n if (attempt >= 6) return\n virtualizer.scrollToIndex(index, { align: 'center', behavior: 'smooth' })\n // Longer than one frame: the aim is only better once the rows crossed\n // have mounted *and* been measured, and that is a layout pass away.\n aimTimer.current = setTimeout(() => aim(attempt + 1), 300)\n }\n aim(0)\n }\n return () => {\n jumpToRecapRef.current = null\n }\n })\n\n return (\n <div\n data-slot='transcript-rows'\n className='relative w-full'\n style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index]\n return (\n <div\n key={row.key}\n ref={virtualizer.measureElement}\n data-index={virtualRow.index}\n className={cn(\n 'absolute inset-x-0 top-0',\n // The card layout's inter-row gap, folded into each row so the\n // measured height carries it: flex `gap` cannot reach absolutely\n // positioned rows, and a pixel constant for the virtualizer's\n // `gap` option would drift from the rem the layout is set in.\n // On this outer wrapper, not the row div, so a nested row's left\n // border still breaks across the gap as it did under flex.\n !lines && virtualRow.index > 0 && 'pt-4',\n )}\n style={{ transform: `translateY(${virtualRow.start}px)` }}>\n {'item' in row ? (\n <div\n className={cn(\n // Full-bleed hover: the row IS the affordance, so the\n // highlight has to reach past the content gutter.\n lines && '-mx-1 rounded-sm px-1 py-0.5 transition-colors hover:bg-surface-hover',\n // Already read: present, legible, and visibly behind you.\n boundary !== undefined && row.index < boundary && 'opacity-45',\n nestedClass(row.item, lines),\n )}>\n <TranscriptItemView\n item={row.item}\n fileUrl={fileUrl}\n attachmentUrl={attachmentUrl}\n hostImage={hostImage}\n />\n </div>\n ) : (\n <RecapRow line={row.line} since={since} />\n )}\n </div>\n )\n })}\n </div>\n )\n}\n\nexport interface TranscriptProps {\n state: TranscriptState\n /** Builds the download URL for a delivered file (see FileCard). Typically\n * `(path) => client.sessionFileUrl(sessionId, path)`. */\n fileUrl?: (path: string) => string\n /** Builds the URL for an uploaded attachment. Typically\n * `(id) => client.attachmentUrl(sessionId, id)`. Same-origin and\n * cookie-authenticated, which is what lets an `<img src>` render one. */\n attachmentUrl?: (attachmentId: string) => string\n /** Whether this gateway serves `@file` search here — the empty state must not\n * advertise an affordance the composer doesn't have. */\n canBrowseFiles?: boolean\n /** Reads a host file as a data URL, for tool calls whose output is a picture\n * on the host (codex's `image_gen`). Omit and those cards name the path. */\n hostImage?: (path: string) => Promise<string | undefined>\n /**\n * How a turn is drawn: `cards` (default, the chat convention) or `lines` —\n * full-width transparent line items with a gutter glyph, for hosts where\n * vertical space is scarce. See {@link TranscriptVariant}.\n */\n variant?: TranscriptVariant\n /**\n * Catch-up: `from` is how many items had been seen last time, `since` when\n * that was. A recap row is drawn at that boundary and everything above it is\n * dimmed. Omit (or pass a boundary at/after the end) and the transcript\n * renders exactly as before.\n */\n catchUp?: { from: number; since?: number }\n /**\n * Filled with a closure that scrolls the recap row into view — the seam the\n * panel's catch-up strip presses. A ref rather than a DOM query because the\n * rows are virtualized: when the recap row isn't mounted, only the\n * virtualizer knows where it would be. Optional; embedders without a\n * catch-up strip never touch it. `null` while no transcript is mounted.\n */\n jumpToRecapRef?: RefObject<(() => void) | null>\n className?: string\n}\n\nexport function Transcript({\n state,\n fileUrl,\n attachmentUrl,\n canBrowseFiles,\n hostImage,\n variant = 'cards',\n catchUp,\n jumpToRecapRef,\n className,\n}: TranscriptProps) {\n const lines = variant === 'lines'\n const runStartedAt = useRunStart(state.status)\n const following = useSettled(state.items.length, state.status)\n // A boundary at (or past) the end means nothing is new — no row, no dimming.\n const boundary =\n catchUp && catchUp.from > 0 && catchUp.from < state.items.length ? catchUp.from : undefined\n const recap = useMemo(\n () => (boundary === undefined ? undefined : recapLine(summarizeSince(state, boundary))),\n [state, boundary],\n )\n const rows = useMemo(() => {\n const out: TranscriptRow[] = []\n for (const [index, item] of state.items.entries()) {\n if (index === boundary && recap) out.push({ key: 'recap', line: recap })\n out.push({ key: `${item.kind}:${item.id}`, item, index })\n }\n return out\n }, [state.items, boundary, recap])\n return (\n <TranscriptVariantProvider value={variant}>\n <Conversation className={className} resize={following ? 'smooth' : 'instant'}>\n <ConversationContent className={cn(lines && 'gap-0 px-2 py-1.5')}>\n {state.items.length === 0 && state.status !== 'starting' ? (\n <SessionEmptyState\n cwd={state.cwd}\n hasCommands={!!state.commands?.length}\n hasSkills={!!state.skills?.some((s) => s.enabled)}\n canBrowseFiles={canBrowseFiles}\n />\n ) : (\n <TranscriptRows\n rows={rows}\n boundary={boundary}\n since={catchUp?.since}\n lines={lines}\n fileUrl={fileUrl}\n attachmentUrl={attachmentUrl}\n hostImage={hostImage}\n jumpToRecapRef={jumpToRecapRef}\n />\n )}\n {showLoader(state) ? (\n <Loader\n label={state.status === 'starting' ? 'Starting session…' : undefined}\n startedAt={runStartedAt}\n tokens={state.contextUsage?.totalTokens}\n className={cn(lines && 'py-0.5')}\n />\n ) : null}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n </TranscriptVariantProvider>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { ProfileEngine, RateLimitInfo } from '@workerdeck/protocol'\nimport { RotateCcw } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader } from '../ui/Dialog.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport {\n formatAgoPrecise,\n formatCost,\n formatCountdown,\n formatRateLimitWindowLong,\n rateLimitWindowSeconds,\n} from '../../lib/format.ts'\n\nexport interface UsageDialogProps {\n /** Windows in reading order — session, weekly, then per-model weeklies. */\n rateLimits: Array<{ key: string; info: RateLimitInfo }>\n /** claude.ai plan behind the windows ('max', 'pro', …), when there is one. */\n subscriptionType?: string\n engine: ProfileEngine\n totalCostUsd: number\n /** Local receipt time of the last window update. `rate_limit` events are one\n * per turn at best, so a stale reading is normal and worth saying out loud. */\n updatedAt?: number\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/** Ticking clock — the countdowns and the pace markers both move with it, and a\n * minute is the finest resolution either of them prints. */\nfunction useMinuteClock(open: boolean): number {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n if (!open) return\n setNow(Date.now())\n const timer = setInterval(() => setNow(Date.now()), 60_000)\n return () => clearInterval(timer)\n }, [open])\n return now\n}\n\nconst usageTint = (pct: number) => (pct >= 90 ? 'bg-danger' : pct >= 70 ? 'bg-warning' : 'bg-accent')\n\n/**\n * The plan's rate-limit windows, spelled out: how much of each is used, how that\n * compares to the pace that would spend the window exactly, and when it resets.\n *\n * The pace marker is the point. A bar alone says \"17% used\", which is only\n * alarming or reassuring once you know how far into the week you are — so every\n * window draws a tick at the elapsed share of its duration. Left of the tick is\n * under budget, right of it is ahead of it. The duration comes from the window\n * key (5h, 7d) because the CLI reports a reset time and a percentage and never a\n * duration; a window whose key doesn't say gets no marker rather than a guessed one.\n */\nexport function UsageDialog({\n rateLimits,\n subscriptionType,\n engine,\n totalCostUsd,\n updatedAt,\n open,\n onOpenChange,\n}: UsageDialogProps) {\n const now = useMinuteClock(open)\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader\n title='Usage'\n description={engine === 'claude' ? 'Claude Code' : engine}\n actions={\n // The CLI reports a tier ('max'), never the multiplier a\n // subscription page shows — so this says \"Max\" and stops there.\n subscriptionType ? (\n <Badge variant='accent' className='mt-0.5 shrink-0 capitalize'>\n {subscriptionType}\n </Badge>\n ) : null\n }\n />\n <DialogBody>\n {rateLimits.length === 0 ? (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n {engine === 'claude'\n ? 'This session reports no plan windows — API-key sessions have none, and a subscription session reports them once a turn has run.'\n : `Plan windows are a claude.ai subscription thing; this session runs on the ${engine} engine.`}\n </p>\n ) : (\n <div className='flex flex-col gap-5'>\n {rateLimits.map(({ key, info }) => (\n <UsageWindow key={key} windowKey={key} info={info} now={now} />\n ))}\n </div>\n )}\n <div className='mt-5 flex items-baseline justify-between gap-4 border-t border-border pt-3'>\n <span className='text-label text-fg-3'>This session has cost</span>\n <span className='font-mono text-body-sm text-fg-1'>{formatCost(totalCostUsd)}</span>\n </div>\n {updatedAt ? (\n <p className='mt-2 text-label text-fg-4'>Updated {formatAgoPrecise(updatedAt, now)}</p>\n ) : null}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction UsageWindow({\n windowKey,\n info,\n now,\n}: {\n windowKey: string\n info: RateLimitInfo\n now: number\n}) {\n const utilization = info.utilization ?? 0\n const resetsAtMs = info.resetsAt !== undefined ? info.resetsAt * 1000 : undefined\n // Share of the window already elapsed — where usage *would* be if it were\n // spent evenly. Needs both a duration (from the key) and a reset time.\n const duration = rateLimitWindowSeconds(windowKey)\n const remaining = resetsAtMs !== undefined ? (resetsAtMs - now) / 1000 : undefined\n const pace =\n duration !== undefined && remaining !== undefined && remaining > 0 && remaining < duration\n ? (duration - remaining) / duration\n : undefined\n return (\n <div>\n <div className='flex items-baseline justify-between gap-3'>\n <span className='truncate text-body-sm text-fg-1'>\n {formatRateLimitWindowLong(windowKey)}\n </span>\n <span\n className={cn(\n 'shrink-0 font-mono text-body-sm font-medium',\n info.status === 'rejected' ? 'text-danger' : 'text-fg-1',\n )}>\n {utilization.toFixed(0)}% used\n </span>\n </div>\n <div className='relative mt-2 h-2 rounded-full bg-border'>\n <div\n className={cn('h-full rounded-full', usageTint(utilization))}\n // A floor, so a barely-touched window still shows a mark instead of\n // reading as missing data.\n style={{ width: `${Math.min(100, Math.max(2, utilization))}%` }}\n />\n {pace !== undefined ? (\n <span\n aria-hidden\n title='Spent evenly, usage would be here by now'\n className='absolute -top-1 h-4 w-0.5 -translate-x-1/2 rounded-full bg-fg-1'\n style={{ left: `${Math.min(100, Math.max(0, pace * 100))}%` }}\n />\n ) : null}\n </div>\n <div className='mt-1.5 flex items-center gap-3 text-label text-fg-4'>\n {resetsAtMs !== undefined ? (\n <span className='inline-flex items-center gap-1'>\n <RotateCcw className='size-3' /> Resets in {formatCountdown(resetsAtMs, now)}\n </span>\n ) : null}\n {info.isUsingOverage ? <span className='text-warning'>overage</span> : null}\n {info.status === 'rejected' ? <span className='text-danger'>limit reached</span> : null}\n </div>\n </div>\n )\n}\n","import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { PROTOCOL_VERSION, type ModelOption, type PermissionMode } from '@workerdeck/protocol'\nimport {\n rateLimitWindows,\n useAttachments,\n useClaudeSession,\n useHostFileSearch,\n useToolCallHost,\n type ConnectionState,\n type ProducedFileRef,\n type TranscriptState,\n} from '@workerdeck/react'\nimport {\n ChartPie,\n FolderTree,\n Gauge,\n Info,\n MoreHorizontal,\n Plug,\n Sparkles,\n TriangleAlert,\n X,\n} from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Menu, MenuContent, MenuItem, MenuTrigger } from '../ui/Menu.tsx'\nimport { Composer, skillPrompt, type ComposerHandle } from './Composer.tsx'\nimport { ContextDialog } from './ContextDialog.tsx'\nimport { HostFilesDialog } from './HostFilesDialog.tsx'\nimport { McpDialog } from './McpDialog.tsx'\nimport { SkillsDialog } from './SkillsDialog.tsx'\nimport { ModelSelect } from './ModelSelect.tsx'\nimport {\n PermissionModeSelect,\n permissionModeChoices,\n type PermissionModeChoice,\n} from './PermissionModeSelect.tsx'\nimport { PermissionPrompt } from './PermissionPrompt.tsx'\nimport { QuestionPrompt, parseUserQuestions } from './QuestionPrompt.tsx'\nimport { SessionInfoDialog } from './SessionInfoDialog.tsx'\nimport { StatusBar } from './StatusBar.tsx'\nimport { Transcript } from './Transcript.tsx'\nimport { TranscriptVariantProvider, type TranscriptVariant } from './transcript-variant.tsx'\nimport { UsageDialog } from './UsageDialog.tsx'\n\nexport interface SessionPanelProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n /**\n * Optional slot rendered at the top, above the status bar.\n *\n * Pass a **function** to take the session-actions (`⋯`) menu into your own\n * chrome: it is called with the menu element, and wherever you put it is\n * where it lives — the status bar then renders without it, so it never\n * appears twice. Pass a plain node (or nothing) and the menu stays in the\n * status bar's trailing slot.\n *\n * The seam exists because the menu can only be *built* here — it needs the\n * capability record, the host-file verdict and the panel's own dialog state —\n * but an embedder with a real header usually wants it up there with the rest\n * of the session's controls, not stranded on the status line.\n */\n header?: ReactNode | ((slots: { actions: ReactNode }) => ReactNode)\n /**\n * Where the info/context/usage/MCP/skills/files surfaces live. `'internal'`\n * (default) renders them as dialogs inside the panel. `'external'` renders\n * NO dialogs and no `⋯` menu: every affordance that would open one calls\n * {@link onOpenPanel} instead, so an embedder can host those surfaces in its\n * own chrome (a VS Code sidebar, a drawer) and keep the panel purely a\n * conversation surface.\n */\n panelSurface?: 'internal' | 'external'\n /**\n * Where the status bar lives. `'internal'` (default) draws it across the top\n * of the panel. `'external'` draws none — the readings still leave through\n * {@link onVitals}, so an embedder with a status line of its own (VS Code's\n * window status bar) renders them there instead of stacking a second bar\n * inside a panel that already sits in one.\n *\n * Deliberately independent of {@link panelSurface}: hosting the dialogs and\n * hosting the bar are separate decisions. One coupling to know about — the\n * `⋯` menu lives in the bar's trailing slot, so `statusSurface: 'external'`\n * with `panelSurface: 'internal'` must pass a **function** {@link header} to\n * take the menu, or it has nowhere left to go.\n */\n statusSurface?: 'internal' | 'external'\n /** Where `panelSurface: 'external'` routes opens. Absent = the affordances\n * (status-bar clicks, `/mcp`) become inert rather than half-working. */\n onOpenPanel?: (panel: SessionSurfacePanel) => void\n /** Live session vitals, fired whenever they change — for embedders mirroring\n * status/context/usage into chrome outside the panel (identity-stable via an\n * internal ref, so an inline closure is fine). */\n onVitals?: (vitals: SessionVitals) => void\n /**\n * How the transcript draws a turn — `'cards'` (default) or `'lines'`, the\n * space-efficient terminal treatment: no boxes, no bubbles, one full-width\n * hover-highlit row per event behind a gutter glyph. An embedder in a dock\n * (the VS Code panel) wants `'lines'`; a full-width dashboard usually doesn't.\n */\n transcriptVariant?: TranscriptVariant\n /**\n * Where the session's own controls — model and permission mode — live.\n * `'internal'` (default) draws them in the composer's toolbar row.\n * `'external'` draws neither, and the composer collapses to a single line\n * with its attach/send buttons beside the field: the embedder renders the\n * pickers in its own chrome (VS Code's status bar, where a click opens a\n * QuickPick) and drives them through {@link onControls}.\n *\n * The options themselves ride {@link SessionVitals} — an embedder must not\n * attach a second time to learn what the models are.\n */\n controlsSurface?: 'internal' | 'external'\n /**\n * Handed the session's setters once the panel is live, and `undefined` on\n * unmount. The counterpart to `controlsSurface: 'external'`: vitals carry the\n * readings out, this carries the commands back in. Stable identity — safe to\n * stash in a ref.\n */\n onControls?: (controls: SessionControls | undefined) => void\n /**\n * Click anywhere the panel isn't already doing something and the caret lands\n * in the composer — the terminal/chat convention, and what a docked panel\n * wants: the field is why the panel is focussed at all.\n *\n * Only dead space. A click that hits a control (a tool row expanding, a link,\n * a button) or that ends a text selection is that action, not a request for\n * the input. Off by default: a full-page surface has plenty of dead space that\n * means nothing in particular.\n */\n focusComposerOnClick?: boolean\n /**\n * What this session looked like when it was last looked at: how many\n * transcript items had been seen, and when. Present and behind the current\n * transcript → **catch-up**: a recap row at the boundary, everything above it\n * dimmed, and a bar offering to jump there or to dismiss.\n *\n * The embedder owns the watermark because only it knows what \"looked at\"\n * means in its own chrome — a hidden dock is not being read. The panel reports\n * the number to remember through `SessionVitals.itemCount`.\n */\n unseen?: { itemCount: number; since?: number }\n className?: string\n}\n\n/** What an embedder needs to *change* a session it doesn't own the attach for. */\nexport type SessionControls = {\n setModel: (model?: string) => void\n setPermissionMode: (mode: PermissionMode) => void\n interrupt: () => void\n}\n\n/** Everything a click can mean other than \"put the caret in the composer\".\n * Deliberately broad: mistaking a real target for dead space steals focus from\n * whatever the user just opened. */\nconst INTERACTIVE = [\n 'button',\n 'a',\n 'input',\n 'textarea',\n 'select',\n 'summary',\n 'img',\n '[contenteditable=\"true\"]',\n '[role=\"button\"]',\n '[role=\"menuitem\"]',\n '[role=\"checkbox\"]',\n '[role=\"radio\"]',\n '[role=\"tab\"]',\n].join(',')\n\n/** The panels the session surface can raise. One at a time, by identity: a bag\n * of booleans would let two open at once. */\nexport type SessionSurfacePanel = 'info' | 'context' | 'usage' | 'mcp' | 'files' | 'skills'\ntype Panel = SessionSurfacePanel\n\n/** What {@link SessionPanelProps.onVitals} reports: the live readings a host\n * chrome outside the panel would otherwise have to attach a second time for —\n * which the tool bridge forbids (it asks the first attached client). */\nexport type SessionVitals = {\n status: TranscriptState['status']\n /**\n * How the client is reaching the gateway. Load-bearing for a host rendering\n * these outside the panel: `status` is the last thing the session *said*, and\n * over a dropped socket that is a stale reading. The panel's own bar gives\n * the connection the status slot when it isn't `'live'` for exactly this\n * reason — an embedder showing `status` alone would present stale as current.\n */\n connection: ConnectionState\n engine: TranscriptState['engine']\n capabilities: TranscriptState['capabilities']\n model: string | undefined\n /** The models this session can switch to — the panel's own list, so an\n * external picker offers exactly what the internal one would. */\n models: ModelOption[]\n permissionMode: TranscriptState['permissionMode']\n /** The modes it can switch into, already filtered by the capability record\n * and the session's bypass grant (see `permissionModeChoices`). */\n permissionModes: PermissionModeChoice[]\n cwd: TranscriptState['cwd']\n contextUsage: TranscriptState['contextUsage']\n rateLimits: TranscriptState['rateLimits']\n /** How many transcript rows exist right now — the number an embedder stores\n * as its \"seen\" watermark while the panel is actually on screen, and compares\n * against later to know what is new. */\n itemCount: number\n}\n\n/**\n * The all-in-one embeddable session surface: status bar, streaming transcript,\n * permission prompts, composer. Attaches via useClaudeSession; remount (key) to switch\n * sessions.\n *\n * Every affordance is gated on the session's **capability record** rather than on\n * the engine name — an absent capability hides the control instead of offering\n * one that can only fail.\n */\nexport function SessionPanel({\n client,\n sessionId,\n header,\n panelSurface = 'internal',\n statusSurface = 'internal',\n onOpenPanel,\n onVitals,\n transcriptVariant = 'cards',\n controlsSurface = 'internal',\n onControls,\n focusComposerOnClick = false,\n unseen,\n className,\n}: SessionPanelProps) {\n const external = panelSurface === 'external'\n const statusExternal = statusSurface === 'external'\n const controlsExternal = controlsSurface === 'external'\n // Rejected commands (the CLI refusing a permission-mode switch, say) render INSIDE\n // the panel rather than through `toast`. The panel does not mount a `Toaster`, and\n // an embedder that doesn't either would drop the only signal that a command failed\n // — the select would just \"not stick\". An error channel a host can lose by omission\n // is not an error channel.\n const [protocolError, setProtocolError] = useState<string | undefined>(undefined)\n const [panel, setPanel] = useState<Panel | undefined>()\n const {\n state,\n connection,\n protocolMismatch,\n models,\n effectiveModel,\n handle,\n send,\n approve,\n deny,\n interrupt,\n setModel,\n setPermissionMode,\n reconnectNow,\n } = useClaudeSession(client, sessionId, { onProtocolError: setProtocolError })\n // Callers are told to remount on a session switch, but a changed prop must not leave\n // the previous session's failure on screen.\n useEffect(() => setProtocolError(undefined), [sessionId])\n\n // Catch-up is entered once, from the watermark the embedder handed over, and\n // left when dismissed or when the user sends anything (they are plainly\n // caught up at that point). Snapshotted into state rather than read from the\n // prop each render: the embedder keeps updating the watermark while the panel\n // is on screen, and a boundary that crept forward under the reader would\n // un-dim the very rows they came back to read.\n const [caughtUp, setCaughtUp] = useState(false)\n useEffect(() => {\n setCaughtUp(false)\n }, [sessionId])\n const [catchUpMark] = useState(unseen)\n const catchUp = caughtUp ? undefined : catchUpMark\n const newCount = catchUp ? Math.max(0, state.items.length - catchUp.itemCount) : 0\n\n // One router for every panel-opening affordance: internal surface opens the\n // dialog, external hands the intent to the embedder (or drops it, absent a\n // handler — inert beats half-working).\n const openPanel = useCallback(\n (target: SessionSurfacePanel) => {\n if (external) onOpenPanel?.(target)\n else setPanel(target)\n },\n [external, onOpenPanel],\n )\n // A tab that was in the background has been sitting out the reconnect backoff;\n // coming back to it is exactly when waiting the rest of it out is wrong.\n useEffect(() => {\n const onVisible = () => {\n if (document.visibilityState === 'visible') reconnectNow()\n }\n document.addEventListener('visibilitychange', onVisible)\n return () => document.removeEventListener('visibilitychange', onVisible)\n }, [reconnectNow])\n // Host server-bridged tool calls (provider-engine sessions) in this tab, on the\n // SAME handle the panel attached with — the bridge asks the first attached\n // client. Free for Claude sessions: the guest loads lazily on the first call,\n // which for them never comes.\n useToolCallHost(handle)\n const capabilities = state.capabilities\n\n // Vitals out to the embedder, keyed on the readings themselves so an inline\n // closure prop doesn't retrigger it every render.\n const onVitalsRef = useRef(onVitals)\n onVitalsRef.current = onVitals\n const vitalsModel = effectiveModel ?? state.model\n // The choices, not just the current values: an external picker has no second\n // attach to learn them from.\n const permissionModes = useMemo(\n () => permissionModeChoices(capabilities.permissionModes, state.session?.canBypassPermissions),\n [capabilities.permissionModes, state.session?.canBypassPermissions],\n )\n useEffect(() => {\n onVitalsRef.current?.({\n status: state.status,\n connection,\n engine: state.engine,\n capabilities: state.capabilities,\n model: vitalsModel,\n models,\n permissionMode: state.permissionMode,\n permissionModes,\n cwd: state.cwd,\n contextUsage: state.contextUsage,\n rateLimits: state.rateLimits,\n itemCount: state.items.length,\n })\n }, [\n state.status,\n connection,\n state.engine,\n state.capabilities,\n vitalsModel,\n models,\n state.permissionMode,\n permissionModes,\n state.cwd,\n state.contextUsage,\n state.rateLimits,\n state.items.length,\n ])\n\n // The commands back in. One stable object reading through refs, so an\n // embedder can stash it and a re-render never invalidates what it holds.\n const onControlsRef = useRef(onControls)\n onControlsRef.current = onControls\n const setters = useRef({ setModel, setPermissionMode, interrupt })\n setters.current = { setModel, setPermissionMode, interrupt }\n const controls = useRef<SessionControls>({\n setModel: (model) => setters.current.setModel(model),\n setPermissionMode: (mode) => setters.current.setPermissionMode(mode),\n interrupt: () => setters.current.interrupt(),\n })\n useEffect(() => {\n const handler = onControlsRef.current\n handler?.(controls.current)\n return () => handler?.(undefined)\n }, [sessionId])\n const busy = state.status === 'running' || state.status === 'awaiting_approval'\n const ended = state.status === 'failed' || state.status === 'closed'\n const attachments = useAttachments(client, sessionId, {\n capabilities,\n engine: state.engine,\n })\n // Rooted at the session's cwd, which arrives with the snapshot — so `@` is\n // inert for the moment before it does, and stays inert on a gateway that\n // serves no host files.\n const hostFiles = useHostFileSearch(client, state.cwd)\n const windows = useMemo(() => rateLimitWindows(state), [state])\n // Reads a picture the engine left on the host (codex's `image_gen` reports a\n // path, never bytes). Stable and memoized per path: transcript rows re-render\n // on every delta, and a fresh function would re-fetch each time.\n const hostImage = useHostImage(client, sessionId, state.producedFiles)\n const composerRef = useRef<ComposerHandle>(null)\n // The catch-up strip's way of scrolling the (virtualized, usually unmounted)\n // recap row into view — the transcript fills it in. See TranscriptProps.\n const jumpToRecap = useRef<(() => void) | null>(null)\n\n // \"/model\" is handled panel-side (see handleSend) — surface it in the autocomplete\n // even though the CLI's command list doesn't include it.\n const commands = useMemo(() => {\n if (!state.commands) return undefined\n if (state.commands.some((c) => c.name === 'model')) return state.commands\n return [\n { name: 'model', description: 'Switch the model for this session', argumentHint: '<model>' },\n ...state.commands,\n ]\n }, [state.commands])\n\n // Two things are answered here rather than sent, because sending them would\n // spend a turn on a model reading the words back.\n const handleSend = (text: string, attachmentIds: string[]) => {\n if (attachmentIds.length === 0) {\n // \"/model <id>\" switches the model directly instead of going to the CLI.\n const modelCommand = /^\\/model\\s+(\\S+)$/.exec(text)\n if (modelCommand) {\n setModel(modelCommand[1])\n return\n }\n // The CLI's own `/mcp` is an interactive picker, not a prompt. Only where\n // the capability exists: elsewhere it is ordinary message text, like any\n // other slash command on an engine without them.\n if (capabilities.mcpStatus && text.trim() === '/mcp') {\n openPanel('mcp')\n return\n }\n }\n // Typing into a session is the clearest possible statement that you have\n // read it — nothing left to catch up on.\n setCaughtUp(true)\n send(text, attachmentIds)\n }\n\n // Everything the panel can open, in one place — and each one is also\n // reachable by clicking the thing on the bar that summarises it. Entries the\n // capability record forswears are absent, not present-and-empty.\n //\n // Built once and placed once: either the embedder's header takes it (see the\n // `header` render-prop) or the status bar does. Never both — two `⋯` menus on\n // one screen is worse than either position.\n const actionsMenu = (\n <Menu>\n <MenuTrigger\n render={\n <Button variant='ghost' size='icon-sm' aria-label='Session actions'>\n <MoreHorizontal className='size-4' />\n </Button>\n }\n />\n <MenuContent>\n {capabilities.contextUsage ? (\n <MenuItem onClick={() => openPanel('context')}>\n <ChartPie className='size-3.5 text-fg-3' /> Context\n </MenuItem>\n ) : null}\n {capabilities.rateLimits ? (\n <MenuItem onClick={() => openPanel('usage')}>\n <Gauge className='size-3.5 text-fg-3' /> Usage\n </MenuItem>\n ) : null}\n <MenuItem onClick={() => openPanel('info')}>\n <Info className='size-3.5 text-fg-3' /> Session info\n </MenuItem>\n {capabilities.mcpStatus ? (\n <MenuItem onClick={() => openPanel('mcp')}>\n <Plug className='size-3.5 text-fg-3' /> MCP servers\n </MenuItem>\n ) : null}\n {/* On the capability alone, like MCP's entry. Codex answers\n `skills/list` only over a live child, so before the first turn there\n is no list yet — but hiding the entry until then made the dialog's\n own explanation of that unreachable, which read as the feature being\n missing. The empty state says it instead. */}\n {capabilities.skillsList ? (\n <MenuItem onClick={() => openPanel('skills')}>\n <Sparkles className='size-3.5 text-fg-3' /> Skills\n </MenuItem>\n ) : null}\n {hostFiles.available ? (\n <MenuItem onClick={() => openPanel('files')}>\n <FolderTree className='size-3.5 text-fg-3' /> Project files\n </MenuItem>\n ) : null}\n </MenuContent>\n </Menu>\n )\n\n // A function header claims the menu; anything else leaves it on the status bar.\n // The external surface has no menu to claim — those entries live in the\n // embedder's own chrome, reached through onOpenPanel.\n const menu = external ? null : actionsMenu\n const headerTakesActions = typeof header === 'function'\n\n // Dead-space clicks land in the composer. Anything the user actually aimed at\n // — a control, a link, the end of a drag-selection — keeps its own meaning;\n // this only claims what was left over.\n const handleClick = (event: ReactMouseEvent<HTMLDivElement>) => {\n if (!focusComposerOnClick) return\n const target = event.target as HTMLElement | null\n if (target?.closest(INTERACTIVE)) return\n if (window.getSelection()?.isCollapsed === false) return\n composerRef.current?.focus()\n }\n\n return (\n // The variant is a panel-wide fact, not a transcript-only one: the approval\n // and question prompts live outside the scroller but are line items in the\n // same run, and they read `useLines()` like every other row.\n <TranscriptVariantProvider value={transcriptVariant}>\n <div\n data-slot='session-panel'\n onClick={handleClick}\n className={cn('flex h-full min-h-0 flex-col overflow-hidden bg-bg', className)}>\n {headerTakesActions ? header({ actions: menu }) : header}\n {statusExternal ? null : (\n <StatusBar\n state={state}\n connection={connection}\n onOpenStatus={external && !onOpenPanel ? undefined : () => openPanel('info')}\n onOpenContext={external && !onOpenPanel ? undefined : () => openPanel('context')}\n onOpenUsage={external && !onOpenPanel ? undefined : () => openPanel('usage')}\n actions={headerTakesActions ? undefined : menu}\n />\n )}\n {protocolMismatch !== undefined ? (\n <Notice level='warning'>\n Server speaks protocol v{protocolMismatch}, this build renders v{PROTOCOL_VERSION}. Some\n events may not render.\n </Notice>\n ) : null}\n {protocolError ? (\n <Notice level='error' onDismiss={() => setProtocolError(undefined)}>\n {protocolError}\n </Notice>\n ) : null}\n <Transcript\n state={state}\n fileUrl={sessionId ? (path) => client.sessionFileUrl(sessionId, path) : undefined}\n attachmentUrl={sessionId ? (id) => client.attachmentUrl(sessionId, id) : undefined}\n canBrowseFiles={hostFiles.available}\n hostImage={hostImage}\n variant={transcriptVariant}\n catchUp={\n catchUp && newCount > 0\n ? { from: catchUp.itemCount, since: catchUp.since }\n : undefined\n }\n jumpToRecapRef={jumpToRecap}\n />\n {/* The way back into what you missed. Above the composer because that is\n where the eye already is on returning, and because the transcript\n itself opens pinned to the newest row. */}\n {catchUp && newCount > 0 ? (\n <div className='px-3 pb-1'>\n <div\n data-slot='catch-up'\n className='mx-auto flex w-full max-w-[var(--wd-content-max-w,48rem)] items-center gap-2 text-label text-fg-3'>\n <span aria-hidden className='select-none text-accent'>\n ※\n </span>\n <span className='min-w-0 flex-1 truncate'>\n {newCount} new {newCount === 1 ? 'row' : 'rows'}\n {catchUp.since !== undefined ? ` since you were last here` : ''}\n </span>\n <button\n type='button'\n onClick={() => jumpToRecap.current?.()}\n className='shrink-0 underline-offset-2 hover:text-fg-1 hover:underline'>\n jump\n </button>\n <button\n type='button'\n onClick={() => setCaughtUp(true)}\n className='shrink-0 underline-offset-2 hover:text-fg-1 hover:underline'>\n dismiss\n </button>\n </div>\n </div>\n ) : null}\n {/* An engine with no approval channel never raises these, but a stale\n pending request from a replayed log would still render — the record is\n the authority on whether an approval UI means anything here. */}\n {capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? (\n <div className='px-3 pb-2'>\n <div className='mx-auto flex w-full max-w-[var(--wd-content-max-w,48rem)] flex-col gap-2'>\n {state.pendingApprovals.map((request) =>\n request.toolName === 'AskUserQuestion' &&\n parseUserQuestions(request.input).length > 0 ? (\n <QuestionPrompt\n key={request.id}\n request={request}\n onAnswer={approve}\n onDismiss={(id) => deny(id, 'Question dismissed by user')}\n />\n ) : (\n <PermissionPrompt\n key={request.id}\n request={request}\n onApprove={approve}\n onDeny={deny}\n />\n ),\n )}\n </div>\n </div>\n ) : null}\n <Composer\n ref={composerRef}\n onSend={handleSend}\n onInterrupt={interrupt}\n busy={busy}\n disabled={ended || !sessionId}\n commands={capabilities.slashCommands ? commands : undefined}\n skills={capabilities.skillsList ? state.skills : undefined}\n attachments={attachments}\n onSearchFiles={\n hostFiles.available\n ? (query, options) => hostFiles.search(query, { ...options, limit: 8 })\n : undefined\n }\n layout={controlsExternal ? 'inline' : 'stacked'}\n toolbar={\n controlsExternal ? undefined : (\n <>\n {/* Codex reports no `capabilities` event, so its models arrive from\n the profile catalog instead — without that fallback its picker\n would be permanently empty and the session unswitchable. */}\n {models.length ? (\n <ModelSelect\n models={models}\n model={effectiveModel}\n onModelChange={setModel}\n disabled={ended}\n />\n ) : null}\n {state.permissionMode ? (\n <PermissionModeSelect\n mode={state.permissionMode}\n onModeChange={setPermissionMode}\n // Only what this engine implements — the rest would come back as\n // a protocol_error.\n modes={capabilities.permissionModes}\n canBypass={state.session?.canBypassPermissions}\n disabled={ended}\n />\n ) : null}\n </>\n )\n }\n />\n\n {/* The internal dialog surface. The external one renders none of these —\n the embedder hosts equivalent surfaces and is handed the intents. */}\n {!external ? (\n <>\n <SessionInfoDialog\n state={state}\n client={client}\n sessionId={sessionId}\n open={panel === 'info'}\n onOpenChange={(next) => setPanel(next ? 'info' : undefined)}\n />\n <ContextDialog\n usage={state.contextUsage}\n open={panel === 'context'}\n onOpenChange={(next) => setPanel(next ? 'context' : undefined)}\n />\n <UsageDialog\n rateLimits={windows}\n subscriptionType={state.subscriptionType}\n engine={state.engine ?? 'claude'}\n totalCostUsd={state.totalCostUsd}\n updatedAt={state.rateLimitsUpdatedAt}\n open={panel === 'usage'}\n onOpenChange={(next) => setPanel(next ? 'usage' : undefined)}\n />\n <McpDialog\n client={client}\n sessionId={sessionId}\n canManageServers={capabilities.mcpServerActions}\n open={panel === 'mcp'}\n onOpenChange={(next) => setPanel(next ? 'mcp' : undefined)}\n />\n <SkillsDialog\n skills={state.skills}\n open={panel === 'skills'}\n onOpenChange={(next) => setPanel(next ? 'skills' : undefined)}\n // Drafts into the composer; the operator sends it. There is no engine\n // call that runs a skill, so there is nothing else this button could do.\n onUse={(skill) => composerRef.current?.insertText(skillPrompt(skill))}\n />\n <HostFilesDialog\n client={client}\n cwd={state.cwd}\n open={panel === 'files'}\n onOpenChange={(next) => setPanel(next ? 'files' : undefined)}\n />\n </>\n ) : null}\n </div>\n </TranscriptVariantProvider>\n )\n}\n\n/**\n * Turns a host path a tool card is holding into something an `<img>` can show.\n *\n * Two sources, tried in that order and for a reason:\n *\n * 1. **The session's produced files.** If this session's own runner announced\n * writing that path (`file_produced`), the gateway will serve it from\n * `/sessions/:id/produced/:fileId` — no host-file roots to declare, no byte\n * cap to raise. This is the path codex's generated images take, and it is\n * why they now render out of the box.\n * 2. **`/fs/read`.** For a path nothing produced — a picture the model looked\n * at, an image already in the tree — where the operator's declared roots are\n * the right gate and the answer is legitimately \"no\" outside them.\n *\n * The cache is what makes this usable from a transcript row: rows re-render on\n * every streamed delta, and an uncached resolver would re-fetch the picture each\n * time. A refusal is cached too, so it costs one request rather than one per\n * render.\n *\n * Keyed by `fileId`-or-path so that a path which becomes produced *after* a\n * failed `/fs/read` is retried under a different key rather than staying cached\n * as a miss.\n */\nfunction useHostImage(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n producedFiles: Record<string, ProducedFileRef> | undefined,\n): (path: string) => Promise<string | undefined> {\n const cache = useRef(new Map<string, Promise<string | undefined>>())\n // Object URLs pin their blob until revoked, so a long session that generated\n // a dozen images would hold a dozen megabytes past unmount.\n const objectUrls = useRef<string[]>([])\n useEffect(\n () => () => {\n for (const url of objectUrls.current) URL.revokeObjectURL(url)\n objectUrls.current = []\n },\n [],\n )\n return useCallback(\n (path: string) => {\n const produced = producedFiles?.[path]\n const key = produced ? `produced:${produced.fileId}` : `fs:${path}`\n const hit = cache.current.get(key)\n if (hit) return hit\n const pending =\n produced && sessionId\n ? // Fetched rather than pointed at: the panel may be talking to a\n // header-authenticated gateway, where a bare URL in an `<img src>`\n // carries no credential.\n client\n .readProducedFile(sessionId, produced.fileId)\n .then((blob) => {\n if (blob.size === 0) return undefined\n const url = URL.createObjectURL(blob)\n objectUrls.current.push(url)\n return url\n })\n .catch(() => undefined)\n : client\n .readHostFile(path)\n .then((file) => {\n if (file.encoding !== 'base64') return undefined\n // The route reports bytes and an encoding but not a media type;\n // the extension is what a browser needs to decode it.\n const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase()\n const mediaType = IMAGE_MEDIA_TYPES[extension]\n return mediaType ? `data:${mediaType};base64,${file.content}` : undefined\n })\n .catch(() => undefined)\n cache.current.set(key, pending)\n return pending\n },\n [client, sessionId, producedFiles],\n )\n}\n\n/** Extensions worth rendering inline, and what to call them. Anything else is\n * left to the card's path text — guessing a media type is how an HTML file ends\n * up in an `<img>`. */\nconst IMAGE_MEDIA_TYPES: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n}\n\n/** A dismissible advisory strip above the transcript. */\nfunction Notice({\n level,\n onDismiss,\n children,\n}: {\n level: 'warning' | 'error'\n onDismiss?: () => void\n children: ReactNode\n}) {\n return (\n <div className='px-3 pt-2'>\n <div\n role='alert'\n className={cn(\n 'mx-auto flex w-full max-w-[var(--wd-content-max-w,48rem)] items-start gap-2 rounded-md border px-3 py-2 text-body-sm',\n level === 'error'\n ? 'border-danger/40 bg-danger-bg text-danger'\n : 'border-warning/40 bg-warning-bg text-warning',\n )}>\n <TriangleAlert className='mt-0.5 size-3.5 shrink-0' />\n <span className='min-w-0 flex-1 break-words'>{children}</span>\n {onDismiss ? (\n <button\n type='button'\n onClick={onDismiss}\n aria-label='Dismiss'\n className='shrink-0 opacity-70 transition-opacity hover:opacity-100'>\n <X className='size-3.5' />\n </button>\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|