@workerdeck/ui 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"SessionPanel-DPx8Iz8a.mjs","names":["Select","SelectPrimitive","Menu","MenuPrimitive","Dialog","DialogPrimitive","TooltipPrimitive","rangeStart","File","FileText","FileCard","noop","RESULT_PREVIEW_CHARS","TurnResultRow","NoticeRow","cssColor","usageTint","Dialog","Dialog","Dialog","Dialog","Select","Select","answerFor","Box","Box","Dialog","TurnResultRow","NoticeRow","useRunStart","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/transcript-variant.tsx","../src/lib/tool-icon.ts","../src/components/agent/pulse.tsx","../src/components/terminal/affordances.tsx","../src/components/terminal/row.tsx","../src/components/terminal/diff.tsx","../src/components/terminal/markdown.tsx","../src/components/terminal/press.tsx","../src/components/terminal/image-box.ts","../src/components/terminal/result-preview.ts","../src/components/agent/tool-result-fetch.tsx","../src/components/agent/tool-result-image.tsx","../src/components/terminal/tool-run.ts","../src/components/terminal/blocks.ts","../src/components/terminal/items.tsx","../src/components/terminal/surface.tsx","../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/components/agent/PermissionPrompt.tsx","../src/components/agent/SubagentStrip.tsx","../src/components/agent/QuestionPrompt.tsx","../src/components/terminal/prompt.tsx","../src/components/terminal/PermissionPrompt.tsx","../src/components/terminal/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/Response.tsx","../src/components/agent/Reasoning.tsx","../src/components/agent/SessionEmptyState.tsx","../src/components/agent/ToolCallCard.tsx","../src/components/terminal/height.ts","../src/components/terminal/scrubber.tsx","../src/components/agent/transcript-rows.ts","../src/components/agent/use-height-epoch.ts","../src/components/agent/use-transcript-jumps.ts","../src/components/terminal/TerminalTranscript.tsx","../src/components/agent/Transcript.tsx","../src/components/agent/UsageMeters.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 {/* `self-center` so the dot stays put if a caller baseline-aligns the\n badge — a no-op under the default `items-center`, and what lets the\n badge's *text* be the baseline it contributes to a row (a status bar\n aligning readings on one line needs that, and the dot's own box would\n otherwise answer for it). */}\n {dot ? <span aria-hidden className='size-1.5 shrink-0 self-center 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-80 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-80 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-90'>\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 /**\n * Size to snap back to on a double-click — the convention every pane divider\n * that can be dragged is expected to honour. Omit and a double-click does\n * nothing, which is the honest behaviour when there is no default to mean.\n */\n defaultValue?: 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 defaultValue,\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 // The second click of a double-click has already started a drag, so the reset\n // has to land after `endDrag` — which it does: dblclick fires after pointerup.\n const onDoubleClick = () => {\n if (defaultValue !== undefined) onValueChange(clamp(defaultValue))\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 onDoubleClick={onDoubleClick}\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\n/**\n * Where the caret is on screen, in viewport coordinates.\n *\n * A collapsed range usually measures fine, but not when it sits *between* child\n * nodes of an element — which is exactly where a freshly inserted newline puts\n * it, because this editor's content is a flat run of text nodes and `<br>`s\n * directly under the editor element. There `getBoundingClientRect()` is all\n * zeros, and the obvious fallback (measure `startContainer`'s element) resolves\n * to the editor itself, whose rect is the viewport box — so it reports the\n * caret as trivially visible and scrolls nothing.\n *\n * So measure the neighbour instead: the child the caret sits before, or failing\n * that the one it sits after. Selecting a node gives a real rect even for a\n * `<br>`, which is what the line the caret just moved to consists of.\n */\nfunction caretRect(range: Range): DOMRect | null {\n const direct = range.getBoundingClientRect()\n if (direct.height > 0) return direct\n\n const node = range.startContainer\n if (node.nodeType !== Node.ELEMENT_NODE) return null\n const children = (node as Element).childNodes\n const neighbours = [children[range.startOffset], children[range.startOffset - 1]]\n for (const neighbour of neighbours) {\n if (!neighbour) continue\n const probe = document.createRange()\n probe.selectNode(neighbour)\n const rect = probe.getBoundingClientRect()\n if (rect.height > 0) return rect\n }\n return null\n}\n\n/**\n * Keep the caret inside the editor's visible box after a *programmatic* move.\n *\n * The editor is its own scroll container once it hits `maxHeight`\n * (`prompt-area.tsx` sets `overflowY: auto` there). Typing scrolls the caret\n * into view for free, because that is the browser's own behaviour for a native\n * edit — but every path that calls `preventDefault()` and rebuilds the DOM\n * itself (Shift+Enter, list continuation, undo/redo, the bold/italic wrap,\n * paste) places the caret with a Range instead, and setting a selection does\n * not scroll anything. The symptom is a prompt that has grown past its cap and\n * stops following what you are writing: type and it scrolls, press Shift+Enter\n * and the new line appears below the fold.\n *\n * A no-op when the caret is already visible, so it is safe on every call — it\n * corrects an off-screen caret rather than scrolling to one.\n */\nexport function scrollCaretIntoView(editor: HTMLElement): void {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (!editor.contains(range.startContainer)) return\n\n const rect = caretRect(range)\n if (!rect) return\n\n const box = editor.getBoundingClientRect()\n if (rect.bottom > box.bottom) editor.scrollTop += rect.bottom - box.bottom\n else if (rect.top < box.top) editor.scrollTop -= box.top - rect.top\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 scrollCaretIntoView(editor)\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 scrollCaretIntoView(editor)\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. `&nbsp;` -> 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 `![${node.getAttribute('alt') ?? ''}](${src})`\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): submit when `submitOnEnter` is\n // set, else insert a newline — continuing a list if we are in one.\n //\n // Under `submitOnEnter`, Enter submits *unconditionally*. It used to try\n // a list continuation first, which meant that once you had typed a\n // bullet, the send key silently stopped sending: every Enter added\n // another empty bullet, and the only way out was to clear the list. Enter\n // is the send key in a chat composer, and a key that does something else\n // depending on what the line above happens to start with is not one.\n // Continuation lives on Shift+Enter (branch 2.8), which is the newline\n // key here and therefore where \"newline, and keep the list going\"\n // belongs. Without `submitOnEnter`, Enter *is* the newline key, so it\n // keeps the continuation.\n if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault()\n if (submitOnEnter) {\n onSubmit?.(readSegmentsFromDOM())\n return\n }\n const editor = editorRef.current\n if (editor && !tryListContinuation(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 { createContext, useContext, type ReactNode } from 'react'\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 * - `terminal` — the CLI's own form: every row on a character cell, no boxes\n * anywhere, diffs as full-width bands. Right where the transcript sits beside\n * a terminal (a VS Code dock) and where vertical space is scarce.\n *\n * `terminal` is **not** a second set of branches in the components under\n * `components/agent/`: it is its own renderer (`components/terminal/`) that the\n * shell mounts *instead* of them. So a row component here never asks which\n * variant it is in — if it is drawing at all, it is drawing cards.\n *\n * A context rather than a prop chain because the pieces that DO need it sit\n * outside the transcript (the composer, the pending prompts) and because\n * `Message`/`ToolCallCard` are exported on their own, so an embedder composing\n * them by hand gets the right treatment for free.\n */\nexport type TranscriptVariant = 'cards' | 'terminal'\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/**\n * How much room the transcript gives each row.\n *\n * - `comfortable` — a blank line between messages, which is what the Claude Code\n * CLI does. The default: a transcript is prose before it is a table.\n * - `compact` — rows tight against each other, for a dock where every line of\n * vertical space is contested.\n *\n * Separate from the variant, and deliberately: they answer different questions.\n * The variant decides *how a row is drawn* and follows from the surface; density\n * decides *how much air is around it* and is a preference the reader holds.\n *\n * Reaches `cards` only. The terminal theme's spacing is a blank *line*, decided\n * per pair of blocks by `needsBlank` — a terminal has one line height, which is\n * the whole premise — so there is nothing there for this to turn.\n */\nexport type TranscriptDensity = 'comfortable' | 'compact'\n\nconst DensityContext = createContext<TranscriptDensity>('comfortable')\n\nexport function TranscriptDensityProvider({\n value,\n children,\n}: {\n value: TranscriptDensity\n children: ReactNode\n}) {\n return <DensityContext.Provider value={value}>{children}</DensityContext.Provider>\n}\n\nexport function useTranscriptDensity(): TranscriptDensity {\n return useContext(DensityContext)\n}\n\n/**\n * The typeface the panel runs in.\n *\n * `sans` is the host's UI font; `mono` repoints the sans token at the mono stack\n * for the panel's subtree (see the `[data-agent-font='mono']` rule in\n * `theme.css`), so the transcript reads as part of a terminal rather than as a\n * web app beside one.\n *\n * No context and no hook, unlike variant and density: nothing branches on it in\n * JS. It is one attribute on the panel root and the cascade does the rest, which\n * is also what keeps it from leaking past the panel.\n */\nexport type TranscriptFont = 'sans' | 'mono'\n\n/**\n * The gap between two rows, per variant and density — the whole of the density\n * feature, since it is the only vertical spacing between rows that exists.\n *\n * `className` goes on the **measured** wrapper (see `Transcript`), so the gap is\n * part of each row's measured height and no pixel constant is load-bearing.\n * `px` is fed to `estimateSize` alone, where being approximate is the contract:\n * it sets the scrollbar's length before rows mount and is replaced by a real\n * measurement the moment one does.\n */\nexport const ROW_GAP: Record<\n TranscriptVariant,\n Record<TranscriptDensity, { className?: string; px: number }>\n> = {\n // The terminal theme has no gap scale: the space between blocks is a **blank\n // line**, decided per pair by `needsBlank` (a tool call and its output get\n // none), so it is a class the shell applies conditionally rather than a\n // constant it applies to every row. Density does not reach it — a terminal\n // has one line height, which is the whole premise.\n terminal: {\n comfortable: { className: 'term-row-gap', px: 18 },\n compact: { className: 'term-row-gap', px: 18 },\n },\n cards: {\n comfortable: { className: 'pt-4', px: 16 },\n compact: { className: 'pt-2', px: 8 },\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 * Is this tool a shell command?\n *\n * Its own question because a run of them is *one* thing the reader skims past:\n * the terminal theme collapses consecutive shell calls into a single \"Ran N\n * shell commands\" line, the way the CLI does. Names from both first-party\n * engines. `BashOutput`/`KillShell` are excluded on purpose — they manage a\n * background shell rather than run something, and folding them into the count\n * would inflate it.\n */\nexport function isShellTool(toolName: string): boolean {\n return toolName === 'Bash' || toolName === 'CodexCommand'\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 { useEffect, useState } from 'react'\n\n/**\n * The brand mark's pulse, as characters — the working marker every surface in the\n * transcript animates.\n *\n * These are the mark's own four states (`docs/assets/BRAND.md`, \"The loading\n * state\"): a dot, an outline, a semi and a full diamond, built in the SVG from\n * two shapes rather than four drawings. 150ms each, so one cycle is the 0.6s\n * clock the marker pulses on in `icon-loading.svg` — the same rhythm, in the\n * medium a transcript row actually has.\n *\n * BRAND.md's caveat applies and is satisfied here: `U+25C6/7/8` are East-Asian\n * *ambiguous width*, so they can render double-width in a terminal under an\n * East-Asian locale and shift every line with them. They are safe wherever the\n * glyph is centred in a fixed-width box, which is what the terminal theme's\n * gutter cell (`.term-gutter`, one `--term-cell` wide) is. Anything writing to a\n * real terminal must use the ASCII set instead.\n */\nexport const PULSE_FRAMES = ['⋄', '◇', '◈', '◆'] as const\nexport const PULSE_MS = 150\n\n/**\n * The resting state. Stopping the animation lands on the complete mark rather\n * than on a half-drawn frame — the same property that makes the SVG's\n * `prefers-reduced-motion` free (see BRAND.md: `translateY(0)` *is* the mark).\n */\nexport const PULSE_REST = PULSE_FRAMES[PULSE_FRAMES.length - 1]\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. */\nexport function 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 current pulse frame, ticking while `animated`. Callers mount this only\n * while something is actually in flight, so nothing here runs on an idle\n * session; with reduced motion, or when not animating, it holds at rest.\n */\nexport function usePulse(animated: boolean): string {\n const reduced = usePrefersReducedMotion()\n const running = animated && !reduced\n const [frame, setFrame] = useState(0)\n useEffect(() => {\n if (!running) return\n const timer = setInterval(() => setFrame((f) => f + 1), PULSE_MS)\n return () => clearInterval(timer)\n }, [running])\n if (!running) return PULSE_REST\n return PULSE_FRAMES[frame % PULSE_FRAMES.length]!\n}\n","import { createContext, useContext, useState, type ReactNode } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * The things a terminal cannot do, and this can.\n *\n * The theme's whole discipline is that it renders like a CLI — but it is not a\n * CLI, and refusing every affordance a pointer makes possible would be cosplay\n * rather than design. A terminal cannot highlight the row under your cursor and\n * cannot put a copy button on a block of output; a web view can do both for free\n * and they are genuinely useful.\n *\n * The rule that keeps this honest is that **each one costs no layout**. A hover\n * fill is a background. An action button is an overlay at the row's right edge,\n * one line tall, absolutely positioned so it displaces nothing. Turn them all\n * off and every glyph is on exactly the same cell it was on — which is what\n * makes `off` a real option rather than a degraded mode, and why it is the mode\n * a host projecting to a real terminal would choose.\n *\n * Off by nothing in particular: both default **on**, because the surface these\n * render on is a browser and pretending otherwise helps nobody. A host that\n * wants the pure article passes `affordances={false}`.\n */\nexport type TerminalAffordances = {\n /** Fill the row under the pointer, on anything pressable. */\n hover?: boolean\n /** Reveal a row's actions (copy, and whatever a row adds) on hover or focus. */\n actions?: boolean\n}\n\nconst DEFAULTS: Required<TerminalAffordances> = { hover: true, actions: true }\n\nconst AffordanceContext = createContext<Required<TerminalAffordances>>(DEFAULTS)\n\nexport function useAffordances(): Required<TerminalAffordances> {\n return useContext(AffordanceContext)\n}\n\n/** Resolve the surface's prop — `false` means none, `true`/absent means all. */\nexport function resolveAffordances(\n value: TerminalAffordances | boolean | undefined,\n): Required<TerminalAffordances> {\n if (value === false) return { hover: false, actions: false }\n if (value === true || value === undefined) return DEFAULTS\n return { ...DEFAULTS, ...value }\n}\n\nexport function AffordanceProvider({\n value,\n children,\n}: {\n value: Required<TerminalAffordances>\n children: ReactNode\n}) {\n return <AffordanceContext.Provider value={value}>{children}</AffordanceContext.Provider>\n}\n\n/**\n * A block that reveals its actions on hover.\n *\n * Wraps rather than decorates because the actions belong to the *block* — a\n * message is many rows and its copy button belongs at the top right of all of\n * them, not on whichever row the pointer happens to be over.\n */\nexport function WithActions({\n actions,\n children,\n className,\n}: {\n actions: ReactNode\n children: ReactNode\n className?: string\n}) {\n const { actions: enabled } = useAffordances()\n if (!enabled) return <>{children}</>\n return (\n <div className={cn('term-hoverable', className)}>\n {children}\n <div className='term-actions'>{actions}</div>\n </div>\n )\n}\n\n/**\n * Copy, as a character.\n *\n * `⧉` and `✓` rather than an SVG for the same reason the rest of the theme uses\n * glyphs: an icon drawn at some other size sits between the cells everything\n * else is on, and reads as a button borrowed from another application. The tick\n * replaces the glyph in place, so the confirmation costs no width either.\n */\n/**\n * Open a sub-agent's own surface, as a character.\n *\n * A row **action** and not the row's press, because the press already means\n * expand/collapse and that is the cheaper, more common intent — a reader\n * glancing at what an agent did wants the four rows inline, not a new screen.\n * Taking over the panel is the deliberate move, so it gets the deliberate\n * target. Same zero-layout contract as `CopyAction`: it lives in the hover\n * actions overlay, so a collapsed Task row is exactly as tall with it as\n * without, which is what keeps the height book honest.\n */\nexport function OpenSubagentAction({\n onOpen,\n label = 'Open sub-agent',\n}: {\n onOpen: () => void\n label?: string\n}) {\n return (\n <button\n type='button'\n className='term-action'\n title={label}\n aria-label={label}\n onClick={(event) => {\n // The row underneath expands; opening is not expanding.\n event.stopPropagation()\n onOpen()\n }}>\n ⤢\n </button>\n )\n}\n\nexport function CopyAction({ text, label = 'Copy' }: { text: string; label?: string }) {\n const [copied, setCopied] = useState(false)\n return (\n <button\n type='button'\n className='term-action'\n title={label}\n aria-label={label}\n onClick={(event) => {\n // The row underneath is usually pressable (a tool call expands); copying\n // is not expanding.\n event.stopPropagation()\n void navigator.clipboard\n ?.writeText(text)\n .then(() => {\n setCopied(true)\n setTimeout(() => setCopied(false), 1200)\n })\n // No toast, no error row: a refused clipboard (an insecure origin, a\n // denied permission) is the host's business, and a failed copy that\n // says nothing is better than a transcript that grows an error.\n .catch(() => {})\n }}>\n {copied ? '✓' : '⧉'}\n </button>\n )\n}\n","import type { CSSProperties, HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * The primitives every terminal row is built from.\n *\n * There are three, and that is the whole vocabulary: a {@link Row} (a gutter\n * cell and a body cell), a {@link Blank} (one empty line), and a {@link Band} (a\n * run of rows carrying a full-bleed background). Anything the theme draws — a\n * message, a tool call, a diff hunk, an approval prompt — is some arrangement of\n * those, which is what keeps the grid a property of the renderer rather than a\n * thing each component re-derives.\n *\n * Geometry is in `styles/terminal.css`. These components choose a class, a\n * marker and a tone; they never carry a measurement.\n */\n\n/** The palette, as a name. See the `[data-tone]` rules in `terminal.css`. */\nexport type Tone =\n | 'fg'\n | 'bright'\n | 'dim'\n | 'faint'\n | 'mark'\n | 'blue'\n | 'green'\n | 'red'\n | 'yellow'\n | 'magenta'\n\nexport interface RowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n /**\n * What sits in the gutter — `●`, `⎿`, `>`, a list bullet, or nothing. Kept to\n * the width of the gutter (`--term-cell`, two columns by default): a wider\n * marker would push its own body off the column every other row starts on.\n * Omitted, the gutter is still drawn as empty space, so an unmarked row's\n * text lines up with a marked one's.\n */\n glyph?: ReactNode\n /** The marker's colour. Defaults to dim — the marker is structure, not content. */\n glyphTone?: Tone\n /** The body's colour. */\n tone?: Tone\n bold?: boolean\n /** Indent levels, one character cell each. A child row's marker then sits\n * exactly under its parent row's first letter. */\n indent?: 0 | 1 | 2 | 3\n /**\n * Gutter width in columns, when the marker needs other than two — an ordered\n * list's `10.` is four, a prompt's `❯ 1.` is five, and a framed payload wants\n * `0`. Changes only this row's split, so the body still starts on a whole\n * column.\n */\n columns?: number\n children?: ReactNode\n}\n\nexport function Row({\n glyph,\n glyphTone,\n tone,\n bold,\n indent,\n columns,\n className,\n children,\n style,\n ...props\n}: RowProps) {\n return (\n <div\n className={cn('term-row', className)}\n data-indent={indent ? String(indent) : undefined}\n data-tone={tone}\n data-weight={bold ? 'bold' : undefined}\n // `!== undefined`, not truthiness: `columns={0}` is a real request for a\n // gutterless row (a framed payload) and must not fall back to the default.\n style={\n columns === undefined\n ? style\n : ({ ...style, '--term-cell': `${columns}ch` } as CSSProperties)\n }\n {...props}>\n <span className='term-gutter' data-tone={glyphTone} aria-hidden>\n {glyph ?? ' '}\n </span>\n {/* A div, not a span: a body holds block content (a markdown message, a\n band of output) as often as it holds a line of text. */}\n <div className='term-body'>{children}</div>\n </div>\n )\n}\n\n/**\n * One empty line — the theme's only vertical spacing.\n *\n * A terminal separates blocks with a blank line, not with padding, and saying it\n * that way has a practical payoff: spacing is part of the row list, so it can be\n * decided by whoever knows whether two blocks belong together (a tool call and\n * its output do not get one; two assistant turns do), instead of by a margin\n * rule that cannot tell them apart.\n */\nexport function Blank() {\n return <div className='term-blank' aria-hidden />\n}\n\n/**\n * A run of rows under a background: a code block, a command's output, a diff\n * hunk. Full-bleed — the wash reaches the scroller's edges, because in a\n * terminal the line is the full width of the screen. See `--term-bleed` on\n * {@link TerminalSurface}.\n */\nexport function Band({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('term-band', className)} {...props} />\n}\n\n/** Inline colour/weight inside a row's body. */\nexport function Ink({\n tone,\n bold,\n className,\n ...props\n}: HTMLAttributes<HTMLSpanElement> & { tone?: Tone; bold?: boolean }) {\n return (\n <span\n data-tone={tone}\n data-weight={bold ? 'bold' : undefined}\n className={className}\n {...props}\n />\n )\n}\n","import type { FilePatch, PatchHunk } from '@workerdeck/protocol'\nimport { Row } from './row.tsx'\n\n/**\n * A file edit, drawn the way the CLI draws it: a right-aligned line-number\n * column, a one-column `+`/`-` marker, and the line — with added and removed\n * rows carrying a full-bleed wash.\n *\n * The line numbers are the engine's own, off the wire (protocol's\n * {@link FilePatch}). Nothing here computes one: this component has never seen\n * the file, and a number it invented would be worse than no number at all —\n * it would look authoritative and send the reader to the wrong line.\n *\n * The whole row — number, marker and text — is one {@link Row}, with the number\n * and marker as its gutter. That is what keeps a wrapped line hanging under the\n * text rather than under the numbers, and it is why the gutter width is computed\n * rather than fixed: a four-digit file and a two-digit one both put their first\n * character of code on a whole column.\n */\n\n/** Which side of the edit a body line belongs to, from its unified-diff prefix. */\ntype LineKind = 'context' | 'add' | 'remove'\n\nconst kindOf = (line: string): LineKind =>\n line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'context'\n\n/**\n * The rows of one hunk, each with the line number it has *in the file*.\n *\n * Two counters, because a diff is two files interleaved: an added line has a\n * number only in the new file, a removed line only in the old one, and context\n * advances both. Showing the new number for adds and the old number for removes\n * is what makes the column mean \"where do I find this line\", which is the only\n * reason to print it.\n */\nfunction hunkRows(hunk: PatchHunk): { kind: LineKind; number: number; text: string }[] {\n let oldLine = hunk.oldStart\n let newLine = hunk.newStart\n const rows = []\n for (const line of hunk.lines) {\n const kind = kindOf(line)\n // The prefix is diff syntax, not content — the marker column says it now.\n const text = line.slice(1)\n if (kind === 'add') rows.push({ kind, number: newLine++, text })\n else if (kind === 'remove') rows.push({ kind, number: oldLine++, text })\n else {\n rows.push({ kind, number: newLine, text })\n oldLine++\n newLine++\n }\n }\n return rows\n}\n\nconst MARKER = { context: ' ', add: '+', remove: '-' } as const\n\n/**\n * A patch for a change that has **not happened yet** — an approval prompt's\n * `Edit`, where the input names the old and new text but no line numbers exist\n * because the edit has not been applied and this client has never read the file.\n *\n * `newStart: 0` is how it says so, and {@link TerminalDiff} reads that back: a\n * diff whose hunks all start at zero renders without a number column rather than\n * printing a column of zeroes or inventing an offset.\n */\nexport function previewPatch(input: unknown): FilePatch | undefined {\n const edit = input as { file_path?: unknown; old_string?: unknown; new_string?: unknown } | null\n const before = typeof edit?.old_string === 'string' ? edit.old_string : undefined\n const after = typeof edit?.new_string === 'string' ? edit.new_string : undefined\n if (before === undefined && after === undefined) return undefined\n const lines = [\n ...(before ? before.split('\\n').map((line) => `-${line}`) : []),\n ...(after ? after.split('\\n').map((line) => `+${line}`) : []),\n ]\n if (lines.length === 0) return undefined\n return {\n ...(typeof edit?.file_path === 'string' && { path: edit.file_path }),\n hunks: [{ oldStart: 0, oldLines: 0, newStart: 0, newLines: 0, lines }],\n }\n}\n\nexport function TerminalDiff({ patch }: { patch: FilePatch }) {\n const rows = patch.hunks.map(hunkRows)\n // Numbers only when the engine gave any (see `previewPatch`): a column of\n // zeroes would look like line 0 of the file, which is a lie about where to\n // look — and a diff with no numbers is honest about having none.\n const numbered = patch.hunks.some((hunk) => hunk.newStart > 0)\n // Wide enough for the largest number any row will print, so the marker and the\n // code start on the same column throughout — including across a hunk boundary,\n // where the numbers jump.\n const width = numbered ? String(Math.max(...rows.flat().map((row) => row.number), 1)).length : 0\n // number + space + marker + space (or just the marker and a space)\n const columns = numbered ? width + 3 : 2\n\n return (\n <div className='term-diff'>\n {rows.map((hunk, index) => (\n <div key={index}>\n {/* Hunks are not adjacent in the file, and a blank line would say they\n were merely a paragraph apart. The CLI's separator is the honest\n one: a row that says lines were skipped. */}\n {index > 0 ? (\n <Row columns={columns} glyph={' '.repeat(width) + ' ⋮'} tone='faint' />\n ) : null}\n {hunk.map((row, line) => (\n <Row\n key={line}\n columns={columns}\n data-diff={row.kind}\n glyph={\n numbered\n ? `${String(row.number).padStart(width)} ${MARKER[row.kind]}`\n : MARKER[row.kind]\n }\n // `pre-wrap` on the body already keeps the code's own indentation;\n // an empty line still has to occupy its row, hence the space.\n >\n {row.text || ' '}\n </Row>\n ))}\n </div>\n ))}\n {patch.truncated ? (\n <Row columns={columns} tone='faint'>\n … diff truncated\n </Row>\n ) : null}\n </div>\n )\n}\n","import { memo, type ReactNode } from 'react'\nimport { Streamdown, type Components } from 'streamdown'\nimport { cn } from '../../lib/utils.ts'\nimport { CopyAction, WithActions } from './affordances.tsx'\nimport { Band } from './row.tsx'\n\n/**\n * Markdown on the character grid.\n *\n * The `lines` variant did this by letting the renderer draw its prose defaults\n * and then overriding roughly sixty declarations back off with `!important` —\n * every margin, every list gap, the fenced-code card's four nested boxes, the\n * table's frame and its floating button pill. That is a losing position by\n * construction: each renderer upgrade is a new set of boxes to find and unpaint,\n * and the CSS says what the output must *not* look like rather than what it is.\n *\n * So this maps the elements instead. Streamdown keeps what only it can do —\n * streaming-safe parsing of half-written markdown — and every block it emits is\n * built from the same {@link Row}/{@link Band} primitives the rest of the theme\n * uses. There is no `!important` here and there is no CSS fighting anything.\n *\n * The rendering rules are a terminal's, not a document's:\n *\n * - **One type size.** Headings are weight and colour; a bigger glyph would\n * break the only line height the grid has.\n * - **Markers are cells.** A bullet is `- ` (two columns), an ordered marker\n * `1. ` (three), and the text starts on the next column exactly as it does in\n * the markdown source — so a wrapped line hangs under the text, not the\n * bullet, and nesting costs one marker width per level.\n * - **Code is a band, not a card.** No frame, no language strip, no floating\n * buttons: a fenced block is a wash of dim text running to the screen edge,\n * which is what a terminal shows.\n */\n\n/** Pull the text out of a fenced block's React children (`<code>…</code>`). */\nfunction codeText(node: ReactNode): string {\n if (node === null || node === undefined || typeof node === 'boolean') return ''\n if (typeof node === 'string' || typeof node === 'number') return String(node)\n if (Array.isArray(node)) return node.map(codeText).join('')\n const element = node as { props?: { children?: ReactNode } }\n return element.props ? codeText(element.props.children) : ''\n}\n\n/** The language a fence declared, from the `language-*` class react-markdown\n * puts on the inner `<code>`. Kept as a data attribute rather than used to\n * highlight: the CLI does not colour a fenced block inside a message, and a\n * second highlighter here would be a second theme to keep in sync. */\nfunction fenceLanguage(node: ReactNode): string | undefined {\n const child = Array.isArray(node) ? node.find(Boolean) : node\n const className = (child as { props?: { className?: string } } | undefined)?.props?.className\n const match = /language-([\\w-]+)/.exec(className ?? '')\n return match?.[1]\n}\n\n/**\n * A fenced block: a band of dim text, and the one place a copy affordance earns\n * its keep most — a command in a message is there to be run.\n *\n * The renderer's own copy/download buttons are turned off (`controls={false}`)\n * in favour of this: they are web buttons floating over the content in another\n * application's idiom, and they are not switchable by the surface the way\n * everything else here is.\n */\nfunction CodeBand({ code, language }: { code: string; language?: string }) {\n return (\n <WithActions className='term-block' actions={<CopyAction text={code} label='Copy code' />}>\n <Band className='term-code' data-language={language}>\n <pre className='term-pre'>{code}</pre>\n </Band>\n </WithActions>\n )\n}\n\n/** Headings differ only in tone — a terminal has one type size, so `h1` and `h4`\n * cannot differ in anything else. Return-typed so the parameter picks up the\n * renderer's own component signature rather than a narrower hand-written one. */\nconst heading = (tone: 'bright' | 'fg'): Components['h1'] =>\n function Heading({ children }) {\n return (\n <div className='term-block' data-tone={tone} data-weight='bold'>\n {children}\n </div>\n )\n }\n\nconst TERMINAL_COMPONENTS: Components = {\n p: ({ children }) => <div className='term-block'>{children}</div>,\n\n h1: heading('bright'),\n h2: heading('bright'),\n h3: heading('bright'),\n h4: heading('fg'),\n h5: heading('fg'),\n h6: heading('fg'),\n\n // `term-block` on every block-level output, without exception: it is what the\n // one-blank-line-between-blocks rule keys on, and a block that forgets it butts\n // straight up against its neighbour (a list running into the paragraph after\n // it, which is exactly how this was found).\n ul: ({ children }) => <ul className='term-block term-list'>{children}</ul>,\n ol: ({ children }) => <ol className='term-block term-list term-list-ordered'>{children}</ol>,\n // The marker is the gutter's `::before` (a CSS counter for the ordered case),\n // so a list item is literally a Row: same two columns, same hanging indent,\n // and a nested list inside the body indents by exactly one marker width.\n li: ({ children }) => (\n <li className='term-row term-li'>\n <span className='term-gutter' aria-hidden />\n <div className='term-body'>{children}</div>\n </li>\n ),\n\n blockquote: ({ children }) => (\n <blockquote className='term-block term-quote' data-tone='dim'>\n {children}\n </blockquote>\n ),\n\n hr: () => <div className='term-block term-rule' aria-hidden />,\n\n // Fenced code. `pre` owns the whole block — the inner `<code>` is only where\n // the text and the language live — so the band is built here and `code` never\n // sees a fence.\n pre: ({ children }) => <CodeBand code={codeText(children)} language={fenceLanguage(children)} />,\n code: ({ children }) => (\n <code className='term-inline-code' data-tone='blue'>\n {children}\n </code>\n ),\n\n strong: ({ children }) => (\n <strong data-tone='bright' data-weight='bold'>\n {children}\n </strong>\n ),\n em: ({ children }) => <em className='term-em'>{children}</em>,\n a: ({ children, href }) => (\n <a className='term-link' data-tone='blue' href={href} target='_blank' rel='noreferrer'>\n {children}\n </a>\n ),\n\n // Tables keep the grid by being a grid: monospace cells, one line per row, and\n // dim box-drawing rules instead of borders that would land between cells.\n table: ({ children }) => (\n <div className='term-block term-table-wrap'>\n <table className='term-table'>{children}</table>\n </div>\n ),\n // Every table element, and not just the ones that looked wrong: any element\n // left unmapped keeps the renderer's own padded, bordered default, and a\n // single one of those puts its rows off the line grid (`td`'s `py-2` was\n // making table rows 23px in an 18px theme).\n thead: ({ children }) => <thead className='term-thead'>{children}</thead>,\n tbody: ({ children }) => <tbody>{children}</tbody>,\n tr: ({ children }) => <tr>{children}</tr>,\n th: ({ children }) => (\n <th data-tone='bright' data-weight='bold'>\n {children}\n </th>\n ),\n td: ({ children }) => <td>{children}</td>,\n}\n\nexport interface TerminalMarkdownProps {\n children: string\n /** Streaming text: tolerate half-written markdown (unclosed fences, half links). */\n streaming?: boolean\n className?: string\n}\n\nexport const TerminalMarkdown = memo(\n function TerminalMarkdown({ children, streaming, className }: TerminalMarkdownProps) {\n return (\n <Streamdown\n mode={streaming ? 'streaming' : 'static'}\n parseIncompleteMarkdown={streaming}\n // The renderer's copy/download affordances are web buttons floating over\n // the content. A terminal has none, and the transcript's own selection\n // is how you copy from one.\n controls={false}\n components={TERMINAL_COMPONENTS}\n className={cn('term-md', className)}>\n {children}\n </Streamdown>\n )\n },\n (prev, next) =>\n prev.children === next.children &&\n prev.streaming === next.streaming &&\n prev.className === next.className,\n)\n","import { useEffect, useRef, type ReactNode } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * A row you can open, that you can also select text out of.\n *\n * A `<button>` cannot be both: text inside one is selectable in principle, but\n * the drag that selects it ends in a `click`, so releasing the mouse collapses\n * the very block you were highlighting — and the selection is discarded with it.\n * A transcript is *read* far more often than it is opened, so copying a command\n * out of a row has to win over the affordance that expands it.\n *\n * So: a `div` with the button role and the keyboard behaviour restored by hand,\n * and a press that is refused when the pointer travelled (a drag, not a click)\n * or when a selection is standing. Both checks are cheap and neither is a\n * heuristic about intent — a pointer that moved four pixels was dragging, and a\n * non-collapsed selection *is* the user having selected something.\n */\nconst DRAG_SLOP = 4\n\nexport function Pressable({\n onPress,\n expanded,\n className,\n children,\n}: {\n onPress: () => void\n /** Mirrored to `aria-expanded` when this press opens something. */\n expanded?: boolean\n className?: string\n children: ReactNode\n}) {\n const origin = useRef<{ x: number; y: number } | null>(null)\n return (\n <div\n role='button'\n tabIndex={0}\n aria-expanded={expanded}\n className={cn('term-press', className)}\n onPointerDown={(event) => {\n origin.current = { x: event.clientX, y: event.clientY }\n }}\n onClick={(event) => {\n const from = origin.current\n origin.current = null\n if (from && Math.abs(event.clientX - from.x) + Math.abs(event.clientY - from.y) > DRAG_SLOP)\n return\n // A click that merely *ends* a selection elsewhere on the page still\n // reads as a click; one that ends a selection inside this row is the\n // tail of a drag the slop check may have missed (a slow, short drag).\n const selection = window.getSelection?.()\n if (selection && !selection.isCollapsed && selection.containsNode(event.currentTarget, true))\n return\n onPress()\n }}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n onPress()\n }}>\n {children}\n </div>\n )\n}\n\n/**\n * Keep an expanding block's *first* line reachable.\n *\n * A row that grows from one line to eighty pushes its own top off the screen:\n * the reader presses a summary and lands somewhere in the middle of what they\n * opened, with no clue that the beginning is above them. The fix is not a\n * scroll-into-view on every expand — that would yank a block already fully in\n * view — but the narrow one: if the block now starts above the fold, bring its\n * first line back to the top edge.\n *\n * Deliberately one-directional and only on the open transition. Collapsing needs\n * nothing (the block shrinks toward its own top, which is already on screen),\n * and a block whose top is already visible must not move at all.\n */\nexport function useRevealOnOpen(open: boolean) {\n const ref = useRef<HTMLDivElement>(null)\n const previous = useRef(open)\n useEffect(() => {\n const opened = open && !previous.current\n previous.current = open\n if (!opened) return\n const element = ref.current\n if (!element) return\n // After paint: the rows this block just grew by have to be laid out, and\n // the virtualizer's own size-change correction has to have run, before an\n // offset read here means anything.\n const frame = requestAnimationFrame(() => {\n const scroller = scrollParent(element)\n if (!scroller) return\n const top =\n element.getBoundingClientRect().top -\n scroller.getBoundingClientRect().top +\n scroller.scrollTop\n if (top >= scroller.scrollTop) return\n // One line of air above it, so the first row isn't flush against the\n // scroller's edge — the same blank line every block gets.\n const line = Number.parseFloat(getComputedStyle(element).lineHeight) || 0\n scroller.scrollTop = Math.max(0, top - line)\n })\n return () => cancelAnimationFrame(frame)\n }, [open])\n return ref\n}\n\n/** The nearest ancestor that actually scrolls. */\nfunction scrollParent(from: HTMLElement): HTMLElement | null {\n let node = from.parentElement\n while (node) {\n const overflow = getComputedStyle(node).overflowY\n if ((overflow === 'auto' || overflow === 'scroll') && node.scrollHeight > node.clientHeight)\n return node\n node = node.parentElement\n }\n return null\n}\n","import { formatBytes } from '../../lib/format.ts'\n\n/**\n * The box a tool result's image is drawn in, and the words drawn in it before\n * the bytes arrive.\n *\n * Its own module, and pure, for `result-preview.ts`'s reason with a constant\n * standing where a string stood: `items.tsx` draws these boxes and `height.ts`\n * predicts their pixel height for the virtualizer's `estimateSize` without a\n * DOM. Two spellings of the box would be two different heights, and the row\n * would grow or shrink the moment it mounted.\n *\n * **A fixed box, sized in whole lines, reserved from plan time.** An image's\n * intrinsic dimensions are not knowable before its bytes are, and the\n * alternative — an `exact: false` row corrected on mount — would demote *most*\n * rows of an image-bearing session to estimates and bring back the growing\n * scrollbar the calculator exists to kill. A box that does not depend on what is\n * inside it is exact by definition, at the cost of some letterboxing.\n */\n\n/**\n * Whole lines per image. 12 ≈ 240px at an 18px line — big enough that a\n * screenshot is legible as *what it is* (which is the whole reason images became\n * visible at all), small enough that a call returning four of them does not\n * become a screenful.\n */\nexport const IMAGE_BOX_LINES = 12\n\n/**\n * What the box says before the fetch lands.\n *\n * `bytes` is the decoded size the gateway stamped on the reference — the client\n * holds no bytes at all until it asks for them, so this is a number it cannot\n * compute, the same reason `total_chars` rides beside a truncated head.\n * `formatBytes` rather than a spelling of its own: the panel says \"336.0 KB\"\n * everywhere else, and a second byte formatter is a second thing to keep in\n * step.\n */\nexport function imagePlaceholder(image: { bytes: number }): string {\n return `image · ${formatBytes(image.bytes)}`\n}\n\n/**\n * What it says when the fetch failed — a stale address after a dormant wake\n * (the route 404s rather than serving another call's pixels), a gateway too old\n * to know the route, a dropped connection.\n *\n * It occupies the same box, because the alternative is the row changing height\n * on a network failure. Silence is not an option here the way it is for\n * `HostImage`: that card names the host path in its result text, so a reader can\n * still find the picture; a replayed image part has no path to name.\n */\nexport const IMAGE_UNAVAILABLE = 'image unavailable'\n","/**\n * How much of a tool result a collapsed row shows, and what it says it hid.\n *\n * Its own module, and pure, because **two** consumers must agree on it to the\n * character: `items.tsx` draws these rows, and `height.ts` predicts their pixel\n * height for the virtualizer's `estimateSize` without a DOM. The budget used to\n * be a private constant in `items.tsx` restated as a copy in `height.ts` with a\n * comment admitting the drift risk — this is that comment's fix.\n *\n * **Two budgets, not one.** Lines alone was the old rule and it has an exact\n * blind spot: a minified JSON reply — which is every MCP tool's reply — is ONE\n * line, so a four-line slice kept all thirty thousand characters of it and the\n * row wrapped to a screenful. `hidden` was computed as `lines.length -\n * shown.length`, so it came out zero and the row did not even offer the \"+N\"\n * affordance: the whole blob was simply the transcript now. Characters alone\n * would be wrong the other way, cutting an ordinary short-line result mid-way\n * for no reason. So both apply, and a *first* line longer than the budget is\n * truncated rather than shown whole — a row has to show something or it opens\n * onto nothing.\n */\n\n/** At most this many lines, however short they are. */\nconst PREVIEW_LINES = 4\n/**\n * …and at most this many characters, however few lines they are. Four lines'\n * worth at any realistic terminal width — the row is indented six cells, so a\n * 100ch panel fits ~94 per line — which keeps the budget honest whether the\n * result arrives as four lines or as one long one.\n */\nconst PREVIEW_CHARS = 400\n\nexport type CollapsedResult = {\n /** The lines to draw. The last may be truncated (it ends in `…`). */\n shown: string[]\n /**\n * The trailing \"there is more\" row, already spelled. The *string* rather than\n * a count, because `height.ts` wraps this exact text to size the row, and two\n * spellings would be two different heights.\n */\n more?: string\n}\n\n/**\n * Reported in characters when the truncation happened *inside* a line and in\n * lines otherwise — a one-line JSON blob has no hidden lines to count, and\n * \"+0 lines\" under a visibly cut-off row is worse than saying nothing.\n */\n/**\n * `totalChars` is the **untruncated** length when the replay delivered only a\n * head (protocol's `ToolResultBlock.total_chars`). Passing it is not cosmetic:\n * computed from the head this row would say \"… +7,600 chars\" where the truth is\n * 641,003, and the wrong string is a *different pixel height* — which is exactly\n * the drift this module exists to prevent, since `height.ts` sizes the row by\n * wrapping this same text. Omitted for a whole result, where the lines are the\n * whole truth.\n */\nexport function collapsedResult(lines: string[], totalChars?: number): CollapsedResult {\n const shown: string[] = []\n let chars = 0\n let cut = false\n\n for (const line of lines.slice(0, PREVIEW_LINES)) {\n if (shown.length === 0 && line.length > PREVIEW_CHARS) {\n shown.push(`${line.slice(0, PREVIEW_CHARS)}…`)\n chars = PREVIEW_CHARS\n cut = true\n break\n }\n if (shown.length > 0 && chars + line.length > PREVIEW_CHARS) break\n shown.push(line)\n chars += line.length + 1\n }\n\n // `join` because the newlines are part of what is not being shown — and\n // `totalChars` wins when it exists, because the lines in hand are then a head\n // rather than the result.\n const held = lines.join('\\n').length\n const total = totalChars ?? held\n if (cut) return { shown, more: `… +${(total - chars).toLocaleString()} chars` }\n // A truncated result always has more, even when its head happened to fit the\n // line budget: the row must never claim to be showing everything.\n if (totalChars !== undefined && total > held)\n return { shown, more: `… +${(total - chars).toLocaleString()} chars` }\n const hidden = lines.length - shown.length\n return { shown, more: hidden > 0 ? `… +${hidden} line${hidden === 1 ? '' : 's'}` : undefined }\n}\n","import { createContext, useContext, type ReactNode } from 'react'\n\n/**\n * How a row gets back the part of a tool result the replay did not send.\n *\n * A **context**, not a prop chain, for the same reason the variant is one: the\n * rows that need it are drawn by `terminalBlocks` and by the cards theme's\n * `ToolCallCard`, several layers below whoever holds the session, and a row\n * composed by hand should get the same behaviour without threading a callback\n * through everything in between.\n *\n * The default is a no-op resolving `false`, which is exactly right for every\n * surface that never asked for truncation (the playground, a fixture, an\n * embedder rendering rows by hand): `result.truncated` is only ever set by a\n * replay a renderer opted into, so a row that has no fetcher also has no head to\n * complete. A press still opens the row; it simply has everything already.\n */\nexport type ToolResultFetcher = (toolUseId: string) => Promise<boolean>\n\nconst FetchContext = createContext<ToolResultFetcher>(async () => false)\n\nexport function ToolResultFetchProvider({\n value,\n children,\n}: {\n value: ToolResultFetcher | undefined\n children: ReactNode\n}) {\n return <FetchContext.Provider value={value ?? noop}>{children}</FetchContext.Provider>\n}\n\nconst noop: ToolResultFetcher = async () => false\n\nexport function useToolResultFetcher(): ToolResultFetcher {\n return useContext(FetchContext)\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\n\n/**\n * How a row gets the picture the replay refused to send.\n *\n * The sibling of `tool-result-fetch.tsx` and the same shape of seam, because it\n * answers the same shape of question: an opted-in socket delivers a base64\n * `image` part as an `image_ref` — media type, decoded size, and its index in\n * the stored block — and the bytes are fetched over REST by whoever is actually\n * looking at the row. Across a measured corpus that payload was 91% of every\n * tool result and nothing rendered a byte of it.\n *\n * A **context**, not a prop chain, for the variant's reason: the rows are drawn\n * by `terminalBlocks` and by `ToolCallCard`, several layers under whoever holds\n * the session. The default resolves `undefined`, which is exactly right for\n * every surface that never asked (the playground, a fixture, a hand-composed\n * row): `result.images` is only ever set by a replay a renderer opted into, so a\n * row with no loader also has no reference to load. Only `SessionPanel` supplies\n * a real one, because it owns the session's one attach and therefore the only\n * `(seq, toolUseId)` addresses that mean anything.\n */\n\n/** One image part, as the row addresses it: the reducer's entry plus the id of\n * the call it came back from. `sourceSeq` is the entry's **own** — the\n * result-level one is cleared by text hydration, and a reader who pressed \"show\n * everything\" must still be able to load the screenshot afterwards. */\nexport type ToolResultImageRef = {\n toolUseId: string\n sourceSeq: number\n partIndex: number\n mediaType: string\n bytes: number\n}\n\n/** Resolves an object URL for the picture, or `undefined` when the gateway will\n * not serve it — a stale address after a dormant wake, a gateway with no such\n * route, a dropped connection. The row draws a box either way. */\nexport type ToolResultImageLoader = (ref: ToolResultImageRef) => Promise<string | undefined>\n\nconst noop: ToolResultImageLoader = async () => undefined\n\nconst ImageContext = createContext<ToolResultImageLoader>(noop)\n\nexport function ToolResultImageProvider({\n value,\n children,\n}: {\n value: ToolResultImageLoader | undefined\n children: ReactNode\n}) {\n return <ImageContext.Provider value={value ?? noop}>{children}</ImageContext.Provider>\n}\n\nexport function useToolResultImageLoader(): ToolResultImageLoader {\n return useContext(ImageContext)\n}\n\n/**\n * Long enough that a fast scrub through an image-heavy session fetches nothing\n * it flew past, short enough to be invisible to a reader who stopped.\n *\n * There is no second visibility system here on purpose: the transcript is\n * virtualized, so a *mounted* row is by definition within an overscan of the\n * viewport — the virtualizer already is the IntersectionObserver, and a second\n * answer to a question that has one is how the two disagree.\n */\nconst MOUNT_SETTLE_MS = 150\n\nexport type ToolResultImageState = { src?: string; failed: boolean }\n\n/**\n * One box's load, for either theme.\n *\n * Fires once the row has been mounted for {@link MOUNT_SETTLE_MS}, and then\n * **runs to completion** — an aborted fetch re-pays the whole image on the\n * return visit, and the gateway is HTTP/1.1, so the browser's per-origin\n * connection cap is the concurrency throttle for free.\n *\n * The effect keys on the address's *primitives*, never on the ref object: the\n * reducer replaces items on every streamed delta, so an object-identity dep\n * would re-run this on every token of the turn after it.\n */\nexport function useToolResultImageSrc(ref: ToolResultImageRef): ToolResultImageState {\n const load = useToolResultImageLoader()\n const [state, setState] = useState<ToolResultImageState>({ failed: false })\n const { toolUseId, sourceSeq, partIndex, mediaType, bytes } = ref\n useEffect(() => {\n let live = true\n setState({ failed: false })\n const timer = setTimeout(() => {\n load({ toolUseId, sourceSeq, partIndex, mediaType, bytes })\n .then((src) => {\n if (live) setState({ src, failed: src === undefined })\n })\n .catch(() => {\n if (live) setState({ failed: true })\n })\n }, MOUNT_SETTLE_MS)\n return () => {\n live = false\n clearTimeout(timer)\n }\n }, [load, toolUseId, sourceSeq, partIndex, mediaType, bytes])\n return state\n}\n\n/** ~64 MB of decoded pictures held at once. At the corpus's 335 KB median that\n * is ~190 images, which no viewport holds; the budget exists so a session\n * scrolled end to end does not pin every screenshot it passed. */\nconst CACHE_BUDGET_BYTES = 64 * 1024 * 1024\n\ntype Entry = { pending: Promise<string | undefined>; url?: string; bytes: number }\n\n/**\n * `useHostImage`'s shape, generalized to the replay route — and **bounded**,\n * which `useHostImage` is not.\n *\n * The promise-per-key cache is what makes this callable from a transcript row at\n * all: rows re-render on every streamed delta, and an uncached resolver would\n * re-fetch each time. The LRU is the part that is new. Object URLs pin their\n * blob until revoked, so a fully-scrolled hundred-image session would otherwise\n * hold ~50 MB until the panel unmounted — and evicting means revoking, or the\n * eviction frees a `Map` entry and nothing else.\n *\n * Re-fetching on a return scroll is fine, and is the whole design: the bytes are\n * one authenticated request away, which is precisely what makes it cheap not to\n * have shipped them in the attach.\n */\nexport function useToolResultImages(\n client: WorkerDeckClient,\n sessionId: string | undefined,\n): ToolResultImageLoader {\n const cache = useRef(new Map<string, Entry>())\n useEffect(\n () => () => {\n for (const entry of cache.current.values()) if (entry.url) URL.revokeObjectURL(entry.url)\n cache.current.clear()\n },\n [],\n )\n return useCallback(\n (ref: ToolResultImageRef) => {\n if (!sessionId) return Promise.resolve(undefined)\n // The whole address, because every part of it can change under a row that\n // is still on screen: a dormant wake restarts the seqs, and a cached\n // address that outlived its log must miss rather than serve another\n // call's pixels.\n const key = `${sessionId}:${ref.sourceSeq}:${ref.toolUseId}:${ref.partIndex}`\n const hit = cache.current.get(key)\n if (hit) {\n // Re-inserting is the \"recently used\" half of the LRU: `Map` iterates in\n // insertion order, so eviction reads oldest-first for free.\n cache.current.delete(key)\n cache.current.set(key, hit)\n return hit.pending\n }\n // Fetched rather than pointed at: a bare `<img src>` at the gateway\n // carries a credential in exactly one of four clients (the dashboard's\n // same-origin host), and a broken icon in the other three.\n const pending = client\n .toolResultImage(sessionId, ref.sourceSeq, ref.toolUseId, ref.partIndex)\n .then((blob) => {\n if (blob.size === 0) return undefined\n const url = URL.createObjectURL(blob)\n const entry = cache.current.get(key)\n if (entry) {\n entry.url = url\n entry.bytes = blob.size\n evict(cache.current, key)\n } else {\n // Evicted (or unmounted) while in flight — nothing will ever draw\n // this, and an unrevoked URL is the leak the budget exists to stop.\n URL.revokeObjectURL(url)\n }\n return url\n })\n .catch(() => undefined)\n // The declared size is what the budget counts until the bytes land: a\n // hundred fetches in flight must not all read as free.\n cache.current.set(key, { pending, bytes: ref.bytes })\n return pending\n },\n [client, sessionId],\n )\n}\n\n/** Drop oldest-first until the held bytes fit the budget, revoking as it goes.\n * `keep` is the entry just resolved — evicting the picture a row is about to\n * draw would be a fetch spent on nothing. */\nfunction evict(cache: Map<string, Entry>, keep: string): void {\n let held = 0\n for (const entry of cache.values()) held += entry.bytes\n for (const [key, entry] of cache) {\n if (held <= CACHE_BUDGET_BYTES) return\n if (key === keep) continue\n if (entry.url) URL.revokeObjectURL(entry.url)\n cache.delete(key)\n held -= entry.bytes\n }\n}\n","/**\n * What a folded run of tool calls is, and the one line that stands for it.\n *\n * The fold was shell-only, and the screenshot that started this made the cost\n * obvious: a run of six calls that happened to alternate `Bash` with an MCP tool\n * folded into *four* rows reading \"Ran 1 shell command\", \"Ran 2 shell commands\",\n * \"Ran 1 shell command\" — a count for every gap between the calls it could not\n * group. The grouping rule was right and the *membership* rule was too narrow.\n *\n * So any consecutive tool calls fold. The CLI's own line is the target\n * (`called roam-code, ran 1 shell command`), and the claim is unchanged from the\n * shell version: a tool call is almost never what you came back to read, and six\n * of them bury the sentence that is.\n *\n * Pure and separate because `items.tsx` draws this line and `height.ts` wraps it\n * to predict the row's pixel height without a DOM — the same reason\n * `result-preview.ts` exists. Two spellings would be two different heights.\n */\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { toolInputPreview } from '../../lib/format.ts'\nimport { isShellTool } from '../../lib/tool-icon.ts'\n\ntype ToolCallItem = Extract<TranscriptItem, { kind: 'tool_call' }>\n\n/**\n * What breaks a run.\n *\n * *Consecutive* is still the whole rule, and it is the reason the fold is honest:\n * anything the model said between two calls breaks the run, because that\n * sentence is the reason the second one happened and a count spanning it would\n * claim the two were one act. The recap boundary breaks it too — the virtualized\n * shell folds each side separately — so a count never spans \"what you already\n * read\".\n *\n * `parentToolUseId` is the one addition the wider membership rule needs: a\n * subagent's calls are drawn stepped in behind a rule, and folding one together\n * with a top-level call would put rows from two different frames of reference\n * under a single count.\n */\nexport function foldsTogether(a: ToolCallItem, b: ToolCallItem): boolean {\n return a.parentToolUseId === b.parentToolUseId\n}\n\n/**\n * The family a tool counts as in a run's breakdown.\n *\n * An MCP tool is `mcp__<server>__<tool>`, and the *server* is the useful unit:\n * \"3 roam-code\" is a thing that happened, where three separate tool names are a\n * list to read. Shell tools from both engines collapse to \"shell\" for the same\n * reason. Everything else is its own name, lowercased so the breakdown reads as\n * prose rather than as identifiers.\n */\nexport function toolFamily(name: string): string {\n if (isShellTool(name)) return 'shell'\n const mcp = /^mcp__([^_]+(?:_[^_]+)*?)__/.exec(name)\n if (mcp?.[1]) return mcp[1].replace(/_/g, '-')\n return name.toLowerCase()\n}\n\n/**\n * The run's one line.\n *\n * A shell-only run keeps its old wording exactly — \"Ran 3 shell commands\" — both\n * because it is the commonest run by far and because it is already the sentence\n * people read here; widening the fold should not have re-worded the case that\n * was working. Anything mixed gets the count plus a breakdown, loudest family\n * first.\n */\nexport function runSummary(items: readonly ToolCallItem[], busy: boolean): string {\n const verb = busy ? 'Running ' : 'Ran '\n const tail = busy ? '…' : ''\n const n = items.length\n\n const counts = new Map<string, number>()\n for (const item of items) {\n const family = toolFamily(item.name)\n counts.set(family, (counts.get(family) ?? 0) + 1)\n }\n if (counts.size === 1 && counts.has('shell')) {\n return `${verb}${n} shell command${n === 1 ? '' : 's'}${tail}`\n }\n // Descending by count, then alphabetical — a stable order matters more than it\n // looks: this string is the row's measured height, so a run whose breakdown\n // reordered between renders would remeasure for no reason.\n const breakdown = [...counts.entries()]\n .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n .map(([family, count]) => `${count} ${family}`)\n .join(', ')\n // The ellipsis trails the whole line, not the count — \"Running 2 tools… · 1\n // read, 1 shell\" reads as though the sentence ended and then carried on.\n return `${verb}${n} tool${n === 1 ? '' : 's'} · ${breakdown}${tail}`\n}\n\n/* ── The task block's one line ─────────────────────────────────────────────\n *\n * A `Task` call and everything its subagent produced collapse to one row (see\n * `blocks.ts`), and these are that row's words. Same contract as `runSummary`:\n * `height.ts` wraps these exact strings to predict the row's pixel height with\n * no DOM, so the component must render them verbatim — two spellings would be\n * two different heights.\n */\n\nconst clip = (text: string, max = 80): string =>\n text.length > max ? text.slice(0, max - 1) + '…' : text\n\nconst trimmed = (value: unknown): string | undefined =>\n typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined\n\n/**\n * The row's identity half: which task this is.\n *\n * The Claude SDK's `Task` input carries `subagent_type` (e.g. \"Explore\") and a\n * 3–5 word `description`, and both are worth the line: parallel tasks are the\n * whole reason the block exists, and two rows both reading `Task(…)` answer\n * nothing. `Task(Explore · find the auth check)` — falling back to the\n * ordinary input preview when an engine sends neither, so the header is never\n * emptier than a plain tool row's.\n */\nexport function taskLabel(task: ToolCallItem): string {\n return `${task.name}(${taskIdentity(task)})`\n}\n\n/**\n * The inner half of {@link taskLabel} — `Explore · find the auth check` — without\n * the `Task(…)` wrapper.\n *\n * Split out for the sub-agent takeover's header, which names the agent it is\n * showing and has no room (or reason) to repeat the tool's own name: the whole\n * surface *is* that Task. Extracted rather than re-spelled so the header and the\n * row it was opened from cannot drift, and so both keep matching protocol's\n * `subagentLabel`, which reads the same two input fields for the sessions list.\n */\nexport function taskIdentity(task: ToolCallItem): string {\n const input = task.input as { description?: unknown; subagent_type?: unknown } | null\n const description = trimmed(input?.description)\n const agent = trimmed(input?.subagent_type)\n return agent && description\n ? `${agent} · ${clip(description)}`\n : (agent ?? (description ? clip(description) : toolInputPreview(task.input)))\n}\n\n/**\n * **What this agent was actually asked** — the sub-agent's brief, or undefined\n * when the engine did not give us one.\n *\n * Measured against a live session rather than assumed: the Agent SDK's call\n * carries `{description, subagent_type, run_in_background, prompt}`, and\n * `prompt` is the instruction.\n *\n * **Whether it also arrives in the stream depends on the agent.** A foreground\n * `Task` forwards its brief as a real nested `user_message` (that is what\n * `forwardSubagentText` buys and what the reducer stamps a `parentToolUseId`\n * on), so `subagentItems` picks it up and the frame already has it. A\n * **background** agent does not: measured on a session with eight of them,\n * zero items carried a `parentToolUseId` on a `user` kind. Those runs are\n * exactly the ones you open a takeover on — an agent working while you read\n * something else — and their brief was nowhere at all.\n *\n * So this is the *fallback*, not the source: the callers splice it in only when\n * the frame carries no brief of its own, or the same instruction would be drawn\n * twice.\n *\n * `description` is deliberately not a fallback: it is the 3–5 word label the\n * header already prints, and repeating it as a brief would claim we know the\n * instruction when we do not. **Codex genuinely has none** — its `spawn_agent`\n * message is an encrypted blob on the wire — so on that engine this is\n * undefined and the row is not drawn, rather than drawn empty.\n */\nexport function taskBrief(task: ToolCallItem): string | undefined {\n const input = task.input as { prompt?: unknown } | null\n return trimmed(input?.prompt)\n}\n\nconst callBusy = (call: ToolCallItem): boolean =>\n call.status === 'running' || call.status === 'pending'\n\n/** Did this one call fail? Both spellings are needed: an out-of-loop execution\n * failure sets `status` with no `is_error` block to read, and an engine can flag\n * `is_error` on a call the reducer has not settled yet. */\nexport const callFailed = (call: ToolCallItem): boolean =>\n call.status === 'failed' || call.result?.isError === true\n\n/**\n * Does a folded run colour red? **Only when its last call failed.**\n *\n * It used to be `some`, on the argument that a failure should colour the block\n * rather than fragment it. The argument was right about not fragmenting and\n * wrong about `some`: a run is a sequence the model worked through, and a\n * failure it recovered from two calls later is how work goes — a grep that\n * matched nothing, a build fixed on the second go. Reddening the whole run for\n * it means a normal working session is painted red, which spends the colour\n * that should have been left for the one thing still broken.\n *\n * The last call is the run's *outcome*, and an outcome is what a collapsed row\n * can honestly claim. The failures inside it are not hidden — they are one\n * press away, each red on its own row, and the recap counts every one. The\n * **scrubber agrees with this rule** rather than overriding it: it marks a\n * failed call only when the call is its row's outcome, which for a run is\n * exactly this one. It used to mark every member on the argument that the\n * rail asks a different question; against a real session that was nine alarms\n * on the rail for a transcript reddening one row.\n */\nexport function runFailed(items: readonly ToolCallItem[]): boolean {\n const last = items[items.length - 1]\n return last !== undefined && callFailed(last)\n}\n\n/** Is anything inside still going? The call itself, normally — the Task\n * settles only when its subagent finishes — but a bridged or deferred child\n * can outlive it, and a pulse that stopped while a child still worked would\n * read as a hang. */\nexport function taskBusy(task: ToolCallItem, children: readonly TranscriptItem[]): boolean {\n return callBusy(task) || children.some((child) => child.kind === 'tool_call' && callBusy(child))\n}\n\n/**\n * Does the row colour red? **The task's own outcome, and nothing else.**\n *\n * It used to be \"or any child call's\", which does not survive contact with a\n * real subagent: an agent that ran a hundred calls, one of them a grep that\n * matched nothing, came back with a red line saying it had failed. It had not —\n * it had done exactly what it was asked, and the transcript said otherwise in\n * the one colour reserved for things that need a human.\n *\n * This is the call `SubagentInfo.status` already makes, and it made it for this\n * reason (see `packages/protocol`): the sub-agent's **own** `tool_result`\n * `is_error`, deliberately not `taskFailed`. The argument there was that a\n * nothing-matched grep must not read as a failed run *beside a session name*;\n * what a hundred-call agent shows is that it must not read that way beside the\n * `Task` row either. Two surfaces, one rule, one spelling.\n *\n * Nothing is concealed by this. A failed child is red on its own row, one press\n * away, and the recap counts it. The **scrubber follows this rule too** and no\n * longer marks such a child: a red tick on the rail says precisely what this\n * row is forbidden from saying. The sub-agent band still says an agent ran\n * here, and the task's own red tick still says it came back broken — which is\n * what the two channels are for.\n */\nexport function taskFailed(task: ToolCallItem): boolean {\n return callFailed(task)\n}\n\n/**\n * The collapsed task row's one line: identity, then scale.\n *\n * `Task(Explore · find the auth check) · 7 tools…` while the subagent works —\n * the count grows as it does, which is the row's progress reading, and the\n * trailing ellipsis is the same in-flight signal `runSummary` uses (the pulse\n * in the gutter carries the beat). Settled, the ellipsis drops:\n * `… · 7 tools`. \"Tools\" and not \"tool calls\" because `runSummary` already\n * chose that word for the same count one row over.\n *\n * The counts are counted from the absorbed children, never read from the\n * engine's structured Task output — WorkerDeck does not plumb structured tool\n * results to clients, so a transcript replayed tomorrow must spell the same\n * line from the same items it holds today.\n *\n * With no tool calls yet — the subagent thinking, or only its brief arrived —\n * the line says `working…`, because `0 tools…` reads as a stall; settled with\n * none it says `done`.\n */\nexport function taskSummary(task: ToolCallItem, children: readonly TranscriptItem[]): string {\n const busy = taskBusy(task, children)\n const calls = children.reduce((n, child) => n + (child.kind === 'tool_call' ? 1 : 0), 0)\n const label = taskLabel(task)\n if (calls === 0) return busy ? `${label} · working…` : `${label} · done`\n return `${label} · ${calls} tool${calls === 1 ? '' : 's'}${busy ? '…' : ''}`\n}\n","/**\n * The terminal theme's block model — **which rows exist**.\n *\n * Pure and separate from `items.tsx` because which rows exist is part of what\n * the theme *is*: the virtualizer counts these, `height.ts` sizes them, the\n * scrubber addresses them, and both renderers — the virtualized shell in\n * `agent/Transcript.tsx` and the plain `TerminalTranscript` — must fold\n * identically or two clients would be showing different transcripts of the\n * same session. `items.tsx` re-exports everything here, so its old imports\n * keep working; the components stay there, the model lives here.\n *\n * Two folds happen in one pass:\n *\n * - **Runs.** Consecutive tool calls fold into one row (`tool-run.ts` owns the\n * membership rule and the summary line).\n * - **Tasks.** A `Task` tool call *absorbs* every item whose\n * `parentToolUseId` names it — its subagent's brief, thinking, text and\n * tool calls — into ONE row, **wherever those items fall in the stream**.\n * Subagents run in parallel, so their items interleave with each other and\n * with top-level work; a consecutive-run rule cannot group them, which is\n * why absorption is by parent id and not by adjacency. The absorbed items\n * are folded again *within* the block (a subagent's consecutive calls\n * become runs — `foldsTogether` already keys on `parentToolUseId`), and the\n * block is always collapsed by default: that preserves the height\n * calculator's invariant that an unmounted row is collapsed by definition.\n *\n * The absorption rule, precisely: a task block forms for a **top-level** tool\n * call (`parentToolUseId` empty) that has at least one child in the slice,\n * and an item is absorbed iff its parent is such a call. Everything else\n * renders as its own row, which settles the edges deliberately:\n *\n * - A **childless** `Task` call is a plain tool call and folds into runs —\n * right for a task still spawning, and for a resumed session whose\n * children were compacted away entirely.\n * - An **orphan** child (its parent call absent from the slice) keeps today's\n * behaviour: its own row, stepped in behind a rule. The recap boundary is\n * the load-bearing case — the shell folds each side separately, so a task\n * split by the boundary shows its post-boundary children *below* the seam\n * rather than hiding new work inside a collapsed row above it, the same\n * claim the run fold makes about never counting across \"what you already\n * read\".\n * - A **grandchild** (parent is itself a subagent's call — unreachable from\n * today's engines, which do not nest sidechains) is not absorbed and not\n * dropped: it renders top-level, stepped in. An unmapped item must be\n * visible, never gone.\n * - Two top-level calls separated only by absorbed items **fold together**:\n * the interleaved step was another frame's work, and once it is absorbed\n * the two calls are adjacent on screen — the count matches what the reader\n * sees.\n */\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { foldsTogether } from './tool-run.ts'\n\nexport type ToolCallItem = Extract<TranscriptItem, { kind: 'tool_call' }>\n\n/**\n * The id of the tool call this item was produced inside, or `undefined` at the\n * top level. One spelling for both shapes the reducer emits: `assistant_text`\n * / `thinking` / `tool_call` carry `parentToolUseId: string | null` on every\n * instance, while `user` carries it **optionally** (a human prompt has no\n * parent at all — the key exists only on a subagent's brief). Callers must go\n * through this rather than reading the field, or the absent-key case silently\n * types as a compile error on one kind and a miss on another.\n */\nexport function parentOf(item: TranscriptItem): string | undefined {\n const parent = 'parentToolUseId' in item ? item.parentToolUseId : undefined\n return parent ?? undefined\n}\n\n/**\n * **The frame membership rule**: the items a sub-agent produced, and nothing\n * else — what the takeover renders instead of the whole conversation.\n *\n * One exported function rather than a `filter` at each call site, because this\n * is a rule two renderers have to agree on: iOS mirrors the terminal model out\n * of this module, and a phone that framed a sub-agent slightly differently would\n * be a second answer to \"what is this agent doing\".\n *\n * It picks up the brief (a `user` item *with* a parent), the thinking, the\n * streamed text (deltas are namespaced per sidechain, so `streaming:<parentId>`\n * carries the parent like any other item), every tool call with its result, and\n * the final report. It excludes the spawning `Task` call itself — that is the\n * frame, not a row in it — and every other agent's work.\n *\n * The slice is safe to hand straight to {@link terminalBlocks} at offset 0:\n * nothing in it is top-level, so nothing absorbs, and consecutive calls still\n * fold into runs because {@link foldsTogether} keys on an *equal* parent rather\n * than on absence of one. Row indices are internally consistent because the rows\n * and the `items` they came from are the same array.\n */\nexport function subagentItems(\n items: readonly TranscriptItem[],\n parentToolUseId: string,\n): TranscriptItem[] {\n return items.filter((item) => parentOf(item) === parentToolUseId)\n}\n\n/** Is this a row the transcript folds into a run? Any tool call is — see\n * `tool-run.ts` for why this is no longer shell-only. */\nexport function isRunCall(item: TranscriptItem): item is ToolCallItem {\n return item.kind === 'tool_call'\n}\n\n/** One transcript item as its own row. */\nexport type ItemBlock = { key: string; item: TranscriptItem; index: number }\n/** A folded run of consecutive tool calls — one row for `run.length` items.\n * At the top level its coverage is contiguous (`[index, index + run.length)`);\n * inside a task block the members' global indices may be scattered (the run is\n * consecutive in the *subagent's* stream, not the transcript's). */\nexport type RunBlock = {\n key: string\n run: ToolCallItem[]\n /** Every member's global transcript index, in stream order — `childIndices`'\n * sibling, and needed for the same reason: a run folded across an absorbed\n * gap has no `[index, index + len)` coverage, so a member's ordinal within\n * the run (what the scrubber anchors a failure by) is unrecoverable from\n * `index` arithmetic. */\n indices: number[]\n index: number\n}\n/** What a task block's children fold into. Never a task block itself — the\n * engines do not nest sidechains, and a hypothetical grandchild renders\n * top-level rather than vanishing (see the module comment). */\nexport type LeafBlock = ItemBlock | RunBlock\n\n/**\n * A `Task` call and everything produced inside it, as ONE row — collapsed by\n * default, pressable to expand, the same shape as the folded tool run.\n *\n * - `task` is the call itself; `index` its own transcript index, which is the\n * row's address (rows stay ordered by `index`).\n * - `children` are the absorbed items in stream order, folded exactly as\n * top-level rows are; each leaf's `index` is its first member's *global*\n * transcript index.\n * - `childIndices` is the flat list of every absorbed item's global index, in\n * stream order. It exists because absorption is the one exception to row\n * contiguity: a child run's members can straddle other rows' starts, so no\n * `[start, start + len)` arithmetic can say what this row covers —\n * `rowIndexForItem` answers from this list instead.\n */\nexport type TaskBlock = {\n key: string\n task: ToolCallItem\n children: LeafBlock[]\n childIndices: number[]\n index: number\n}\n\n/**\n * Fold consecutive tool calls into runs and absorb subagent items into task\n * blocks, leaving everything else alone.\n */\nexport type TerminalBlock = ItemBlock | RunBlock | TaskBlock\n\n/** The absorbed items, flat and in stream order — what `taskSummary` counts\n * and the collapsed row's one line is built from. */\nexport function taskChildItems(block: TaskBlock): TranscriptItem[] {\n return block.children.flatMap((child) => ('run' in child ? child.run : [child.item]))\n}\n\n/** Append one item to a leaf-block list, folding it into the previous run when\n * the membership rule allows — the one fold implementation, used for the\n * top-level stream and for each task's children alike. */\nfunction pushLeaf(out: LeafBlock[], item: TranscriptItem, index: number): void {\n const previous = out.at(-1)\n if (isRunCall(item)) {\n if (previous && 'run' in previous && foldsTogether(previous.run[0]!, item)) {\n previous.run.push(item)\n previous.indices.push(index)\n } else {\n // Keyed by the run's *first* call, so the key is stable as the run grows.\n out.push({ key: `run:${item.id}`, run: [item], indices: [index], index })\n }\n return\n }\n out.push({ key: `${item.kind}:${item.id}`, item, index })\n}\n\n/**\n * @param offset What `items[0]`'s index is in the whole transcript — the\n * virtualized shell folds each side of the recap boundary separately, and the\n * rows still have to say where they sit for the catch-up dimming.\n * @param fold Whether to group at all. `false` gives one block per item —\n * no runs *and no task absorption* — which is what the cards variant\n * renders: this is the terminal theme's rule and must not silently reshape\n * another renderer's row list.\n */\nexport function terminalBlocks(\n items: readonly TranscriptItem[],\n offset = 0,\n fold = true,\n): TerminalBlock[] {\n if (!fold) {\n return items.map((item, position) => ({\n key: `${item.kind}:${item.id}`,\n item,\n index: offset + position,\n }))\n }\n\n // Which top-level tool calls have children in this slice, and what those\n // children are. Collected over the whole slice before any block is built:\n // membership is by parent id, not adjacency, so a call cannot know it is a\n // task until every item has been seen.\n const topLevelCalls = new Set<string>()\n for (const item of items) {\n if (item.kind === 'tool_call' && parentOf(item) === undefined) topLevelCalls.add(item.id)\n }\n const childrenOf = new Map<string, { item: TranscriptItem; index: number }[]>()\n items.forEach((item, position) => {\n const parent = parentOf(item)\n if (parent !== undefined && topLevelCalls.has(parent)) {\n const list = childrenOf.get(parent)\n if (list) list.push({ item, index: offset + position })\n else childrenOf.set(parent, [{ item, index: offset + position }])\n }\n })\n\n const out: TerminalBlock[] = []\n for (const [position, item] of items.entries()) {\n const index = offset + position\n const parent = parentOf(item)\n // Absorbed into its task's row — it must not also appear as its own.\n if (parent !== undefined && childrenOf.has(parent)) continue\n if (item.kind === 'tool_call') {\n const children = childrenOf.get(item.id)\n if (children) {\n const folded: LeafBlock[] = []\n for (const child of children) pushLeaf(folded, child.item, child.index)\n out.push({\n key: `task:${item.id}`,\n task: item,\n children: folded,\n childIndices: children.map((child) => child.index),\n index,\n })\n continue\n }\n }\n pushLeaf(out as LeafBlock[], item, index)\n }\n return out\n}\n\n/** Spacing between two items: a blank line, unless the pair belongs together.\n * Tool output already sits under its call, and a run of tool calls reads as one\n * block — the CLI leaves no blank line inside either. */\nexport function needsBlank(previous: TranscriptItem, next: TranscriptItem): boolean {\n if (previous.kind === 'tool_call' && next.kind === 'tool_call') return false\n return true\n}\n\n/** The same rule over blocks: a run counts as the tool calls it folded, and a\n * task block counts as the `Task` call it stands for — a collapsed task row\n * sits flush with the tool rows of the same turn, exactly as the call itself\n * did before it grew children. */\nexport function blockNeedsBlank(previous: TerminalBlock, next: TerminalBlock): boolean {\n const kind = (block: TerminalBlock) => ('item' in block ? block.item.kind : 'tool_call')\n return !(kind(previous) === 'tool_call' && kind(next) === 'tool_call')\n}\n","import { useEffect, useState } from 'react'\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { formatBytes, formatCost, formatDuration, toolInputPreview } from '../../lib/format.ts'\nimport { isMutatingTool } from '../../lib/tool-icon.ts'\nimport { usePulse } from '../agent/pulse.tsx'\nimport { CopyAction, WithActions } from './affordances.tsx'\nimport { TerminalDiff } from './diff.tsx'\nimport { TerminalMarkdown } from './markdown.tsx'\nimport { Pressable, useRevealOnOpen } from './press.tsx'\nimport { IMAGE_BOX_LINES, IMAGE_UNAVAILABLE, imagePlaceholder } from './image-box.ts'\nimport { collapsedResult } from './result-preview.ts'\nimport { useToolResultFetcher } from '../agent/tool-result-fetch.tsx'\nimport { useToolResultImageSrc } from '../agent/tool-result-image.tsx'\nimport { runFailed, runSummary } from './tool-run.ts'\nimport { type ToolCallItem } from './blocks.ts'\nimport { Band, Blank, Ink, Row, type Tone } from './row.tsx'\n\n// The pure block model — which rows exist — lives in `blocks.ts` now that a\n// task block made it non-trivial; re-exported here because this file is where\n// consumers have always found it.\nexport {\n blockNeedsBlank,\n isRunCall,\n needsBlank,\n parentOf,\n taskChildItems,\n terminalBlocks,\n type ItemBlock,\n type LeafBlock,\n type RunBlock,\n type TaskBlock,\n type TerminalBlock,\n type ToolCallItem,\n} from './blocks.ts'\n\n/**\n * One transcript item, drawn as terminal rows.\n *\n * Each renderer here answers the same two questions the CLI answers: which\n * marker goes in the gutter, and what the body says. Nothing chooses a spacing,\n * a radius or a border — a row is a row, and the space between blocks is a\n * {@link Blank} decided by the transcript, which is the only thing that knows\n * whether two blocks belong together.\n *\n * The markers are the CLI's:\n *\n * | glyph | means |\n * |-------|------------------------------------------|\n * | `❯` | what you typed |\n * | `●` | what the model said, or a tool it called |\n * | `⎿` | that tool's output, one level in |\n * | `✻` | thinking |\n * | `!` | a notice from the runner, not the model |\n */\n\n/**\n * The prompt marker, in the transcript and in the composer both.\n *\n * `❯` rather than `>`: it is the shell prompt of every terminal anyone has\n * configured this decade, and it reads as a *prompt* where `>` reads as a\n * quotation or a greater-than. Exported because the composer is the same\n * marker in the same gutter cell — that is the whole claim of the terminal\n * composer, and two spellings of it would put the caret one glyph off the\n * column the rows above start on.\n */\nexport const PROMPT_GLYPH = '❯'\n\n/** How much the expanded row shows before offering the rest. The collapsed\n * budget is `collapsedResult`'s, shared with the height calculator.\n *\n * Exported for one test and not from the package: protocol's\n * `TOOL_RESULT_HEAD_CHARS` is chosen to exceed it, so that a truncated result's\n * open state is byte-identical to an untruncated one and only the uncapped\n * `full` press ever fetches. That relationship is asserted, not assumed. */\nexport const RESULT_PREVIEW_CHARS = 2000\n\n/** Whole lines up to a character budget — never zero, because a single line\n * longer than the budget still has to be shown or the row would open onto\n * nothing. */\nfunction clipToChars(lines: string[], maxChars: number): string[] {\n const out: string[] = []\n let chars = 0\n for (const line of lines) {\n if (out.length > 0 && chars + line.length > maxChars) break\n out.push(line)\n chars += line.length + 1\n }\n return out\n}\n\nexport function UserRow({ item }: { item: Extract<TranscriptItem, { kind: 'user' }> }) {\n return (\n <div className='term-user'>\n {item.attachments?.length ? (\n <Row glyph={PROMPT_GLYPH} glyphTone='dim' tone='dim'>\n {item.attachments.map((attachment) => attachment.name).join(', ')}\n </Row>\n ) : null}\n {/* Every line of a multi-line prompt keeps the band and the column; only\n the first keeps the marker, exactly as a shell continuation does. */}\n {item.text\n ? item.text.split('\\n').map((line, index) => (\n <Row\n key={index}\n glyph={index === 0 ? PROMPT_GLYPH : undefined}\n glyphTone='dim'\n tone='fg'>\n {line || ' '}\n </Row>\n ))\n : null}\n </div>\n )\n}\n\nexport function AssistantRow({\n item,\n}: {\n item: Extract<TranscriptItem, { kind: 'assistant_text' }>\n}) {\n return (\n // Copy the markdown source, not the rendered text: what you paste into an\n // issue or a commit message should keep its lists and its code fences.\n // Absent while streaming — half a message is not a thing anyone wants on\n // their clipboard, and the button would appear mid-sentence.\n <WithActions\n actions={item.streaming ? null : <CopyAction text={item.text} label='Copy message' />}>\n <Row glyph='●' glyphTone='fg' tone='fg'>\n <TerminalMarkdown streaming={item.streaming}>{item.text}</TerminalMarkdown>\n </Row>\n </WithActions>\n )\n}\n\nexport function ThinkingRow({ item }: { item: Extract<TranscriptItem, { kind: 'thinking' }> }) {\n return (\n <Row glyph='✻' glyphTone='dim' tone='dim'>\n <span className='term-em'>{item.text}</span>\n </Row>\n )\n}\n\n/** The gutter dot's colour: the call's state, said without a badge. */\nconst TOOL_TONE: Record<string, Tone> = {\n running: 'blue',\n pending: 'blue',\n deferred: 'yellow',\n settled: 'dim',\n failed: 'red',\n}\n\nexport function ToolRow({ item }: { item: ToolCallItem }) {\n const [open, setOpen] = useState(false)\n const [full, setFull] = useState(false)\n // Set while the rest of a truncated result is in flight. Row-local, unlike the\n // text itself, which lands in transcript state — this is a spinner, not a\n // fact about the session.\n const [fetching, setFetching] = useState(false)\n const fetchResult = useToolResultFetcher()\n const reveal = useRevealOnOpen(open)\n const status = item.status ?? (item.result === undefined ? 'running' : 'settled')\n const busy = status === 'running' || status === 'pending'\n const isError = status === 'failed' || item.result?.isError === true\n // Ticks only while this row is really running: an idle transcript of a hundred\n // settled calls starts no timers at all.\n const pulse = usePulse(busy)\n\n const text = item.result?.text ?? ''\n const lines = text.trimEnd().split('\\n')\n // Three states, not two. Collapsed shows a few lines; open shows the output up\n // to a character budget; `full` lifts the budget. The middle one is the reason\n // the budget exists at all: a tool result can be a hundred thousand characters\n // (a test run, a `find /`), and the whole of it lands in **one** virtual row —\n // the virtualizer mounts rows, so it cannot help with what is inside a single\n // one. Without the clip, expanding one row commits thousands of DOM nodes and\n // the transcript stops being smooth for the rest of the session.\n // The true total when the replay delivered only a head — the row must count\n // what is missing, not what it happens to hold.\n const collapsed = collapsedResult(lines, item.result?.totalChars)\n const preview = open\n ? full\n ? lines\n : clipToChars(lines, RESULT_PREVIEW_CHARS)\n : collapsed.shown\n const hidden = lines.length - preview.length\n const clipped = open && !full && hidden > 0\n // The replay sent a head. `full` then means \"fetch the rest\", not \"lift the\n // clip\" — and the marker outlives the clip, because a head short enough to fit\n // the open budget still is not the result.\n const truncated = item.result?.truncated === true\n const missing = truncated ? (item.result?.totalChars ?? 0) - text.length : 0\n\n const tone: Tone = isError\n ? 'red'\n : // A settled write is green: skimming a run, \"what did it change\" is the\n // question you come back to, and the one you might need to undo.\n status === 'settled' && isMutatingTool(item.name)\n ? 'green'\n : (TOOL_TONE[status] ?? 'dim')\n\n // What is worth having on the clipboard from a tool call is the command you\n // would re-run, when there is one, and otherwise its output.\n const command = (item.input as { command?: unknown } | null)?.command\n const copyable = typeof command === 'string' ? command : text\n\n return (\n // Open, the whole block keeps a fill: an expansion that runs past the top of\n // the screen otherwise leaves no mark of where it began, and the reader has\n // to guess which rows they opened.\n <div ref={reveal} className={open ? 'term-open' : undefined}>\n <WithActions\n actions={copyable ? <CopyAction text={copyable} label='Copy' /> : null}>\n <Pressable onPress={() => setOpen((v) => !v)} expanded={open}>\n <Row glyph={busy ? pulse : '●'} glyphTone={tone} tone='fg'>\n <Ink bold tone='bright'>\n {item.name}\n </Ink>\n <Ink tone='dim'>({toolInputPreview(item.input)})</Ink>\n {item.backend && item.backend !== 'server' ? (\n <Ink tone='faint'> · {item.backend}</Ink>\n ) : null}\n </Row>\n </Pressable>\n {/* Above the output, because when a call returned a picture the picture is\n what the call was: a screenshot's result text is \"took a screenshot\".\n Drawn collapsed as well as open — this is not detail behind a press,\n it is the answer. */}\n {item.result?.images?.map((image) => (\n <TerminalImage key={image.partIndex} toolUseId={item.id} image={image} />\n ))}\n {/* A file edit shows its diff, not its result prose: \"The file has been\n updated\" is what the *model* needed to hear, and the change is what the\n reader did. The text stays reachable by expanding. */}\n {item.patch && !open ? (\n <TerminalDiff patch={item.patch} />\n ) : text ? (\n <>\n {preview.map((line, index) => (\n <Row\n key={index}\n indent={1}\n columns={3}\n glyph={index === 0 ? '⎿' : undefined}\n tone={isError ? 'red' : 'dim'}>\n {line || ' '}\n </Row>\n ))}\n {/* One row for \"there is more\", pressable exactly when pressing it\n would do something. Collapsed, the count is a label — the header\n above is already the toggle, and a second control for the same act\n is one too many. Open and clipped, it is the way to the rest.\n Collapsed spells its own label (it may be counting characters\n rather than lines, having cut inside one), and it is the string\n `height.ts` sizes the row from. */}\n {!open ? (\n collapsed.more ? (\n <Row indent={1} columns={3} tone='faint'>\n {collapsed.more}\n </Row>\n ) : null\n ) : clipped || truncated ? (\n <Row indent={1} columns={3} tone='faint'>\n {fetching ? (\n // Never a row that does nothing when pressed: it says what it is\n // doing instead. See `planToolCall`'s comment on the same rule.\n <>… fetching {(item.result?.totalChars ?? 0).toLocaleString()} chars</>\n ) : clipped || truncated ? (\n <button\n type='button'\n className='term-press term-link'\n onClick={() => {\n // One press, two acts, in the order that keeps the row\n // honest: lift the clip immediately (that part is local and\n // instant), and fetch the rest when there is a rest. The\n // fetched text lands in transcript state, so the row\n // re-renders with the marker gone.\n setFull(true)\n if (!truncated) return\n setFetching(true)\n void fetchResult(item.id).finally(() => setFetching(false))\n }}>\n {truncated\n ? `… +${missing.toLocaleString()} chars — fetch the rest`\n : `… +${hidden} line${hidden === 1 ? '' : 's'} — show all ${text.length.toLocaleString()} chars`}\n </button>\n ) : (\n <>\n … +{hidden} line{hidden === 1 ? '' : 's'}\n </>\n )}\n </Row>\n ) : hidden > 0 ? (\n <Row indent={1} columns={3} tone='faint'>\n … +{hidden} line{hidden === 1 ? '' : 's'}\n </Row>\n ) : null}\n </>\n ) : null}\n </WithActions>\n </div>\n )\n}\n\n/** One image part of a tool result, as the reducer holds it. */\ntype ToolResultImage = NonNullable<NonNullable<ToolCallItem['result']>['images']>[number]\n\n/**\n * A picture a tool returned, in a box of {@link IMAGE_BOX_LINES} whole lines.\n *\n * **Three states, one height.** Before the fetch lands the box is a wash and the\n * size the gateway declared; after it, the picture, letterboxed inside the same\n * box; on a refusal, `image unavailable` in it. Nothing here may ever collapse\n * to nothing — that is `HostImage`'s return-null-then-pop, which in a\n * *virtualized* list is not a flicker but a reflow of every row below it, and\n * the height calculator would have been lying about the row from plan time.\n *\n * The box is why the calculator can stay exact: it is a constant, not a function\n * of pixels nobody has downloaded yet.\n */\nfunction TerminalImage({ toolUseId, image }: { toolUseId: string; image: ToolResultImage }) {\n const { src, failed } = useToolResultImageSrc({ toolUseId, ...image })\n return (\n <Row indent={1} columns={3}>\n <div\n className='term-image'\n data-state={src ? 'loaded' : failed ? 'failed' : 'pending'}\n // The one measurement in this file, and it is the shared constant\n // spelled once — `height.ts` adds exactly this many lines for exactly\n // this box.\n style={{ height: `calc(var(--term-line) * ${IMAGE_BOX_LINES})` }}>\n {src ? (\n <img src={src} alt={imagePlaceholder(image)} />\n ) : (\n <Ink tone='faint'>{failed ? IMAGE_UNAVAILABLE : imagePlaceholder(image)}</Ink>\n )}\n </div>\n </Row>\n )\n}\n\n/**\n * A run of tool calls, as one line.\n *\n * The CLI's own compression, and the reason it works: a tool call is almost\n * never what you came back to read. `Bash(pnpm -w typecheck)` and forty lines of\n * its output say nothing the model's next sentence doesn't say better, and six\n * of them in a row bury that sentence a screen and a half down. So a run\n * collapses to its count and gets out of the way — and opens, in full, the\n * moment it is the thing you actually want.\n *\n * The membership, wording and failure rules live in `tool-run.ts`, shared with\n * the height calculator. A failure never breaks the run — fragmenting it around\n * one would hide the failure in a longer list rather than surface it — but only\n * the run's **last** call colours it, because that is the run's outcome and an\n * outcome is what a collapsed row can honestly claim (see `runFailed`).\n */\nexport function ToolRunRow({ items }: { items: ToolCallItem[] }) {\n const [open, setOpen] = useState(false)\n const reveal = useRevealOnOpen(open)\n const busy = items.some((item) => {\n const status = item.status ?? (item.result === undefined ? 'running' : 'settled')\n return status === 'running' || status === 'pending'\n })\n const failed = runFailed(items)\n const pulse = usePulse(busy)\n\n return (\n <div ref={reveal} className={open ? 'term-open' : undefined}>\n <Pressable onPress={() => setOpen((v) => !v)} expanded={open}>\n {/* No marker once settled: a run of calls is an aside, and a bullet\n would give it the weight of something the model said. While one is\n running the pulse earns the gutter — that much is news. */}\n <Row\n glyph={busy ? pulse : undefined}\n glyphTone={busy ? 'mark' : undefined}\n tone={failed ? 'red' : 'dim'}>\n {runSummary(items, busy)}\n </Row>\n </Pressable>\n {open ? (\n <div>\n {items.map((item) => (\n <ToolRow key={item.id} item={item} />\n ))}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport function TurnResultRow({\n item,\n}: {\n item: Extract<TranscriptItem, { kind: 'turn_result' }>\n}) {\n return (\n <div>\n <Row tone={item.isError ? 'red' : 'faint'}>\n {item.isError ? item.subtype : 'done'} · {formatDuration(item.durationMs)} ·{' '}\n {formatCost(item.totalCostUsd)}\n </Row>\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?.map((message, index) => (\n <Row key={index} tone='red'>\n {message}\n </Row>\n ))}\n </div>\n )\n}\n\nexport function NoticeRow({ item }: { item: Extract<TranscriptItem, { kind: 'notice' }> }) {\n const error = item.level === 'error'\n return (\n <Row glyph='!' glyphTone={error ? 'red' : 'yellow'} tone={error ? 'red' : 'dim'}>\n {item.text}\n </Row>\n )\n}\n\nexport function FileRow({\n item,\n href,\n}: {\n item: Extract<TranscriptItem, { kind: 'file_delivered' }>\n href?: string\n}) {\n return (\n <Row glyph='⤓' glyphTone='blue' tone='dim'>\n {href ? (\n <a className='term-link' data-tone='blue' href={href} download>\n {item.path}\n </a>\n ) : (\n <Ink tone='blue'>{item.path}</Ink>\n )}\n <Ink tone='faint'> · {formatBytes(item.bytes)}</Ink>\n {item.description ? <Ink tone='faint'> · {item.description}</Ink> : null}\n </Row>\n )\n}\n\n/** A once-a-second clock, running only while `on`. */\nexport function useTicker(on: boolean): number {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n if (!on) return\n const timer = setInterval(() => setNow(Date.now()), 1000)\n return () => clearInterval(timer)\n }, [on])\n return now\n}\n\n/**\n * The working line: the mark's pulse, the word, and the run's readings — the\n * CLI's own status line, which is a *row of the transcript* rather than a\n * spinner floating over it.\n */\nexport function WorkingRow({\n label,\n startedAt,\n tokens,\n}: {\n label: string\n startedAt?: number\n tokens?: number\n}) {\n const pulse = usePulse(true)\n // The row owns its clock rather than taking `now` from above, because it is\n // mounted only while a turn is in flight: the ticking starts and stops with\n // the thing being timed, and an idle transcript runs no interval at all.\n const now = useTicker(startedAt !== undefined)\n const elapsed = startedAt === undefined ? undefined : formatDuration(now - startedAt)\n const readings = [elapsed, tokens ? `↓ ${(tokens / 1000).toFixed(1)}k tokens` : undefined].filter(\n Boolean,\n )\n return (\n <Row glyph={pulse} glyphTone='mark' tone='mark'>\n {label}\n {readings.length ? <Ink tone='faint'> ({readings.join(' · ')})</Ink> : null}\n </Row>\n )\n}\n\nexport { Band, Blank }\n","import type { CSSProperties, HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport {\n AffordanceProvider,\n resolveAffordances,\n type TerminalAffordances,\n} from './affordances.tsx'\n\n/**\n * The root of the terminal theme: the element that establishes the character\n * cell every row inside it lands on.\n *\n * It exists as a component rather than a class because the cell has to be *set*\n * somewhere — the font, the size and the line height together are what make\n * `1ch` mean one column and `--term-line` mean one row — and because that is the\n * one place a host is allowed to change the metrics. Everything below this\n * element measures itself in those two units and never in pixels.\n *\n * The geometry and the palette live in `styles/terminal.css`; this only names\n * the element and hands down the two numbers.\n */\nexport interface TerminalSurfaceProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Cell size in **whole pixels**. Fractional values put every other row on a\n * half-pixel: text softens and the diff bands show a seam, which is exactly\n * what a grid renderer must not do. Defaults (13/18) are the CLI's own.\n */\n fontSize?: number\n lineHeight?: number\n /**\n * How far a full-bleed band reaches past the content edge — normally the\n * scroller's own horizontal padding. A band cancels it with matched negative\n * margin and padding, so a diff hunk's wash runs to the viewport edge the way\n * a terminal's line does, instead of stopping at a gutter.\n */\n bleed?: string\n /**\n * The things a real terminal cannot do — the pointer hover fill, the\n * hover-revealed copy actions. `false` turns them all off, an object picks.\n * Default: all on. See {@link TerminalAffordances}; none of them costs layout,\n * so switching them off changes no glyph's position.\n */\n affordances?: TerminalAffordances | boolean\n}\n\nexport function TerminalSurface({\n fontSize,\n lineHeight,\n bleed,\n affordances,\n className,\n style,\n children,\n ...props\n}: TerminalSurfaceProps) {\n const resolved = resolveAffordances(affordances)\n return (\n <div\n data-terminal=''\n // A space-separated list so CSS can ask for one with `~=` — the styling\n // half of the switch, where the JS half (whether a button exists at all)\n // rides the context.\n data-affordances={\n [resolved.hover && 'hover', resolved.actions && 'actions'].filter(Boolean).join(' ') ||\n undefined\n }\n className={cn('min-w-0', className)}\n style={\n {\n ...(fontSize !== undefined && { '--term-font-size': `${Math.round(fontSize)}px` }),\n ...(lineHeight !== undefined && { '--term-line': `${Math.round(lineHeight)}px` }),\n ...(bleed !== undefined && { '--term-bleed': bleed }),\n ...style,\n } as CSSProperties\n }\n {...props}>\n <AffordanceProvider value={resolved}>{children}</AffordanceProvider>\n </div>\n )\n}\n","import {\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type DragEvent,\n type ReactNode,\n type Ref,\n} 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 { useTranscriptVariant } from './transcript-variant.tsx'\nimport type { TerminalAffordances } from '../terminal/affordances.tsx'\nimport { PROMPT_GLYPH } from '../terminal/items.tsx'\nimport { TerminalSurface } from '../terminal/surface.tsx'\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 /**\n * Terminal theme only: the cell the composer draws on. Passed straight\n * through to its own {@link TerminalSurface}, and it has to be passed — the\n * composer sits outside the transcript's scroller, so it establishes a second\n * surface, and a surface handed no metrics falls back to the 13/18 default. A\n * host running the transcript at its editor's size and the composer at the\n * default would have the caret land on a different column from the text above\n * it, which is the one thing this theme exists to prevent.\n */\n fontSize?: number\n lineHeight?: number\n /** Terminal theme only; see {@link TerminalAffordances}. */\n affordances?: TerminalAffordances | boolean\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 fontSize,\n lineHeight,\n affordances,\n className,\n ref,\n}: ComposerProps) {\n const inline = layout === 'inline'\n // The transcript's variant reaches here through the panel-wide context, so\n // the composer matches the rows above it without a prop chain.\n const terminal = useTranscriptVariant() === 'terminal'\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 <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 ) : null\n\n /**\n * The terminal composer's **gutter cell** — the one column every transcript\n * row's marker sits in, so whatever stands here cannot move the text beside\n * it. It holds one of three things, in this order:\n *\n * `✕` **while the session is working**, because the gutter is where the eye\n * already is (it is the column the pulse and every marker share) and stop is\n * the only action that matters mid-run. Note the condition is `busy` alone,\n * not the old `busy && !canSend`: with send living on the other side of the\n * field there is no longer a slot to compete for, so typing a follow-up while\n * a turn runs no longer hides the way to stop it.\n *\n * A cross rather than the `■` this started as: the square reads as a *state*\n * (\"stopped\") in a column where `●` and `◆` really are states, so it looked\n * like a status marker rather than something to press. A cross reads as an\n * action and collides with nothing else on the column. It is also one of the\n * few candidates that measures exactly 1ch in JetBrains Mono — `⏹`, `⏸` and\n * `⏻`, the obvious picks, are 1.05–1.31 cells and would break the grid.\n *\n * `+` **otherwise**, when there is anything to attach — the composer's own\n * affordance, in the composer's own gutter.\n *\n * `❯` when neither applies, so the column is never empty and the typed line\n * never shifts. Blue, not the brand's coral: coral is the *working* mark, and\n * a prompt waiting for you is not the session working.\n */\n const gutter = busy ? (\n <GlyphButton gutter label='Interrupt' tone='yellow' onClick={onInterrupt}>\n ✕\n </GlyphButton>\n ) : canAttach ? (\n <GlyphButton\n gutter\n label='Attach files'\n disabled={disabled}\n onClick={() => fileInput.current?.click()}>\n +\n </GlyphButton>\n ) : (\n <span aria-hidden className='term-gutter' data-tone='blue'>\n {PROMPT_GLYPH}\n </span>\n )\n\n const interrupting = busy && !canSend\n const submitButton = terminal ? (\n // Terminal furniture rather than chat furniture: a glyph that lights up on\n // hover/focus instead of a filled pill. `↵` is the only thing on this side\n // now — stop moved to the gutter — so it means one thing at all times, and\n // a reader never has to check which symbol is currently under their cursor.\n <GlyphButton label='Send' disabled={!canSend} onClick={submit} tone={canSend ? 'blue' : undefined}>\n ↵\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 const dropHandlers = {\n onDragOver: (e: DragEvent) => {\n if (attachments && !attachments.disabled) {\n e.preventDefault()\n setDragging(true)\n }\n },\n onDragLeave: () => setDragging(false),\n onDrop: (e: DragEvent) => {\n if (!attachments || attachments.disabled) return\n e.preventDefault()\n setDragging(false)\n pick(e.dataTransfer.files)\n },\n }\n\n const errorRow = attachments?.error ? (\n <div\n className={cn(\n 'mx-auto mt-1 flex w-full max-w-[var(--wd-content-max-w,48rem)] items-center gap-2 text-label text-danger',\n )}>\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 ) : null\n\n if (terminal) {\n // The CLI's own prompt, on the CLI's own grid: `>` sits in the gutter cell\n // every transcript row's marker sits in, so what you type starts on the\n // column what you are reading starts on. That alignment is the entire\n // reason this is its own branch rather than the docked chrome restyled —\n // the composer is outside the transcript's scroller, so it needs a surface\n // of its own, at the same metrics.\n const line = lineHeight ?? 18\n return (\n <div data-slot='composer' className={cn('shrink-0', className)}>\n <TerminalSurface\n {...dropHandlers}\n fontSize={fontSize}\n lineHeight={lineHeight}\n affordances={affordances}\n bleed='1ch'\n data-dragging={dragging || undefined}\n className={cn('term-composer', disabled && 'opacity-60')}>\n <div className='term-composer-body'>\n {staged.length > 0 && attachments ? (\n <AttachmentStrip attachments={attachments} />\n ) : null}\n <div className='term-row'>\n {/* The hidden input only — its trigger lives in the gutter. */}\n {canAttach ? fileField : null}\n {gutter}\n <div className='flex min-w-0 items-start'>\n <PromptArea\n {...bind}\n triggers={triggers}\n // What the agent receives is markdown, so the marker the user\n // typed is the marker that has to survive. Left on (the\n // default), the editor rewrites `- ` to `• ` in the *model*,\n // not just on screen — so a bulleted message reached the agent\n // as `• item`, which is not a list in any markdown parser, and\n // drew a glyph the character grid has no cell for. Off, the\n // convenience stays and only the rewrite goes:\n // `insertListContinuation` keys on `[•\\-*] ` and reuses the\n // line's own marker, so Enter after `- item` still inserts\n // `\\n- `, and Enter on an empty item still leaves the list.\n normalizeBullets={false}\n onSubmit={submit}\n disabled={disabled}\n placeholder={disabled ? 'Session ended' : placeholder}\n // The field's own metrics are the cell's: one row is one line,\n // and it grows in whole lines from there.\n minHeight={line}\n maxHeight={line * 10}\n aria-label='Message the agent'\n className='term-composer-field min-w-0 flex-1'\n onImagePaste={(file) => attachments?.add([file])}\n />\n {submitButton}\n </div>\n </div>\n {/* Model and mode below the prompt, on the body column so they read\n as a continuation of the line rather than new chrome. Absent\n under `controlsSurface: 'external'`. */}\n {toolbar ? (\n <div className='term-row'>\n <span aria-hidden className='term-gutter' />\n <div className='flex min-w-0 items-center gap-[1ch]'>{toolbar}</div>\n </div>\n ) : null}\n </div>\n </TerminalSurface>\n {errorRow}\n </div>\n )\n }\n\n return (\n <div data-slot='composer' className={cn('px-3 pb-3', className)}>\n <div\n {...dropHandlers}\n className={cn(\n 'mx-auto w-full max-w-[var(--wd-content-max-w,48rem)] overflow-hidden border border-border bg-bg',\n 'transition-colors rounded-lg shadow-(--shadow-xs)',\n '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 // See the terminal composer above: `•` is not a markdown list\n // marker, and this rewrite reaches the sent message.\n normalizeBullets={false}\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 // See the terminal composer above: `•` is not a markdown list\n // marker, and this rewrite reaches the sent message.\n normalizeBullets={false}\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 {errorRow}\n </div>\n )\n}\n\n/**\n * A composer action as a **character**, for the terminal composer: no pill, no\n * border, nothing drawn until you reach for it — the surface only appears on\n * hover/focus, which is how a terminal's own affordances behave.\n *\n * One cell wide and one line tall (`term-glyph` in `terminal.css`), so it sits\n * on the same grid as the row it shares and removing it would move nothing.\n */\nfunction GlyphButton({\n tone,\n label,\n disabled,\n onClick,\n gutter,\n className,\n children,\n}: {\n tone?: 'blue' | 'yellow'\n label: string\n disabled?: boolean\n onClick: () => void\n /** Standing in the gutter cell rather than at the row's trailing edge, so the\n * glyph aligns to the column's start like every transcript marker instead of\n * centring in the 2ch cell — see `.term-glyph[data-gutter]`. */\n gutter?: boolean\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 data-tone={tone}\n data-gutter={gutter || undefined}\n className={cn('term-glyph', className)}>\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 const terminal = useTranscriptVariant() === 'terminal'\n return (\n <div\n className={cn(\n 'flex gap-2 overflow-x-auto',\n // The terminal form draws no rule of its own: the composer's frame is\n // already directly below this strip, and two rules a few pixels apart\n // is the box the theme has none of. Geometry in `terminal.css`.\n terminal ? 'term-attachments' : 'border-b border-border px-2 py-2',\n )}>\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 const terminal = useTranscriptVariant() === 'terminal'\n // Corners are the whole difference: rounded and floating in `cards`, square\n // and inside the frame here. Kept as one flag rather than four so a thumbnail\n // and the overlays stacked on it cannot disagree about their own shape.\n const round = terminal ? '' : 'rounded-md'\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 bg-surface',\n round,\n terminal ? 'term-attachment' : 'border border-border',\n failed && 'border-danger/50',\n )}\n data-failed={terminal && failed ? '' : undefined}>\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={cn('absolute inset-0 flex items-center justify-center bg-black/35', round)}>\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={cn(\n 'absolute inset-0 flex items-center justify-center bg-black/45 text-warning',\n round,\n )}>\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={cn(\n 'absolute flex size-4 items-center justify-center bg-surface text-fg-3 hover:text-fg-1',\n // Cards hang it off the corner as a round badge; the terminal tucks it\n // inside, squared, with two rules for its corner (`terminal.css`).\n terminal\n ? 'term-attachment-remove'\n : '-top-1 -right-1 rounded-full border border-border shadow-(--shadow-xs)',\n )}>\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 { 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'\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 [showInput, setShowInput] = useState(false)\n const [denying, setDenying] = useState(false)\n const [reason, setReason] = useState('')\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\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 &amp; 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 { ArrowLeft } from 'lucide-react'\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatDuration } from '../../lib/format.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Ink, Row } from '../terminal/row.tsx'\nimport { TerminalSurface } from '../terminal/surface.tsx'\nimport { taskBusy, taskFailed, taskIdentity } from '../terminal/tool-run.ts'\nimport type { ToolCallItem } from '../terminal/blocks.ts'\nimport { usePulse } from './pulse.tsx'\nimport { useTicker } from '../terminal/items.tsx'\n\n/**\n * The one line above a sub-agent takeover: who this is, how it is doing, and the\n * way back.\n *\n * **It claims exactly what the `TaskRow` it was opened from claims** — the same\n * `taskBusy` / `taskFailed` / tool count over the same items — and deliberately\n * *not* `SubagentInfo.status`. Protocol documents those two as divergent on\n * purpose (`index.ts:1476-1487`), but that divergence is transcript-versus-*list*\n * and this surface **is** the transcript. The disagreement that must not exist\n * here is between a header and the rows directly beneath it.\n *\n * The rollup is still allowed one job — naming an agent whose `Task` call is not\n * in the transcript — because a label is not content, and `SessionInfo.subagents`\n * keeps only eight settled records (`SUBAGENT_HISTORY`), so it can never be the\n * source of what a frame *shows*.\n *\n * Both variants, because the panel has two and a strip that only existed in one\n * would make the takeover a terminal-theme feature rather than a session feature.\n */\nexport function SubagentStrip({\n task,\n items,\n label,\n onBack,\n terminal,\n fontSize,\n lineHeight,\n}: {\n /** The spawning call. Absent when the transcript does not have it — the strip\n * still draws, because the way back must exist even when the content cannot. */\n task: ToolCallItem | undefined\n /** The frame's items, for the busy reading and the tool count. */\n items: readonly TranscriptItem[]\n /** Fallback name when there is no task item. */\n label: string\n onBack: () => void\n terminal: boolean\n fontSize?: number\n lineHeight?: number\n}) {\n const busy = task ? taskBusy(task, items) : false\n const failed = task ? taskFailed(task) : false\n const pulse = usePulse(busy)\n const tools = items.reduce((n, item) => n + (item.kind === 'tool_call' ? 1 : 0), 0)\n // Ticks only while it works, off the same once-a-second clock the working\n // line uses. A settled agent has no end timestamp to measure against, and\n // \"N tools\" is the settled reading anyway — a frozen clock would read as a\n // stall. Absent `ts` (an item from before the reducer stamped it) simply\n // draws no elapsed rather than counting from the epoch.\n const startedAt = busy ? task?.ts : undefined\n const now = useTicker(startedAt !== undefined)\n const elapsed = startedAt === undefined ? undefined : formatDuration(now - startedAt)\n\n const name = task ? taskIdentity(task) : label\n /**\n * Running or finished, in the theme's own words rather than new ones:\n * `taskSummary` already says `working…` and `done` for exactly this state one\n * row over, and a header that said \"running\"/\"completed\" about the same agent\n * would be a second vocabulary for one fact. The trailing ellipsis is the\n * theme's in-flight signal; the pulse beside it is the beat.\n *\n * Unknown when the transcript has no `Task` call to read — better silent than\n * confidently wrong about an agent we cannot see.\n */\n const status = !task ? undefined : failed ? 'failed' : busy ? `${pulse} working…` : 'done'\n const detail = [tools > 0 ? `${tools} tool${tools === 1 ? '' : 's'}` : undefined, elapsed]\n .filter(Boolean)\n .join(' · ')\n\n if (terminal) {\n return (\n <TerminalSurface fontSize={fontSize} lineHeight={lineHeight} className='shrink-0'>\n {/* A row on the grid, not a chrome bar: the takeover is a mode of the\n transcript, and a toolbar in some other metric above it would read as\n a different application's. The whole line is the target — there is\n exactly one thing to do here. */}\n <button\n type='button'\n onClick={onBack}\n aria-label='Back to the session'\n className='block w-full cursor-pointer text-left'>\n {/* The arrow is in the gutter unconditionally. It used to give way to\n the pulse while the agent worked, which put the way *out* of the\n frame on a timer — the one control here should not come and go.\n The beat moved into the status instead. `indent` because this is a\n frame around the rows rather than one of them, and a marker flush\n against the panel edge reads as a clipped row. */}\n <Row glyph='←' glyphTone='dim' indent={1} tone={failed ? 'red' : 'green'}>\n {name}\n {status ? (\n <Ink tone={failed ? 'red' : busy ? 'mark' : 'dim'}> · {status}</Ink>\n ) : null}\n {detail ? <Ink tone='faint'> · {detail}</Ink> : null}\n </Row>\n </button>\n </TerminalSurface>\n )\n }\n\n return (\n <div className='flex shrink-0 items-center gap-2 border-b border-border px-3 py-1.5'>\n <Button variant='ghost' size='sm' onClick={onBack} className='h-6 gap-1 px-1.5'>\n <ArrowLeft className='size-3.5' />\n Back\n </Button>\n <span\n className={cn('min-w-0 flex-1 truncate text-body-sm', failed ? 'text-danger' : 'text-fg-2')}>\n {name}\n </span>\n {status ? (\n <span\n className={cn(\n 'shrink-0 text-label',\n failed ? 'text-danger' : busy ? 'text-accent' : 'text-fg-3',\n )}>\n {status}\n </span>\n ) : null}\n {detail ? <span className='shrink-0 text-label text-fg-4'>{detail}</span> : 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'\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\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 questions = parseUserQuestions(request.input)\n const [selections, setSelections] = useState<Selection[]>(() =>\n questions.map(() => EMPTY_SELECTION),\n )\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 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, useRef, type ReactNode } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport { Blank, Ink, Row } from './row.tsx'\n\n/**\n * The parts a terminal prompt is built from.\n *\n * An approval and a question are the two places the transcript stops being a log\n * and becomes a form, and \"no boxes\" has to be paid for by something. In the CLI\n * it is paid for three ways, and all three are here: a **rule** marks where the\n * run stops and the decision starts, the options are **numbered** so a key press\n * is an answer, and a **hint line** says which keys. That is what makes a prompt\n * answerable without reaching for the mouse — which is the whole reason a\n * terminal UI can be faster than a dialog.\n *\n * Everything stays on the grid: the rules are one line tall with the stroke\n * drawn through the middle by a background (a border would cost layout), and the\n * roving `❯` lives in the same gutter cell every other row uses.\n */\n\n/**\n * The boundary above a prompt. Solid separates the run from the decision; dashed\n * separates parts *within* it (the CLI puts one between a diff and its question),\n * which is why there are two weights and not one.\n */\nexport function Rule({ dashed }: { dashed?: boolean }) {\n return <div className={cn('term-rule-row', dashed && 'term-rule-dashed')} aria-hidden />\n}\n\n/** The dim `·`-separated key legend under a prompt. */\nexport function Hint({ children }: { children: ReactNode }) {\n return (\n <Row tone='faint'>\n {children}\n </Row>\n )\n}\n\n/**\n * A framed payload — a preview, a snippet. The frame is drawn with four\n * background gradients rather than a border, so it costs no layout: a 1px border\n * would push its contents a pixel off the column every other row sits on.\n */\nexport function Box({ children, className }: { children: ReactNode; className?: string }) {\n return <div className={cn('term-box', className)}>{children}</div>\n}\n\n/**\n * The prompt's heading: what is being asked, and about what.\n *\n * Two lines because the engine gives two — `displayName` (\"Edit file\") is the\n * action, and the subject (the path) is the thing it acts on. The CLI shows them\n * exactly this way, and it is the one place in the theme where colour is used\n * for emphasis rather than for state.\n */\nexport function PromptTitle({ title, subject }: { title: string; subject?: string }) {\n return (\n <>\n <Row tone='blue' bold>\n {title}\n </Row>\n {subject ? <Row tone='dim'>{subject}</Row> : null}\n </>\n )\n}\n\nexport type Choice = {\n key: string\n label: string\n /** Rendered dim on its own row under the label, as the CLI does for a\n * multi-select's options — never appended to the label, which would make the\n * row wrap and cost the list its scannability. */\n description?: string\n /**\n * Present → the row carries selection state and draws it. `marker` says in\n * which idiom: `[x]` for a multi-select, `(•)` for a one-of. Absent → the row\n * is an action (Allow, Cancel), which has no state to show.\n */\n checked?: boolean\n marker?: 'check' | 'radio'\n /**\n * Chosen, in a list that draws no markers (a one-of). The colour is the whole\n * signal there: without it, tabbing back to an answered question would show no\n * trace of the answer given.\n */\n selected?: boolean\n danger?: boolean\n /** Rendered under the row, outside the button — a preview, a text field. The\n * caller decides when it exists (focused, checked); a button may not hold one. */\n detail?: ReactNode\n}\n\n/** The two-state glyph pairs, in the forms a terminal would use. */\nconst MARKERS = {\n check: ['[ ]', '[✓]'],\n radio: ['( )', '(•)'],\n} as const\n\nexport interface ChoicesProps {\n options: Choice[]\n /** Roving index: the one row that is tab-reachable and wears the `❯`. */\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 text field, another question's list) —\n * two lists both chasing `focused` would tear the caret back and forth.\n */\n active?: boolean\n /**\n * Take the keyboard when the list first appears. True by default — a prompt\n * whose whole affordance is \"press 1\" is useless if the keys go somewhere\n * else, and the CLI hands the keyboard over the moment it asks.\n *\n * It is a *first mount* decision only, and it declines when the reader is\n * already typing (see {@link isTyping}): an approval landing mid-sentence must\n * not pull the caret out of the composer and scatter the rest of the sentence\n * across an option list.\n */\n autoFocus?: boolean\n label: string\n}\n\n/** Is the reader mid-keystroke somewhere that keeps its own caret? */\nfunction isTyping(element: Element | null): boolean {\n if (!(element instanceof HTMLElement)) return false\n const editable =\n element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.isContentEditable\n if (!editable) return false\n // Focus in a field is not the same as a message in progress, and only the\n // second is worth protecting. This used to return true for any focused\n // editable, which read fine until a host that keeps the composer focused at\n // all times ran it: VS Code puts the caret in the composer when a session is\n // shown and again on any click in dead space, so the field was *always* the\n // active element and the prompt therefore *never* took the keyboard. The\n // approval that has to be answered was the one thing you could not answer\n // without reaching for the mouse.\n //\n // An empty field has nothing to lose, so the takeover proceeds; a half-typed\n // message still wins, which is the case the guard was written for.\n const text =\n element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement\n ? element.value\n : (element.textContent ?? '')\n return text.trim().length > 0\n}\n\n/**\n * A keyboard-first list of choices, as rows: `↑`/`↓` move, `1`–`9` pick\n * directly, `Enter`/`Space` take the focused one (the button does that itself).\n *\n * The number is part of the gutter, not the label, so every option's text starts\n * on the same column and the list reads as a column of answers rather than a\n * ragged paragraph.\n */\nexport function Choices({\n options,\n focused,\n onFocus,\n onChoose,\n active = true,\n autoFocus = true,\n label,\n}: ChoicesProps) {\n const refs = useRef<Array<HTMLButtonElement | null>>([])\n\n useEffect(() => {\n if (!active) return\n // Two different jobs, told apart by where the keyboard already is rather\n // than by how many times this has run.\n //\n // If focus is already on one of these rows, the roving cursor is moving and\n // the DOM must follow `focused` unconditionally — otherwise the `❯` and the\n // real caret drift apart. If it is not, this is the initial takeover, which\n // is refusable so it cannot snatch a half-written message.\n //\n // This used to be a `mounted` ref: refuse on the first pass, follow on\n // every pass after. That is not safe under StrictMode, which mounts,\n // unmounts and remounts in development — the ref survives the simulated\n // remount, so the second pass saw `mounted === true`, skipped the guard\n // entirely and stole focus from whatever you were typing. It read as\n // correct in production and wrong in dev, which is the worst way round.\n const focusIsInList = refs.current.some(\n (row) => row !== null && row === document.activeElement,\n )\n if (!focusIsInList && (!autoFocus || isTyping(document.activeElement))) return\n refs.current[focused]?.focus()\n }, [active, focused, autoFocus])\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 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 as far as\n // the rows that exist, so `9` on a three-option prompt stays a no-op\n // rather 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}>\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='term-press'>\n {/* `❯ 1.` is the gutter: marker and number together, so the label\n starts on one column whether or not the row is focused. */}\n <Row\n columns={5}\n glyph={`${isFocused ? '❯' : ' '} ${index + 1}.`}\n glyphTone={isFocused ? 'fg' : 'faint'}\n tone={option.danger ? 'red' : option.selected ? 'green' : 'fg'}\n data-focused={isFocused ? '' : undefined}>\n {option.checked !== undefined ? (\n <Ink tone={option.checked ? 'green' : 'faint'}>\n {MARKERS[option.marker ?? 'check'][option.checked ? 1 : 0]}{' '}\n </Ink>\n ) : null}\n <Ink bold={isFocused || option.selected}>{option.label}</Ink>\n </Row>\n </button>\n {option.description ? (\n <Row columns={5} tone='dim'>\n {option.description}\n </Row>\n ) : null}\n {option.detail ? <div className='term-detail'>{option.detail}</div> : null}\n </div>\n )\n })}\n </div>\n )\n}\n\n/**\n * A single-line text field in the terminal idiom: a caret and a rule, no box.\n * `Enter` commits, `Escape` backs out — the caller says what those mean.\n */\nexport function PromptInput({\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 <Row columns={5} glyph=' ›' glyphTone='dim'>\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='term-input'\n />\n </Row>\n )\n}\n\n/**\n * The question strip: one chip per question plus the submit step, with the\n * active one filled.\n *\n * It exists because the CLI asks **one question at a time**, and a form that\n * hides two of its three questions has to say so — otherwise answering the first\n * looks like finishing. The arrows are not controls, they are the legend for\n * `Tab`/`Shift+Tab`, which is what actually moves between them.\n */\nexport function TabStrip({\n tabs,\n active,\n onSelect,\n}: {\n /** `glyph` rather than a derived done/not-done mark: the submit step is always\n * a `✓` (it is the act of finishing, not a thing to answer), and deriving it\n * would make it a hollow box until every question was done. */\n tabs: { key: string; label: string; glyph: string }[]\n active: number\n onSelect: (index: number) => void\n}) {\n return (\n <Row glyph='←' glyphTone='faint'>\n {tabs.map((tab, index) => (\n <button\n key={tab.key}\n type='button'\n tabIndex={-1}\n onClick={() => onSelect(index)}\n className={cn('term-tab', index === active && 'term-tab-active')}>\n {tab.glyph} {tab.label}\n </button>\n ))}\n <Ink tone='faint'> →</Ink>\n </Row>\n )\n}\n\nexport { Blank }\n","import { useState } from 'react'\nimport type { PermissionRequest } from '@workerdeck/protocol'\nimport { toolInputPreview } from '../../lib/format.ts'\nimport { TerminalDiff, previewPatch } from './diff.tsx'\nimport { Choices, Hint, PromptInput, PromptTitle, Rule } from './prompt.tsx'\nimport { Blank, Row } from './row.tsx'\n\n/**\n * The approval, as the CLI draws it: a rule, what is being asked and about what,\n * the change itself, a dashed rule, the question, numbered answers, and the keys.\n *\n * Two things it does that the card version does not, both taken from the CLI:\n *\n * - **It shows the change, not the payload.** For a file edit, `old_string` and\n * `new_string` are a diff — so it renders one, rather than the JSON input a\n * reader would have to diff in their head. Without line numbers, necessarily:\n * the edit has not happened and this client has never read the file (see\n * {@link previewPatch}). Everything else falls back to the input.\n * - **The heading is the engine's own sentence.** `displayName` (\"Edit file\")\n * over `title`, never \"wants to use {tool}\" composed here — for codex an\n * approval is an *escalation after a sandbox refusal*, and the runner has\n * already written the sentence that says so.\n */\nexport interface TerminalPermissionPromptProps {\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}\n\nexport function TerminalPermissionPrompt({\n request,\n onApprove,\n onDeny,\n}: TerminalPermissionPromptProps) {\n const [focused, setFocused] = useState(0)\n const [denying, setDenying] = useState(false)\n const [reason, setReason] = useState('')\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 patch = previewPatch(request.input)\n const summary = toolInputPreview(request.input)\n const heading = request.displayName ?? request.title ?? 'Permission needed'\n // The path is the subject when there is one; otherwise the input preview says\n // what this is about, which for a Bash approval is the command itself.\n const subject = patch?.path ?? (summary || undefined)\n\n // \"Deny\" opens the reason field rather than acting, because a denial the agent\n // can learn from is the one worth defaulting to — an empty field still denies.\n const options = [\n { key: 'allow', label: 'Yes' },\n {\n key: 'deny',\n label: 'No, and tell the agent what to do differently',\n detail: denying ? (\n <PromptInput\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: 'No, and stop the turn', danger: true },\n ]\n\n return (\n <div\n data-slot='permission-prompt'\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n deny(false)\n }\n }}>\n <Rule />\n <PromptTitle title={heading} subject={subject} />\n <Blank />\n {patch ? (\n <>\n <TerminalDiff patch={patch} />\n <Blank />\n <Rule dashed />\n </>\n ) : request.description ? (\n <>\n <Row tone='dim'>{request.description}</Row>\n <Blank />\n </>\n ) : null}\n {request.decisionReason ? <Row tone='faint'>{request.decisionReason}</Row> : null}\n <Row>\n {request.title ?? `Do you want to proceed?`}\n </Row>\n <Choices\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 <Blank />\n <Hint>Enter to select · ↑/↓ to navigate · 1–3 to choose · Esc to cancel</Hint>\n </div>\n )\n}\n","import { useState } from 'react'\nimport type { PermissionRequest, UserQuestion } from '@workerdeck/protocol'\nimport { parseUserQuestions } from '../agent/QuestionPrompt.tsx'\nimport { Box, Choices, Hint, PromptInput, Rule, TabStrip, type Choice } from './prompt.tsx'\nimport { Blank, Ink, Row } from './row.tsx'\n\n/**\n * The AskUserQuestion form, in the CLI's shape: **one question at a time**,\n * behind a strip of chips, ending in a review step.\n *\n * The card version stacks every question on screen at once, which is right for a\n * dialog and wrong here for a reason worth stating: a terminal form is answered\n * with the keyboard, and a stacked form has no answer to \"where does `Tab` go\" —\n * it has as many focus targets as there are options across every question. One\n * question at a time makes the keys unambiguous (`↑↓` within, `Tab` between,\n * `Enter` to take), and it is why the chips exist: something has to say that\n * answering the first of three is not finishing.\n *\n * The review step is the other half of that. Answers given one screen at a time\n * are answers you cannot see together, so the last chip shows all of them and\n * asks once more before they go back to the model.\n */\n\ntype Selection = { labels: string[]; other: string; otherActive: boolean }\n\nconst EMPTY: Selection = { labels: [], other: '', otherActive: false }\n\n/** A question's answer: chosen label(s), comma-joined, with any free-text\n * \"Other\" appended — the shape the CLI's own UI puts 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 TerminalQuestionPromptProps {\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}\n\nexport function TerminalQuestionPrompt({\n request,\n onAnswer,\n onDismiss,\n}: TerminalQuestionPromptProps) {\n const questions = parseUserQuestions(request.input)\n const [selections, setSelections] = useState<Selection[]>(() => questions.map(() => EMPTY))\n const [cursors, setCursors] = useState<number[]>(() => questions.map(() => 0))\n // Tabs are the questions plus the review step, so `questions.length` is the\n // review — one index space, which is what makes Tab a single `+1`.\n const [tab, setTab] = useState(0)\n const [reviewCursor, setReviewCursor] = useState(0)\n\n const review = tab >= questions.length\n const selection = selections[tab] ?? EMPTY\n const answered = (index: number) => answerFor(selections[index] ?? EMPTY) !== ''\n const complete = questions.every((_, index) => answered(index))\n\n const update = (index: number, patch: Partial<Selection>) =>\n setSelections((prev) => prev.map((s, i) => (i === index ? { ...s, ...patch } : s)))\n\n // Functional, and it has to be: reading `selections` from the render closure\n // means two toggles in one tick both compute from the same base and the second\n // silently drops the first. A multi-select is exactly where that happens.\n const toggle = (index: number, label: string, multiSelect: boolean) => {\n setSelections((prev) =>\n prev.map((current, i) => {\n if (i !== index) return current\n if (!multiSelect) return { ...current, labels: [label], otherActive: false }\n return {\n ...current,\n labels: current.labels.includes(label)\n ? current.labels.filter((l) => l !== label)\n : [...current.labels, label],\n }\n }),\n )\n }\n\n const submit = () => {\n const answers: Record<string, string> = {}\n questions.forEach((question, index) => {\n answers[question.question] = answerFor(selections[index] ?? EMPTY)\n })\n onAnswer(request.id, { ...request.input, answers })\n }\n\n const dismiss = () => onDismiss(request.id, 'Question dismissed by user')\n\n const move = (delta: number) =>\n setTab((current) => Math.min(questions.length, Math.max(0, current + delta)))\n\n return (\n <div\n data-slot='question-prompt'\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n dismiss()\n return\n }\n if (event.key === 'Tab') {\n event.preventDefault()\n move(event.shiftKey ? -1 : 1)\n }\n }}>\n <Rule />\n <TabStrip\n tabs={[\n ...questions.map((question, index) => ({\n key: `q${index}`,\n label: question.header || `Question ${index + 1}`,\n glyph: answered(index) ? '▣' : '▢',\n })),\n { key: 'submit', label: 'Submit', glyph: '✓' },\n ]}\n active={tab}\n onSelect={setTab}\n />\n <Blank />\n {review ? (\n <ReviewStep\n questions={questions}\n answers={questions.map((_, index) => answerFor(selections[index] ?? EMPTY))}\n complete={complete}\n cursor={reviewCursor}\n onCursor={setReviewCursor}\n onSubmit={submit}\n onCancel={dismiss}\n />\n ) : (\n <QuestionStep\n // Keyed by question, and load-bearing: without it React reuses the\n // step across a Tab and the option list never re-arms its focus, so\n // the keyboard lands on nothing and the next `3` or `Esc` goes\n // nowhere. Remounting is also what re-runs the focus takeover.\n key={tab}\n question={questions[tab]!}\n selection={selection}\n cursor={cursors[tab] ?? 0}\n onCursor={(next) => setCursors((prev) => prev.map((c, i) => (i === tab ? next : c)))}\n onToggle={(label) => toggle(tab, label, questions[tab]!.multiSelect === true)}\n onOther={(patch) => update(tab, patch)}\n onAdvance={() => move(1)}\n onDismiss={dismiss}\n />\n )}\n <Blank />\n <Hint>\n Enter to select · ↑/↓ to navigate · Tab to switch questions · Esc to cancel\n </Hint>\n </div>\n )\n}\n\nfunction QuestionStep({\n question,\n selection,\n cursor,\n onCursor,\n onToggle,\n onOther,\n onAdvance,\n onDismiss,\n}: {\n question: UserQuestion\n selection: Selection\n cursor: number\n onCursor: (index: number) => void\n onToggle: (label: string) => void\n onOther: (patch: Partial<Selection>) => void\n onAdvance: () => void\n onDismiss: () => void\n}) {\n const multiSelect = question.multiSelect === true\n\n const options: Choice[] = [\n ...question.options.map((option, index): Choice => {\n const selected = selection.labels.includes(option.label)\n return {\n key: option.label,\n label: option.label,\n description: option.description,\n // Markers only where there is state to keep: a multi-select accumulates\n // and has to show what is in the set, a one-of answers itself by being\n // chosen and moves on — a column of empty radios in front of it would be\n // three characters of nothing on every row. The CLI draws it the same way.\n checked: multiSelect ? selected : undefined,\n marker: 'check',\n selected: !multiSelect && selected,\n // A preview shows on **focus**, not only on selection: walking a list\n // with the arrow keys, \"what does this one look like\" is the question\n // the cursor is asking.\n detail:\n option.preview && cursor === index ? (\n <Box>\n {option.preview.split('\\n').map((line, row) => (\n <Row key={row} columns={0} tone='dim'>\n {line || ' '}\n </Row>\n ))}\n </Box>\n ) : undefined,\n }\n }),\n {\n key: '__other',\n label: 'Other…',\n // Always markered, in both kinds: this row is a *mode* (the field is open\n // or it isn't), which the reader cannot see any other way.\n checked: selection.otherActive,\n marker: 'check' as const,\n detail: selection.otherActive ? (\n <PromptInput\n value={selection.other}\n onChange={(value) => onOther({ other: value })}\n onSubmit={onAdvance}\n onCancel={() => onOther({ otherActive: false, other: '' })}\n placeholder='Type your own answer'\n />\n ) : undefined,\n },\n // Multi-select has no natural end — every toggle leaves the list where it\n // was — so it needs a row that says \"done with this one\". A one-of answers\n // itself by being chosen and advances on its own.\n ...(multiSelect ? [{ key: '__next', label: 'Submit' }] : []),\n { key: '__chat', label: 'Chat about this' },\n ]\n\n return (\n <>\n <Row bold tone='bright'>\n {question.question}\n </Row>\n <Blank />\n <Choices\n label={question.question}\n options={options}\n focused={cursor}\n active={!selection.otherActive}\n onFocus={onCursor}\n onChoose={(index) => {\n if (index < question.options.length) {\n onToggle(question.options[index]!.label)\n // A one-of is finished the moment it is picked; making the reader\n // press Tab as well would be a keystroke that answers nothing.\n if (!multiSelect) onAdvance()\n return\n }\n if (index === question.options.length) {\n onOther({ otherActive: !selection.otherActive })\n return\n }\n if (multiSelect && index === question.options.length + 1) {\n onAdvance()\n return\n }\n onDismiss()\n }}\n />\n </>\n )\n}\n\n/** The last chip: every answer together, and one more chance to change it. */\nfunction ReviewStep({\n questions,\n answers,\n complete,\n cursor,\n onCursor,\n onSubmit,\n onCancel,\n}: {\n questions: UserQuestion[]\n answers: string[]\n complete: boolean\n cursor: number\n onCursor: (index: number) => void\n onSubmit: () => void\n onCancel: () => void\n}) {\n return (\n <>\n <Row bold tone='bright'>\n Review your answers\n </Row>\n <Blank />\n {questions.map((question, index) => (\n <div key={index}>\n <Row glyph='●' glyphTone='dim'>\n {question.question}\n </Row>\n <Row indent={1} glyph='→' glyphTone='green'>\n <Ink tone={answers[index] ? 'green' : 'faint'}>\n {answers[index] || 'not answered'}\n </Ink>\n </Row>\n </div>\n ))}\n <Blank />\n <Row>{complete ? 'Ready to submit your answers?' : 'Some questions are unanswered.'}</Row>\n <Choices\n label='Submit answers'\n options={[\n // Offered even when incomplete: an unanswered question is a legitimate\n // answer to give, and the model is told which ones were skipped. What\n // it must not do is *look* finished, which is what the row above says.\n { key: 'submit', label: complete ? 'Submit answers' : 'Submit anyway' },\n { key: 'cancel', label: 'Cancel', danger: true },\n ]}\n focused={cursor}\n onFocus={onCursor}\n onChoose={(index) => (index === 0 ? onSubmit() : onCancel())}\n />\n </>\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 { meterColorClass } from '../../lib/status.ts'\nimport { STATUS_META } from './status.ts'\nimport { useTranscriptVariant } from './transcript-variant.tsx'\n\nexport interface StatusBarProps {\n state: TranscriptState\n /**\n * Plan windows to draw, when they should not be the session's own. The panel\n * hands over the gateway's per-profile state merged over this transcript's\n * reading (`mergeUsage`): a session's own `rate_limit` readings arrive only at\n * a turn's edges, so one idle since yesterday would otherwise render\n * yesterday's number as current. Absent = `state.rateLimits`, which is what a\n * bar composed by hand outside the panel gets.\n */\n rateLimits?: Record<string, RateLimitInfo>\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 /**\n * The session's own controls (model, permission mode), for\n * `controlsSurface: 'status'`. They sit at the end of the readings cluster —\n * status, context, usage, then what you can *change* — rather than out by the\n * actions menu, because they belong with the session facts they act on.\n */\n controls?: ReactNode\n /** Trailing slot — the session-actions menu, at the bar's trailing edge. */\n actions?: ReactNode\n /** Which edge the bar sits on, so its separating rule goes on the other side.\n * Placement is the panel's decision; this only styles it. */\n placement?: 'top' | 'bottom'\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\n/** The shared ramp, aliased for the two call sites below — the thresholds live\n * in `lib/status.ts` beside the severity they mirror. */\nconst utilizationColor = (pct: number) => meterColorClass(pct)\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 {/* Inline, not `inline-flex`: a flex container contributes its FIRST\n item's baseline, and the first item here is the ring — so a flex\n version hands the row a 13px circle's baseline instead of the number's,\n and every reading beside it sits on a different line. Inline text has\n only one baseline to give. `align-middle` centres the ring on the\n x-height, which is where it looked right anyway. */}\n <span\n className={cn(\n 'cursor-default font-mono text-label whitespace-nowrap',\n info.status === 'rejected' ? 'text-danger' : utilizationColor(pct ?? 0),\n )}>\n <ProgressRing value={pct ?? 0} className='mr-1 inline-block align-middle' />\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 // `leading-4` matches the label metrics inside: a button's own line box is\n // the page's 24px one, and the extra strut was padding the bar by 2.5px\n // that nothing was using.\n // No horizontal padding: the bar's own 6px is the inset, and a slot that\n // added its own would start the first reading 10px in. The hover surface\n // hugs the text instead, which is what a status line's items do anyway.\n // `leading-4` matches the label metrics inside — a button's own line box\n // is the page's 24px one, and the strut padded the bar with nothing.\n className='rounded-md py-0.5 leading-4 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 rateLimits,\n connected,\n connection,\n onOpenStatus,\n onOpenContext,\n onOpenUsage,\n controls,\n actions,\n placement = 'top',\n className,\n}: StatusBarProps) {\n const meta = STATUS_META[state.status]\n const now = useNow()\n const windows = rateLimits ?? state.rateLimits\n const session = windows?.five_hour\n const weekly = windows?.seven_day\n const link: ConnectionState = connection ?? (connected === false ? 'reconnecting' : 'live')\n const terminal = useTranscriptVariant() === 'terminal'\n return (\n <div\n data-slot='status-bar'\n className={cn(\n // Baselines, not boxes. `items-center` centres each child's *box*, and\n // this bar's children are boxes of different heights for reasons that\n // have nothing to do with their text — a badge's pill padding, a ring\n // beside a number, a select's border and chevron. Centring those lands\n // their text on four slightly different lines. A flex item's baseline is\n // its first line's baseline, so aligning on it puts every reading on one\n // line by construction, whatever is drawn around it.\n // One explicit height, shared with the docked composer above it (see\n // `Composer.tsx`) — the two strips along the foot of the panel read as\n // one piece of chrome, and a pixel of drift between them shows.\n 'flex h-[38px] items-baseline gap-2 border-border bg-surface p-1.5',\n // The rule goes between the bar and the content, so which edge it sits\n // on follows the placement — except under the terminal theme at the\n // foot, where the composer directly above already closes itself with a\n // rule of its own. Two adjacent 1px rules is a 2px rule with a seam in\n // it, and it is off the line grid besides.\n placement === 'bottom' ? (terminal ? undefined : 'border-t') : 'border-b',\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 // `items-baseline` so the badge answers the row with its label's\n // baseline rather than its pill's box; the dot and the spinner keep\n // their own centring.\n <Badge variant={meta.variant} dot={!meta.busy} className='items-baseline'>\n {meta.busy ? <Spinner className='size-3 self-center text-current' /> : null}\n {meta.label}\n </Badge>\n ) : (\n <Badge\n variant={link === 'offline' ? 'danger' : 'warning'}\n dot={false}\n className='items-baseline'>\n {link === 'offline' ? (\n <WifiOff className='size-3 self-center text-current' />\n ) : (\n <RefreshCw className='size-3 animate-spin self-center 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-baseline 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 {controls}\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\n/** Scroll container that stays pinned to the bottom while streaming, unless the user\n * scrolls up — plus a floating scroll-to-bottom button.\n *\n * **Nothing here animates its scroll position.** `initial` and `resize` are both\n * `'instant'` and are no longer configurable: the follow spring's job is to\n * *stay* at the bottom, not to travel there, and every travel a reader has ever\n * complained about on this surface was an animation we asked for. The `resize`\n * prop used to exist so a caller could pick, which required the caller to know\n * whether the transcript was streaming or replaying — a latch's worth of\n * machinery to decide something that has one right answer. */\nexport function Conversation({ className, children, ...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='instant'\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('instant')}>\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'\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 name = item.path.split('/').pop() || item.path\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 { cn } from '../../lib/utils.ts'\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 * Unused by the card treatment; kept so the `cards` and `terminal` loaders\n * take the same call site in `Transcript`. */\n startedAt?: number\n /** Context tokens in play. Same story as `startedAt`. */\n tokens?: number\n className?: string\n}\n\n/**\n * \"The agent is working and hasn't produced output yet.\"\n *\n * Three dots pulsing in CSS — the chat convention, for the `cards` variant. The\n * terminal theme has its own working line (`terminal/items.tsx`'s `WorkingRow`,\n * with the mark's pulse in the gutter and the run's readings beside the verb),\n * because there it is a *row of the transcript* rather than a spinner floating\n * over one.\n */\nexport function Loader({ label, className }: LoaderProps) {\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","import type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\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 in a bubble, assistant content is flat and\n * full-width. Both are **left-aligned**: the transcript is a log read top to\n * bottom, and an editor-shaped host (a full-width session view beside a sessions\n * rail) has no right edge to anchor to — a bubble drifting right in a 1600px\n * column separates a prompt from the reply it produced. The bubble alone is\n * enough to say who spoke.\n *\n * The terminal theme draws none of this: it is its own renderer\n * (`components/terminal/`) and mounts instead of these rows.\n */\nexport function Message({ from, className, children, ...props }: MessageProps) {\n return (\n <div\n data-slot='message'\n data-from={from}\n className={cn('flex w-full flex-col items-start gap-1', className)}\n {...props}>\n {children}\n </div>\n )\n}\n\nexport function MessageContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='message-content'\n className={cn(\n 'min-w-0 text-body-sm leading-6 text-fg-1',\n // Bubble treatment only within a user message row.\n // The tail points bottom-*left* now that the bubble is left-aligned.\n 'in-data-[from=user]:max-w-[85%] in-data-[from=user]:rounded-lg in-data-[from=user]:rounded-bl-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 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 { memo } from 'react'\nimport { Streamdown } from 'streamdown'\nimport { cn } from '../../lib/utils.ts'\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 return (\n <Streamdown\n mode={streaming ? 'streaming' : 'static'}\n parseIncompleteMarkdown={streaming}\n shikiTheme={['github-light', 'github-dark']}\n className={cn('size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0', className)}>\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, useState } from 'react'\nimport { Brain, ChevronDown } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Response } from './Response.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 [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='flex items-center gap-1.5 text-label text-fg-3 transition-colors outline-none disabled:cursor-default hover:text-fg-1 disabled:hover:text-fg-3'>\n <Brain className='size-3.5' />\n <span>{isStreaming ? 'Thinking…' : 'Thought process'}</span>\n {hasText ? (\n <ChevronDown className={cn('size-3.5 transition-transform', open && 'rotate-180')} />\n ) : null}\n </button>\n {open && hasText ? (\n <div\n className='mt-2 border-l-2 border-border pl-3 text-body-sm text-fg-3 [&_*]:text-fg-3'>\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 { toolIcon } from '../../lib/tool-icon.ts'\nimport { useToolResultFetcher } from './tool-result-fetch.tsx'\nimport { useToolResultImageSrc } from './tool-result-image.tsx'\n// From the terminal folder, which is where the box's one spelling lives: the\n// height calculator is the reason it is a constant at all, and a second copy\n// here would be a second thing to keep in step for no gain. Cards has no\n// calculator — this frame is about not reflowing a virtualized list when the\n// bytes land, which is a claim both themes make.\nimport { IMAGE_UNAVAILABLE, imagePlaceholder } from '../terminal/image-box.ts'\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/**\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 [open, setOpen] = useState(false)\n const [fullResult, setFullResult] = useState(false)\n const [fetching, setFetching] = useState(false)\n const fetchResult = useToolResultFetcher()\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 clipped = !fullResult && resultText.length > RESULT_PREVIEW_CHARS\n const shownResult = clipped ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText\n // The replay sent a head (see protocol's `ToolResultBlock.truncated`): what is\n // on screen is not merely clipped, it is all this client was given. The press\n // therefore fetches rather than only lifting the clip, and the count has to be\n // the real one — `resultText.length` here is the head's.\n const headOnly = item.result?.truncated === true\n const totalChars = item.result?.totalChars ?? resultText.length\n\n const details = open ? (\n <div className='flex flex-col gap-2 border-t border-border p-2.5'>\n <PlainPayload\n code={JSON.stringify(item.input, null, 2)}\n language='json'\n label='Parameters'\n />\n {item.logs?.length ? (\n <PlainPayload code={item.logs.join('\\n')} label='Logs'/>\n ) : null}\n {item.result !== undefined ? (\n <div>\n <PlainPayload\n code={shownResult || '(empty result)'}\n language={resultLanguage(item)}\n label={isError ? 'Error' : 'Result'}\n className={cn(isError && 'border-danger/40 [&_pre]:text-danger')}\n />\n {fetching ? (\n <p className='mt-1 text-label text-fg-3'>\n Fetching {totalChars.toLocaleString()} chars…\n </p>\n ) : clipped || headOnly ? (\n <button\n type='button'\n className='mt-1 text-label text-fg-3 underline-offset-2 hover:underline'\n onClick={() => {\n setFullResult(true)\n if (!headOnly) return\n setFetching(true)\n void fetchResult(item.id).finally(() => setFetching(false))\n }}>\n Show all {totalChars.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} /> : null\n\n // Beside the host-path picture above, never instead of it: that one is a file\n // the engine wrote, read back through `/produced` or `/fs`; these are image\n // parts of the result itself, addressed by `(seq, toolUseId, part)`. Different\n // store, different route, and a call can plausibly have both.\n const resultImages = item.result?.images?.length ? (\n <div className='flex flex-col gap-2 border-t border-border p-2.5'>\n {item.result.images.map((ref) => (\n <ResultImage key={ref.partIndex} toolUseId={item.id} image={ref} />\n ))}\n </div>\n ) : null\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 {resultImages}\n {details}\n </div>\n )\n}\n\n/** The framed card the `cards` transcript expands into. Unhighlighted by\n * design: it is structured data in a panel, not a file. */\nfunction PlainPayload({\n code,\n label,\n className,\n}: {\n code: string\n label: string\n language?: string\n className?: string\n}) {\n return <CodeBlock code={code} label={label} variant='panel' className={className} />\n}\n\n/** One image part of a tool result, as the reducer holds it. */\ntype ToolResultImage = NonNullable<NonNullable<ToolCallItem['result']>['images']>[number]\n\n/**\n * An image part of the result, fetched by reference.\n *\n * The frame is a **fixed height in all three states** — placeholder, picture,\n * failure — which is the one rule this shares with the terminal theme and the\n * only reason it is worth a component: the transcript is virtualized in both,\n * and a box that appears when the bytes land shoves every row below it down\n * while the reader is mid-sentence. Unlike `HostImage`, a failure here is *said*\n * rather than swallowed: there is no host path in the result text to fall back\n * on, so silence would be a blank frame with no account of itself.\n */\nfunction ResultImage({ toolUseId, image }: { toolUseId: string; image: ToolResultImage }) {\n const { src, failed } = useToolResultImageSrc({ toolUseId, ...image })\n return (\n <div className='flex h-60 items-start overflow-hidden rounded-md border border-border bg-surface-hover'>\n {src ? (\n <img src={src} alt={imagePlaceholder(image)} className='h-full max-w-full object-contain' />\n ) : (\n <span className='p-2 text-label text-fg-4'>\n {failed ? IMAGE_UNAVAILABLE : imagePlaceholder(image)}\n </span>\n )}\n </div>\n )\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}: {\n path: string\n load: (path: string) => Promise<string | undefined>\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='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 type { FilePatch, PatchHunk } from '@workerdeck/protocol'\nimport type { TranscriptItem } from '@workerdeck/react'\nimport {\n formatBytes,\n formatCost,\n formatDuration,\n toolInputPreview,\n} from '../../lib/format.ts'\nimport { taskChildItems, type TerminalBlock, type ToolCallItem } from './blocks.ts'\nimport { IMAGE_BOX_LINES } from './image-box.ts'\nimport { collapsedResult } from './result-preview.ts'\nimport { runSummary, taskSummary } from './tool-run.ts'\n\n/**\n * The terminal theme's row-height calculator.\n *\n * Computes, from a {@link TranscriptItem} and the cell metrics alone — no DOM —\n * the pixel height the terminal renderer will draw it at. This exists because\n * the theme makes it possible: one line height, one cell, monospace, ligatures\n * off. It is a *terminal* capability, deliberately homed here rather than in a\n * generic location — the cards variant has padding scales, borders and a\n * proportional face, and nothing in this file transfers to it.\n *\n * The consumer is the virtualizer's `estimateSize` (see `agent/Transcript.tsx`):\n * a computed height governs a row only until the row mounts and is measured, so\n * the contract is \"almost always exact, and honest when it cannot be\". Honesty\n * is the `exact` flag on {@link ComputedHeight}: `false` marks content whose\n * advance this model cannot know — emoji and CJK render from fallback faces\n * whose advances are not whole cells.\n * Measured against the real renderer in the dev playground\n * (`dev/height-audit.ts`): every unflagged row was pixel-exact across fixtures,\n * widths and cell sizes — the audit is this file's regression gate.\n *\n * Two invariants keep the model small, and both are load-bearing:\n *\n * - **Only the default state is ever computed.** A tool row's expansion is\n * component-local state that resets when the row unmounts, so an off-screen\n * row is always collapsed — and an expanded row is by definition mounted,\n * which means the virtualizer has its real measurement. There is no\n * open/expanded branch here on purpose.\n *\n * **This is a decided, permanent divergence from iOS**, not a gap anyone\n * should close. There (`apps/ios/WorkerDeckKit/.../TerminalExpansion.swift`)\n * nothing self-measures — a `UICollectionViewLayout` takes every frame from\n * the height book — so expansion has to be an *input to the planner*, and a\n * height the book does not know about is a frame the layout gets wrong. That\n * inversion is what lets the iOS scrubber be expansion-aware (a band over the\n * region you opened; a failed member of an *open* run marking on its own\n * line) and it is the one thing this client's rail cannot do. Lifting\n * expansion to shared state here to match would buy one rail feature and cost\n * this invariant — the central simplification of the whole calculator — so\n * the two clients share the *rule* and differ in how much of it each can see.\n *\n * And the gap is entailed by **where the state can live**, not by how it is\n * plumbed: a paint-only registry would preserve this invariant, but web\n * expansion exists only while a row is *mounted*, so the rail it fed could\n * only ever band the rows currently on screen — the one place an overview\n * rail is useless, because the yellow wash is already visible there.\n * Said here as well as in the Swift, because this is the file where someone\n * would go looking to \"fix\" it.\n * - **Mutation is object replacement.** The transcript reducer never mutates an\n * item in place — streaming text, a result arriving, a patch attaching each\n * produce a new object — which is what lets {@link HeightEpoch}'s cache key on\n * identity and never go stale.\n *\n * The wrap model mirrors `.term-body` (`white-space: pre-wrap` +\n * `overflow-wrap: break-word` in a `minmax(0,1fr)` grid column): greedy fill,\n * breaks at preserved spaces (which hang at the line end rather than wrapping),\n * after hyphens/dashes/`?` not followed by a digit (verified against Chrome's\n * actual break points in long URLs), and between CJK characters; a token wider\n * than the whole column moves to its own line first and then breaks per cell —\n * which is exactly where `break-word` differs from `anywhere`.\n */\n\n/** The cell everything is measured in. `ch` is the advance of `0` in px —\n * *measured* off the live surface via {@link measureCh}, never derived from the\n * font size (7.83px at 13px JetBrains Mono, not 13 × 0.6). */\nexport type CellMetrics = {\n /** Content-box width of the virtual row wrapper, px. */\n width: number\n /** Advance of `0`, px. */\n ch: number\n /** `--term-line`, px. */\n line: number\n}\n\nexport type ComputedHeight = {\n px: number\n /** False when the content contains glyphs or structures whose rendered size\n * this model cannot know — treat the row as an estimate, corrected the\n * moment it mounts and measures. */\n exact: boolean\n}\n\n/* ── The epoch ─────────────────────────────────────────────────────────────\n *\n * One cache generation. **Owned by the transcript shell** (`TranscriptRows` in\n * `agent/Transcript.tsx` holds exactly one, in React state): it is created from\n * the first real measurement of the mounted surface and *replaced wholesale*\n * whenever width, `ch` or line change — a computed height is only meaningful\n * against the metrics it was computed for, so partial invalidation is a bug\n * factory and a new WeakMap is free. Within an epoch the cache keys on item\n * object identity, which the reducer's replace-on-mutation discipline turns\n * into automatic invalidation: a streamed delta is a new object and simply\n * misses. Run blocks are not cached — the folded array is rebuilt every\n * render, so its identity is worthless as a key, and a collapsed run is one\n * `wrapOne` over a short string (~2µs).\n */\nexport type HeightEpoch = CellMetrics & {\n cache: WeakMap<TranscriptItem, ComputedHeight>\n}\n\nexport function createHeightEpoch(width: number, ch: number, line: number): HeightEpoch {\n return { width, ch, line, cache: new WeakMap() }\n}\n\n/** A virtual row's computed height under `epoch` — the `estimateSize` feed.\n * The inter-row gap is the *pair's* business (`gapBefore`), not the row's, so\n * it is added by the caller. */\nexport function estimateBlockPx(block: TerminalBlock, epoch: HeightEpoch): number {\n // Only item blocks cache. A run's array is rebuilt every render, so its\n // identity is worthless as a key — and a task block must not key on its\n // `task` item either: children arrive without the call object changing (the\n // reducer replaces the *child*), so a height cached against the task would\n // survive exactly the mutation that changes the summary line. Both are one\n // `wrapOne` over a short string (~2µs), so neither needs the cache.\n if (!('item' in block)) return blockHeight(block, epoch).px\n const hit = epoch.cache.get(block.item)\n if (hit) return hit.px\n const computed = itemHeight(block.item, epoch)\n epoch.cache.set(block.item, computed)\n return computed.px\n}\n\n/**\n * How many lines of a sub-agent's brief the collapsed row shows.\n *\n * Four, and the number is a judgement rather than a measurement: a brief runs\n * to thousands of characters, and an uncapped one buries the work it was asking\n * for under its own instructions. Four is enough to recognise the task and\n * short enough that the agent's first line stays on screen beside it.\n */\nexport const BRIEF_LINES = 4\n\n/**\n * The collapsed brief row's height — the frame's first row, which is synthetic\n * and so cannot go through {@link estimateBlockPx}.\n *\n * Clipped, so it is `min(wrapped, BRIEF_LINES)` lines plus the row the header\n * occupies. Expanding is local state on a *mounted* row, which the virtualizer\n * re-measures; what has to be right before it mounts is the collapsed height,\n * exactly as a task row is always collapsed when unmounted for the same reason.\n */\nexport function briefPx(text: string, m: CellMetrics): number {\n const cols = Math.max(1, Math.floor(m.width / m.ch + EPS))\n return (Math.min(textLines(text, cols).lines, BRIEF_LINES) + 1) * m.line\n}\n\n/** Measure the advance of `0` on the live surface. A DOM read — call it from\n * the epoch's measurement pass (an effect / ResizeObserver callback), never\n * from render. The probe is absolutely positioned, so it contributes no layout\n * and cannot re-trigger the observer that called it. */\nexport function measureCh(surface: HTMLElement): number {\n const probe = document.createElement('span')\n probe.textContent = '0'.repeat(200)\n probe.style.position = 'absolute'\n probe.style.visibility = 'hidden'\n probe.style.whiteSpace = 'pre'\n surface.appendChild(probe)\n const width = probe.getBoundingClientRect().width / 200\n probe.remove()\n return width\n}\n\n/* ── Cell counting ─────────────────────────────────────────────────────────── */\n\nconst segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })\n\nconst WIDE_RANGES: [number, number][] = [\n [0x1100, 0x115f], // Hangul Jamo\n [0x2e80, 0x303e], // CJK radicals, Kangxi, CJK symbols/punctuation\n [0x3041, 0x33ff], // Hiragana … CJK compatibility\n [0x3400, 0x4dbf],\n [0x4e00, 0x9fff],\n [0xa000, 0xa4cf],\n [0xac00, 0xd7a3], // Hangul syllables\n [0xf900, 0xfaff],\n [0xfe30, 0xfe4f],\n [0xff00, 0xff60], // fullwidth forms\n [0xffe0, 0xffe6],\n [0x20000, 0x3fffd],\n]\n\nconst isWide = (cp: number): boolean => WIDE_RANGES.some(([lo, hi]) => cp >= lo && cp <= hi)\n\nconst PICTOGRAPHIC = /\\p{Extended_Pictographic}/u\n\n/** One grapheme cluster's advance, in cells. Wide and pictographic clusters are\n * counted as 2 but flagged: they render from a fallback face whose advance is\n * not a whole number of cells, and the flag is what keeps the model honest. */\nfunction clusterCells(cluster: string): { w: number; exact: boolean } {\n if (PICTOGRAPHIC.test(cluster) || cluster.includes('‍') || cluster.includes('️'))\n return { w: 2, exact: false }\n const cp = cluster.codePointAt(0) ?? 0\n if (isWide(cp)) return { w: 2, exact: false }\n if (cp < 0x20) return { w: 0, exact: true }\n return { w: 1, exact: true }\n}\n\n/* ── The wrap model ────────────────────────────────────────────────────────── */\n\ntype Token = { kind: 'word' | 'space'; w: number; exact: boolean }\n\n/** `tab-size: 2` on the surface. */\nconst TAB_SIZE = 2\n\n/** Break *after* these when the next character is not a digit. The digit guard\n * keeps `protocol-0.16.0` together; `?` is in the set because Chrome breaks\n * long URLs after it (verified against real break rects). */\nconst BREAK_AFTER = new Set(['-', '–', '—', '?'])\n\n/** Printable ASCII, tab excluded: every character is one cell and one grapheme,\n * so the segmenter has nothing to tell us. This is nearly every line a\n * transcript holds, and the segmenter was the calculator's whole CPU bill —\n * ~1s of a 30s scroll sweep on the 4k-item perf fixture, all of it spent\n * segmenting text that `charCodeAt` classifies for free. */\nconst PLAIN_ASCII = /^[\\x20-\\x7e]*$/\n\n/** The ASCII half of {@link tokenize}: same tokens, same break rules ('-' and\n * '?' break unless a digit follows; '–'/'—' are not ASCII), no segmenter and\n * no per-grapheme allocation. Must stay semantically identical to the general\n * path — the height audit runs both, via content that takes each. */\nfunction tokenizeAscii(line: string): Token[] {\n const tokens: Token[] = []\n let wordW = 0\n const flushWord = () => {\n if (wordW > 0) tokens.push({ kind: 'word', w: wordW, exact: true })\n wordW = 0\n }\n for (let i = 0; i < line.length; i++) {\n const code = line.charCodeAt(i)\n if (code === 0x20) {\n flushWord()\n const last = tokens[tokens.length - 1]\n if (last?.kind === 'space') last.w += 1\n else tokens.push({ kind: 'space', w: 1, exact: true })\n continue\n }\n wordW += 1\n if (code === 0x2d /* - */ || code === 0x3f /* ? */) {\n const next = line.charCodeAt(i + 1)\n if (!(next >= 0x30 && next <= 0x39)) flushWord()\n }\n }\n flushWord()\n return tokens\n}\n\n/**\n * Tokenize one hard line into wrap units. Tabs advance to the next 2-cell stop\n * measured from the hard line's start — position-dependent after a soft wrap in\n * principle, but content with tabs beyond the first wrap point is vanishingly\n * rare and the error is bounded by one tab stop.\n */\nfunction tokenize(line: string): Token[] {\n if (PLAIN_ASCII.test(line)) return tokenizeAscii(line)\n const tokens: Token[] = []\n let word = { w: 0, exact: true }\n let col = 0\n const flushWord = () => {\n if (word.w > 0) tokens.push({ kind: 'word', w: word.w, exact: word.exact })\n word = { w: 0, exact: true }\n }\n const segments = [...segmenter.segment(line)].map((s) => s.segment)\n for (const [index, segment] of segments.entries()) {\n if (segment === ' ') {\n flushWord()\n const last = tokens.at(-1)\n if (last?.kind === 'space') last.w += 1\n else tokens.push({ kind: 'space', w: 1, exact: true })\n col += 1\n continue\n }\n if (segment === '\\t') {\n flushWord()\n const advance = TAB_SIZE - (col % TAB_SIZE) || TAB_SIZE\n const last = tokens.at(-1)\n if (last?.kind === 'space') last.w += advance\n else tokens.push({ kind: 'space', w: advance, exact: true })\n col += advance\n continue\n }\n const { w, exact } = clusterCells(segment)\n const cp = segment.codePointAt(0) ?? 0\n if (isWide(cp) || PICTOGRAPHIC.test(segment)) {\n // Each wide cluster is its own token — a break may fall between any two.\n flushWord()\n tokens.push({ kind: 'word', w, exact })\n col += w\n continue\n }\n word.w += w\n word.exact = word.exact && exact\n col += w\n if (BREAK_AFTER.has(segment) && !/^\\d/.test(segments[index + 1] ?? '')) {\n flushWord()\n }\n }\n flushWord()\n return tokens\n}\n\n/** Visual lines one hard line occupies at `cols` columns. */\nfunction wrapOne(line: string, cols: number): { lines: number; exact: boolean } {\n if (cols <= 0) return { lines: 1, exact: false }\n const tokens = tokenize(line)\n let lines = 1\n let pos = 0\n let exact = true\n for (const token of tokens) {\n exact = exact && token.exact\n if (token.kind === 'space') {\n // Preserved spaces hang at the line end (CSS Text 3): they may overflow\n // without forcing a wrap, and the next word starts the next line.\n pos += token.w\n continue\n }\n if (pos + token.w <= cols) {\n pos += token.w\n continue\n }\n if (token.w <= cols) {\n lines += 1\n pos = token.w\n continue\n }\n // break-word: the token first moves to its own line, then fills whole lines.\n if (pos > 0) lines += 1\n const full = Math.ceil(token.w / cols)\n lines += full - 1\n pos = token.w - (full - 1) * cols\n }\n return { lines, exact }\n}\n\n/** Visual lines of a (possibly multi-hard-line) text at `cols` columns.\n * Exported for the dev audit; also the one genuinely pure piece a unit test\n * could pin, should `ui` ever grow a runner. */\nexport function textLines(text: string, cols: number): { lines: number; exact: boolean } {\n let lines = 0\n let exact = true\n for (const hard of text.split('\\n')) {\n const r = wrapOne(hard, cols)\n lines += Math.max(1, r.lines)\n exact = exact && r.exact\n }\n return { lines: Math.max(1, lines), exact }\n}\n\n/* ── Rows ──────────────────────────────────────────────────────────────────── */\n\n/** Layout rounds to 1/64px; the epsilon keeps a body width that is exactly\n * N × ch from flooring to N−1. */\nconst EPS = 1e-4\n\ntype Acc = { px: number; exact: boolean }\n\nconst add = (a: Acc, b: Acc): Acc => ({ px: a.px + b.px, exact: a.exact && b.exact })\n\n/**\n * One `Row`: `indentCells` of padding (already resolved against the row's own\n * `--term-cell` — a `columns={3} indent={1}` row loses 3ch to the indent *and*\n * 3ch to the gutter, because the indent padding reads the same variable the\n * override sets), `gutterCells` of gutter, the body wrapping in what is left.\n * `extraPx` is non-cell chrome around the row (the nested-run border+padding).\n */\nfunction rowH(\n text: string,\n m: CellMetrics,\n { indentCells = 0, gutterCells = 2, extraPx = 0 } = {},\n): Acc {\n const bodyPx = m.width - extraPx - (indentCells + gutterCells) * m.ch\n const cols = Math.floor(bodyPx / m.ch + EPS)\n const { lines, exact } = textLines(text, cols)\n return { px: lines * m.line, exact }\n}\n\n/* ── Markdown ──────────────────────────────────────────────────────────────── */\n\n/** Rendered text of an inline run — markers stripped the way the component map\n * renders them (`**bold**` is 4 chars narrower on screen than in source).\n * Approximate: reference links, raw HTML and nested emphasis edge cases are\n * not modelled. */\nfunction stripInline(s: string): string {\n return s\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/``([^`]+)``/g, '$1')\n .replace(/`([^`]+)`/g, '$1')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n .replace(/__([^_]+)__/g, '$1')\n .replace(/\\*([^*\\s][^*]*)\\*/g, '$1')\n .replace(/(^|\\W)_([^_]+)_(?=\\W|$)/g, '$1$2')\n .replace(/\\\\([\\\\`*_{}[\\]()#+.!-])/g, '$1')\n}\n\n/**\n * Text blocks carry **rendered lines**, resolved by CommonMark's own break\n * rule: a source line ending in two spaces (or a backslash) is a hard break\n * and renders as `<br>`; any other newline is soft and collapses to a space\n * under `white-space: normal`. Both halves are load-bearing and each was the\n * whole model once. Join-always missed the hard breaks — models write poems\n * with trailing double-spaces, and a 350-line one estimated ~115 lines short\n * (a phantom scrollbar tail, scrubber marks resizing as rows mounted).\n * Break-always missed the soft ones and overestimated the same stanza written\n * without them by four lines. The `markdown` fixture carries one poem of each\n * as the audit's guard.\n */\ntype MdBlock =\n | { t: 'p'; lines: string[] }\n | { t: 'h'; text: string }\n | { t: 'fence'; code: string }\n | { t: 'list'; items: { lines: string[]; gutter: number; indent: number }[] }\n | { t: 'quote'; paras: string[][] }\n /** One line per row, unconditionally — see the `table` case in\n * {@link markdownHeight}. */\n | { t: 'table'; rows: number }\n | { t: 'hr' }\n\n/** Source lines of one paragraph-ish run → the lines the renderer draws: hard\n * breaks (trailing double space or backslash) split, soft newlines join with a\n * space. The break marker itself never renders and is stripped. */\nfunction hardLines(source: string[]): string[] {\n const out: string[] = []\n let current: string[] = []\n for (const raw of source) {\n const hard = /(?:\\s{2}|\\\\)$/.test(raw)\n current.push(stripInline(raw.replace(/(?:\\s+|\\\\)$/, '')))\n if (hard) {\n out.push(current.join(' '))\n current = []\n }\n }\n if (current.length) out.push(current.join(' '))\n return out\n}\n\n/** A line-based CommonMark-ish block parser — just enough structure to mirror\n * what Streamdown + the terminal component map emit for transcript content. */\nfunction parseBlocks(md: string): MdBlock[] {\n const lines = md.split('\\n')\n const blocks: MdBlock[] = []\n let i = 0\n const isBlank = (s: string | undefined) => s !== undefined && s.trim() === ''\n while (i < lines.length) {\n const line = lines[i]!\n if (line.trim() === '') {\n i += 1\n continue\n }\n if (line.startsWith('```')) {\n const code: string[] = []\n i += 1\n while (i < lines.length && !lines[i]!.startsWith('```')) {\n code.push(lines[i]!)\n i += 1\n }\n i += 1 // closing fence (or EOF, mid-stream)\n blocks.push({ t: 'fence', code: code.join('\\n') })\n continue\n }\n const h = /^(#{1,6})\\s+(.*)$/.exec(line)\n if (h) {\n blocks.push({ t: 'h', text: stripInline(h[2]!) })\n i += 1\n continue\n }\n if (/^(-{3,}|\\*{3,}|_{3,})\\s*$/.test(line)) {\n blocks.push({ t: 'hr' })\n i += 1\n continue\n }\n if (/^>\\s?/.test(line)) {\n const raw: string[] = []\n while (i < lines.length && /^>\\s?/.test(lines[i]!)) {\n raw.push(lines[i]!.replace(/^>\\s?/, ''))\n i += 1\n }\n const paras: string[][] = []\n let current: string[] = []\n for (const l of raw) {\n if (l.trim() === '') {\n if (current.length) paras.push(hardLines(current))\n current = []\n } else current.push(l)\n }\n if (current.length) paras.push(hardLines(current))\n blocks.push({ t: 'quote', paras })\n continue\n }\n const listMarker = (s: string) => {\n const m = /^(\\s*)([-*+]|\\d+\\.)\\s+(.*)$/.exec(s)\n if (!m) return undefined\n const depth = Math.floor(m[1]!.length / 2)\n const ordered = /\\d/.test(m[2]!)\n return { depth, ordered, text: m[3]! }\n }\n if (listMarker(line)) {\n const items: { lines: string[]; gutter: number; indent: number }[] = []\n // Marker cells per level, outermost first — a nested item's indent is the\n // sum of its ancestors' gutters (each nested list sits in its parent li's\n // body column).\n const gutters: number[] = []\n let listOrdered: boolean | undefined\n while (i < lines.length) {\n const m = listMarker(lines[i]!)\n if (!m) {\n if (isBlank(lines[i]) && listMarker(lines[i + 1] ?? '')) {\n i += 1\n continue\n }\n break\n }\n // A different marker type at the top level starts a *new* list — one\n // more block, one more inter-block margin.\n if (m.depth === 0) {\n if (listOrdered !== undefined && m.ordered !== listOrdered) break\n listOrdered = m.ordered\n }\n gutters[m.depth] = m.ordered ? 3 : 2\n const indent = gutters.slice(0, m.depth).reduce((a, b) => a + (b ?? 2), 0)\n const source = [m.text]\n i += 1\n // Lazy continuation lines belong to the item; whether each newline\n // renders is `hardLines`' call, same as a paragraph's.\n while (i < lines.length && lines[i]!.trim() !== '' && !listMarker(lines[i]!)) {\n // trimStart only: a trailing double space is the hard-break marker,\n // and `hardLines` needs to see it.\n source.push(lines[i]!.trimStart())\n i += 1\n }\n items.push({ lines: hardLines(source), gutter: gutters[m.depth]!, indent })\n }\n blocks.push({ t: 'list', items })\n continue\n }\n if (line.trimStart().startsWith('|')) {\n // Only the row count. A table lays out at `max-content` and scrolls rather\n // than compressing, so a row is a line whatever the cells hold — the\n // per-column width measuring this used to do fed a wrap model that no\n // longer exists, and it was real work on every table.\n let rows = 0\n while (i < lines.length && lines[i]!.trimStart().startsWith('|')) {\n const cells = lines[i]!\n .trim()\n .replace(/^\\||\\|$/g, '')\n .split('|')\n .map((cell) => cell.trim())\n // The `|---|:--:|` delimiter row is structure, not a rendered line.\n if (!cells.every((cell) => /^:?-+:?$/.test(cell))) rows += 1\n i += 1\n }\n blocks.push({ t: 'table', rows: Math.max(1, rows) })\n continue\n }\n // Paragraph: gather until a blank line or another block opener.\n const para: string[] = [line]\n i += 1\n while (\n i < lines.length &&\n lines[i]!.trim() !== '' &&\n !lines[i]!.startsWith('```') &&\n !/^(#{1,6})\\s+/.test(lines[i]!) &&\n !/^>\\s?/.test(lines[i]!) &&\n !listMarker(lines[i]!) &&\n !lines[i]!.trimStart().startsWith('|') &&\n !/^(-{3,}|\\*{3,}|_{3,})\\s*$/.test(lines[i]!)\n ) {\n para.push(lines[i]!)\n i += 1\n }\n blocks.push({ t: 'p', lines: hardLines(para) })\n }\n return blocks\n}\n\n/** The markdown body's height: blocks, one line between consecutive ones\n * (`.term-md .term-block + .term-block`). Exported for the dev audit only. */\nexport function markdownHeight(md: string, m: CellMetrics, extraPx = 0): Acc {\n const bodyPx = m.width - extraPx - 2 * m.ch // the outer Row's gutter\n const cols = Math.floor(bodyPx / m.ch + EPS)\n const blocks = parseBlocks(md)\n let acc: Acc = { px: 0, exact: true }\n for (const [index, block] of blocks.entries()) {\n if (index > 0) acc = add(acc, { px: m.line, exact: true })\n switch (block.t) {\n case 'p': {\n for (const line of block.lines) {\n const r = textLines(line, cols)\n acc = add(acc, { px: r.lines * m.line, exact: r.exact })\n }\n break\n }\n case 'h': {\n const r = textLines(block.text, cols)\n acc = add(acc, { px: r.lines * m.line, exact: r.exact })\n break\n }\n case 'fence': {\n let px = 0\n let exact = true\n for (const codeLine of block.code.split('\\n')) {\n const r = wrapOne(codeLine, cols)\n px += Math.max(1, r.lines) * m.line\n exact = exact && r.exact\n }\n acc = add(acc, { px: Math.max(px, m.line), exact })\n break\n }\n case 'list': {\n let px = 0\n let exact = true\n for (const item of block.items) {\n for (const line of item.lines) {\n const r = textLines(line, cols - item.indent - item.gutter)\n px += r.lines * m.line\n exact = exact && r.exact\n }\n }\n acc = add(acc, { px, exact })\n break\n }\n case 'quote': {\n let px = 0\n let exact = true\n for (const [pi, para] of block.paras.entries()) {\n if (pi > 0) px += m.line\n for (const line of para) {\n const r = textLines(line, cols - 2)\n px += r.lines * m.line\n exact = exact && r.exact\n }\n }\n acc = add(acc, { px, exact })\n break\n }\n case 'table':\n // One line per row, always. A table is laid out at `max-content` and\n // carries no `max-width`, so a wide one scrolls inside\n // `.term-table-wrap` rather than compressing — which means the auto\n // table layout never wraps a cell and there is nothing here to model.\n // This used to branch on whether the table fit, and flag the wide case:\n // that branch was the theme's own `max-width: 100%` leaking into the\n // calculator, and both went at once. See `terminal.css` §Markdown.\n acc = add(acc, { px: block.rows * m.line, exact: true })\n break\n case 'hr':\n acc = add(acc, { px: m.line, exact: true })\n break\n }\n }\n return { px: Math.max(acc.px, m.line), exact: acc.exact }\n}\n\n/* ── Items ─────────────────────────────────────────────────────────────────── */\n\nfunction diffHeight(patch: FilePatch, m: CellMetrics, extraPx = 0): Acc {\n const walk = (hunk: PatchHunk) => {\n let newLine = hunk.newStart\n let oldLine = hunk.oldStart\n return hunk.lines.map((line) => {\n const kind = line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'context'\n const text = line.slice(1)\n const number = kind === 'add' ? newLine++ : kind === 'remove' ? oldLine++ : newLine\n if (kind === 'context') {\n oldLine++\n newLine++\n }\n return { number, text }\n })\n }\n const hunks = patch.hunks.map(walk)\n const numbered = patch.hunks.some((hunk) => hunk.newStart > 0)\n const width = numbered\n ? String(Math.max(...hunks.flat().map((row) => row.number), 1)).length\n : 0\n const columns = numbered ? width + 3 : 2\n let acc: Acc = { px: 0, exact: true }\n for (const [index, hunk] of hunks.entries()) {\n if (index > 0) acc = add(acc, { px: m.line, exact: true }) // the ⋮ separator row\n for (const row of hunk) {\n acc = add(acc, rowH(row.text || ' ', m, { indentCells: 2, gutterCells: columns, extraPx }))\n }\n }\n if (patch.truncated) acc = add(acc, { px: m.line, exact: true })\n return acc\n}\n\n/** A collapsed tool row: the header, then the diff (when the call carries a\n * patch) or the shared collapsed preview plus its trailing \"there is more\" row.\n * No expanded branch — see the module comment's first invariant. */\nfunction toolRowHeight(item: ToolCallItem, m: CellMetrics, extraPx: number): Acc {\n const preview = toolInputPreview(item.input)\n const backend = item.backend && item.backend !== 'server' ? ` · ${item.backend}` : ''\n let acc = rowH(`${item.name}(${preview})${backend}`, m, { gutterCells: 2, extraPx })\n\n // Each replayed image part draws a box of whole lines, and it draws it in\n // every state — placeholder, picture, failure — so the height is settled\n // before the first byte is asked for and the load can never reflow the list.\n // No wrap and no `exact: false`: the box is the constant, not the image (see\n // `image-box.ts`).\n const images = item.result?.images\n if (images?.length)\n acc = add(acc, { px: images.length * IMAGE_BOX_LINES * m.line, exact: true })\n\n if (item.patch) return add(acc, diffHeight(item.patch, m, extraPx))\n const text = item.result?.text ?? ''\n if (!text) return acc\n // The renderer's own budget, not a copy of it: `collapsedResult` returns both\n // the lines and the exact trailing label, so this cannot drift from what\n // `items.tsx` draws — which it previously could, and which its own comment\n // said only the dev audit was catching.\n const { shown, more } = collapsedResult(text.trimEnd().split('\\n'), item.result?.totalChars)\n for (const line of shown) {\n // indent=1 with columns=3 resolves the indent against the row's own\n // --term-cell: 3ch of padding + 3ch of gutter.\n acc = add(acc, rowH(line || ' ', m, { indentCells: 3, gutterCells: 3, extraPx }))\n }\n if (more) acc = add(acc, rowH(more, m, { indentCells: 3, gutterCells: 3, extraPx }))\n return acc\n}\n\n/** Rows produced inside a subagent are stepped in behind a rule —\n * `border-l-2` (2px) + `pl-3` (12px) on the wrapper in `agent/Transcript.tsx`. */\nconst nestedExtraPx = (item: TranscriptItem): number =>\n 'parentToolUseId' in item && item.parentToolUseId != null ? 14 : 0\n\n/** One transcript item's height, in its default (collapsed, settled-or-not)\n * presentation. */\nexport function itemHeight(item: TranscriptItem, m: CellMetrics): ComputedHeight {\n const extraPx = nestedExtraPx(item)\n switch (item.kind) {\n case 'user': {\n let acc: Acc = { px: 0, exact: true }\n if (item.attachments?.length)\n acc = add(acc, rowH(item.attachments.map((a) => a.name).join(', '), m, { extraPx }))\n if (item.text)\n for (const line of item.text.split('\\n')) acc = add(acc, rowH(line || ' ', m, { extraPx }))\n return acc\n }\n case 'assistant_text':\n return markdownHeight(item.text, m, extraPx)\n case 'thinking':\n return rowH(item.text, m, { extraPx })\n case 'tool_call':\n return toolRowHeight(item, m, extraPx)\n case 'turn_result': {\n let acc = rowH(\n `${item.isError ? item.subtype : 'done'} · ${formatDuration(item.durationMs)} · ${formatCost(item.totalCostUsd)}`,\n m,\n { extraPx },\n )\n for (const message of item.errors ?? []) acc = add(acc, rowH(message, m, { extraPx }))\n return acc\n }\n case 'notice':\n return rowH(item.text, m, { extraPx })\n case 'file_delivered': {\n const text =\n `${item.path} · ${formatBytes(item.bytes)}` +\n (item.description ? ` · ${item.description}` : '')\n return rowH(text, m, { extraPx })\n }\n default:\n return { px: 0, exact: false }\n }\n}\n\n/** A virtual row's height: an item, a folded tool run, or a task block —\n * either fold collapsed is its one summary line, built by the same function\n * the row draws (`runSummary` / `taskSummary`), rendered as one standard\n * 2-cell-gutter `Row`. No expanded branch for the task block either: it is\n * always collapsed by default, which is the invariant that keeps every\n * unmounted row's estimate exact. */\nexport function blockHeight(block: TerminalBlock, m: CellMetrics): ComputedHeight {\n if ('task' in block) {\n return rowH(taskSummary(block.task, taskChildItems(block)), m)\n }\n if ('run' in block) {\n const busy = block.run.some((item) => item.status === 'running' || item.status === 'pending')\n return rowH(runSummary(block.run, busy), m)\n }\n return itemHeight(block.item, m)\n}\n","import { useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { useStickToBottomContext } from 'use-stick-to-bottom'\nimport type { PermissionRequest } from '@workerdeck/protocol'\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { formatCost, formatDuration, toolInputPreview } from '../../lib/format.ts'\nimport { parentOf } from './blocks.ts'\nimport { TerminalSurface } from './surface.tsx'\n\n/**\n * The overview ruler — VS Code's strip beside the minimap, with this\n * transcript's own semantics.\n *\n * A 12px rail over the scroller's right edge, two lanes of 6px: **left** is\n * what you typed (blue), **right** is each turn's final response and its\n * `turn_result` as *one* merged mark (white; red when the turn failed — the\n * turn boundary is the response's address, so two ticks would answer no\n * question the peek doesn't). Those two are the conversation, and they are what\n * you navigate by.\n *\n * Everything else spans the **full 12px** and is an annotation rather than a\n * step: an error, the pending approval (pinned at the foot, pulsing), a\n * bookmark (magenta — paint only; the store is the client's, the way watermarks\n * are) and the catch-up seam (dashed). A mark is its row's extent at rail\n * scale, floored at 2px, drawn as a solid 2px head with a 25% tail; marks\n * merge when closer than a pixel, loudest colour winning.\n *\n * Positions are **pixel space**, not index space: mark y = the row's\n * virtualizer offset over `getTotalSize()`. That is only honest because the\n * height calculator (`height.ts`) feeds `estimateSize`, so an unmounted row's\n * offset is computed rather than guessed — the rail is the calculator's\n * payoff, and it is why the rail can be a *real scrollbar*: drag scrubs\n * `scrollTop` directly, and the native scrollbar is hidden while an\n * interactive rail is mounted.\n *\n * The peek renders from `items`, never the DOM — the row it describes is\n * usually unmounted, so there is nothing to clone. A click is a jump through\n * the transcript's re-aim closure (`onJumpToRow`), which starts with\n * `stopScroll()`: the rail is a third writer of `scrollTop` beside the follow\n * spring and the virtualizer's corrections, and that call is the library's\n * own \"the user is leaving the bottom\" switch.\n *\n * Under `affordances={false}` the rail is passive paint: `pointer-events:\n * none`, no peek, no drag, no click — and the native scrollbar stays, working\n * straight through the paint.\n */\n\ntype Lane = 'l' | 'r' | 'f'\ntype MarkKind =\n | 'user'\n | 'subagent'\n | 'turn'\n | 'turnFailed'\n | 'toolFailed'\n | 'error'\n | 'approval'\n | 'recap'\n | 'bookmark'\n\ntype Mark = {\n kind: MarkKind\n /** The jump anchor (for a turn mark: the paired response). −1 for recap. */\n itemIndex: number\n rowIndex: number\n /** The `turn_result` behind a center mark — the peek shows its done-line. */\n turnIndex?: number\n}\n\n/** Members keep their own y: a dense transcript chain-merges a lane into one\n * tall bar (600 prompts over a 300px rail IS a solid stripe, exactly as VS\n * Code draws dense decorations), and the bar answers the pointer by its\n * *nearest member* — a click or peek at the middle of the bar must not act on\n * the first mark that happened to found the cluster. */\ntype Cluster = { lane: Lane; kind: MarkKind; y: number; h: number; marks: { mark: Mark; y: number }[] }\n\n/** The member closest to a rail-space y — what a press or peek on a merged\n * cluster resolves to. */\nfunction nearestMember(cluster: Cluster, y: number): Mark | undefined {\n let best: { mark: Mark; y: number } | undefined\n for (const member of cluster.marks)\n if (!best || Math.abs(member.y - y) < Math.abs(best.y - y)) best = member\n return best?.mark\n}\n\n/**\n * The two lanes are **channels, not classes**: left is what went *in* — your\n * prompts, and the sub-agents you dispatched — and right is what came *out* —\n * each turn's answer, and everything that went wrong producing one. That is the\n * question a reader actually asks of a rail (\"where did I say something\", \"where\n * did it go wrong\"), and it puts every failure in one column instead of\n * scattering some down the middle.\n *\n * Full width is reserved for what is not a channel at all: a waiting approval\n * (which is the session asking *you*, pinned at the foot), a bookmark (the\n * reader's own annotation) and the catch-up seam (a boundary across both).\n *\n * It also buys the marks their width back: three lanes in a 16px rail is 5px a\n * lane, which is a hard target to hit and a hard colour to see.\n */\nconst LANE: Record<MarkKind, Lane> = {\n user: 'l',\n // Delegated work is input: a sub-agent runs because you asked for it, and its\n // stretch of the transcript is *your* dispatch rather than the session's\n // answer. It also gives a folded `Task` its one honest signal on the rail —\n // collapsed, sixty rows of somebody else's working are one line, and this is\n // the mark that says the region is there at all.\n subagent: 'l',\n turn: 'r',\n turnFailed: 'r',\n // Output, with the answers: a failed tool call is something the run produced.\n // It had been full-width on the argument that it is an alarm rather than a\n // step — but \"alarm\" is not a lane, and half the failures ending up down the\n // middle while `turnFailed` sat in the right lane meant no single column\n // answered \"did anything go wrong\". Its rank in LOUDNESS and its 55% strength\n // are what keep it from shouting over the turns it now sits beside.\n toolFailed: 'r',\n error: 'r',\n approval: 'f',\n recap: 'f',\n bookmark: 'f',\n}\n\n/** Who wins the colour when marks merge. */\nconst LOUDNESS: Record<MarkKind, number> = {\n approval: 7,\n error: 6,\n turnFailed: 5,\n // Under `error`, which is the rank that actually does work: both are lane `f`,\n // so a session error and a tool failure a pixel apart merge and the error must\n // keep the cluster. (It cannot merge with `turnFailed` — that is lane `r`, and\n // merging is per lane.) A failed tool call the model recovered from is routine\n // in a way a session error is not, hence quieter here and at 55% in the CSS.\n // It now shares the response lane with the turn marks, which is the rank that\n // matters: a failure a pixel from a turn end keeps the cluster red.\n toolFailed: 4,\n user: 3,\n turn: 2,\n bookmark: 1,\n // Lane `l`, so this is only ever weighed against `user`, and a prompt wins:\n // the prompt is the step you navigate by and the sub-agent band is the\n // annotation on it. (It ties with `bookmark`, which it can never meet.)\n subagent: 1,\n recap: 0,\n}\n\nconst KIND_NAME: Record<MarkKind, string> = {\n user: 'you',\n subagent: 'sub-agent',\n turn: 'response · turn end',\n turnFailed: 'turn failed',\n toolFailed: 'tool failed',\n error: 'error',\n approval: 'pending approval',\n recap: 'catch-up boundary',\n bookmark: 'bookmark',\n}\n\n// The floor, not the height: a mark spans its row's actual extent at rail\n// scale, so a one-line prompt is a tick and a hundred-line response is a bar —\n// the rail is a map, and on a map a long answer looks long. 2px keeps a tick\n// findable while the CSS draws only the first 2px solid (the rest is a 25%\n// tail); the pointer's real target is the 6px-wide lane, and a press resolves\n// through `nearestMember`, so hit reliability does not ride on mark height.\nconst MIN_MARK = 2\n\n/**\n * Rail pixels per content pixel — the one scale both the marks and the viewport\n * band are drawn at, so they cannot disagree about where a row sits.\n *\n * The denominator is `max(totalSize, viewportH)` and never `totalSize` alone.\n * A transcript **shorter than its viewport** is the case that forces it: with\n * 90px of content in a 906px window, `railH / totalSize` is ~10, and the band\n * — `viewportH * scale` — comes out at 9120px inside a 906px rail. The rail is\n * absolutely positioned *within the scroller*, so that overflow becomes real\n * scrollable height: a short session grew ~8000px of empty space below it, and\n * the reader could scroll away from the only three rows there were.\n *\n * Clamping the denominator says the thing that is actually true: when\n * everything fits, the rail represents the **viewport**, not the content. The\n * band then fills it exactly (`viewportH * railH / viewportH === railH`), which\n * is what \"you are looking at all of it\" should look like. It also makes the\n * overflow structurally impossible rather than merely unlikely — `bandH` can\n * never exceed `railH` again, for any content, because `viewportH` can never\n * exceed the denominator.\n */\nexport function railScale(railH: number, totalSize: number, viewportH: number): number {\n return totalSize > 0 ? railH / Math.max(totalSize, viewportH) : 0\n}\n\n/**\n * The right lane is anchored on **the answer, not the turn end**.\n *\n * It used to be built from `turn_result` items alone, which made it silently\n * history-blind: `#backfillHistory` maps only `user` and `assistant` entries, so\n * a session replayed after a gateway restart — or any resumed session — carried\n * no turn rows at all and the whole white lane came back empty. The blue lane\n * survived, which is what made it look like a rendering bug rather than a\n * missing input.\n *\n * So a turn's mark is emitted for the last top-level assistant message of each\n * segment, and a `turn_result` (when there is one) *decorates* it rather than\n * conjuring it — contributing the failed colour and the `turnIndex` its peek\n * shows the done-line from. Live behaviour is unchanged by construction: the\n * item this lands on is exactly the one `pairedResponse` used to find, because\n * a settled `assistant_text` always precedes the `turn_result` that ends it.\n */\ntype Segment = { response?: number; turn?: number; failed?: boolean }\n\nconst doneLine = (turn: Extract<TranscriptItem, { kind: 'turn_result' }>): string =>\n `${turn.isError ? turn.subtype : 'done'} · ${formatDuration(turn.durationMs)} · ${formatCost(turn.totalCostUsd)}`\n\nfunction excerpt(item: TranscriptItem): string {\n switch (item.kind) {\n case 'user':\n case 'assistant_text':\n case 'thinking':\n case 'notice':\n return item.text\n case 'tool_call':\n return `${item.name}(${toolInputPreview(item.input)})`\n case 'turn_result':\n return doneLine(item)\n case 'file_delivered':\n return item.path\n default:\n return ''\n }\n}\n\nexport interface TerminalScrubberProps {\n items: readonly TranscriptItem[]\n pendingApprovals: readonly PermissionRequest[]\n /** The catch-up boundary's virtual row, when the recap is spliced in. */\n recapRow?: { rowIndex: number; label: string }\n /** Bookmarked item indices. Paint only — no store, no set affordance. */\n bookmarks: readonly number[]\n /**\n * The sub-agent takeover's parent id, when this rail belongs to a frame.\n *\n * **It is what \"top level\" means here.** Three of the rules below mark only\n * items at the conversation's own level, so that a sub-agent's work is\n * represented by the one band its `Task` row gets rather than by a second set\n * of prompts and answers scattered through the rail. Inside a frame that same\n * test excludes *everything* — every item there has a parent by construction —\n * and the rail came out empty: mounted, banded, and marking nothing on a\n * hundred-tool agent. So the level is a parameter, `undefined` at the top and\n * the frame's id inside one.\n */\n frameParentId?: string\n /** Item index → virtual row index (the off-by-a-fold mapping; see\n * `rowIndexForItem` in `agent/Transcript.tsx`). */\n rowIndexFor: (itemIndex: number) => number\n /** A virtual row's offset in content space — the virtualizer's measurements,\n * which the height calculator keeps honest for unmounted rows. */\n offsetOfRow: (rowIndex: number) => number\n /** A virtual row's height in content space, same source — what a mark's own\n * height is scaled from. */\n sizeOfRow: (rowIndex: number) => number\n /** Where an item sits inside a row shared with other items — a task block's\n * absorbed child or a folded run's member (`positionInRow` in\n * `agent/transcript-rows.ts`). Optional and additive: without it every mark\n * spans its row's extent, which for an expanded task block is the whole\n * subagent area. */\n positionInRow?: (itemIndex: number) => { ordinal: number; count: number } | undefined\n totalSize: number\n scrollOffset: number\n viewportH: number\n /** The transcript's re-aim jump (stopScroll + aim + exact finish). */\n onJumpToRow: (rowIndex: number) => void\n /** False renders passive paint: no pointer events at all. */\n interactive: boolean\n fontSize?: number\n lineHeight?: number\n}\n\n/**\n * Exported for `test/scrubber.test.ts` and nothing else — it is not part of the\n * package's surface (`index.ts` does not re-export it). Both of the bugs this\n * function has shipped were pure-logic ones a unit test catches: a live answer\n * with no `turn_result` yet went unmarked for the whole two minutes it was the\n * only thing worth navigating to, and a replayed history — which carries no turn\n * rows at all — came back with an empty right lane.\n */\nexport function buildClusters(\n props: TerminalScrubberProps,\n railH: number,\n): Cluster[] {\n const {\n items,\n bookmarks,\n frameParentId,\n recapRow,\n pendingApprovals,\n rowIndexFor,\n offsetOfRow,\n sizeOfRow,\n positionInRow,\n totalSize,\n viewportH,\n } = props\n const marks: Mark[] = []\n // Which top-level calls a sub-agent ran inside — by `parentToolUseId` and\n // never by the spawning call's *name*: the SDK's own convention is `Task`,\n // but it is a convention (a background agent arrives as `Agent`), and an id\n // that other items demonstrably nest under IS a sub-agent whatever spawned\n // it. The same membership rule `terminalBlocks` folds by, for the same reason.\n const subagentParents = new Set<string>()\n for (const item of items) {\n const parent = parentOf(item)\n if (parent !== undefined) subagentParents.add(parent)\n }\n // The **outcome** call of each row: the last top-level tool call the row\n // holds. A failed call is marked only when it is one of these — see the\n // `toolFailed` branch below for why, and note this needs no block lookup,\n // only `rowIndexFor`.\n const rowOutcome = new Map<number, number>()\n items.forEach((item, index) => {\n if (item.kind !== 'tool_call' || parentOf(item) !== frameParentId) return\n rowOutcome.set(rowIndexFor(index), index)\n })\n // One right-lane mark per segment, emitted when the segment closes. A segment\n // is closed by the next prompt, by its own turn end, or by running out of\n // items — that last one is what a replayed history is made of.\n let segment: Segment = {}\n const closeSegment = () => {\n const anchor = segment.response ?? segment.turn\n if (anchor !== undefined) {\n marks.push({\n kind: segment.failed ? 'turnFailed' : 'turn',\n itemIndex: anchor,\n rowIndex: rowIndexFor(anchor),\n turnIndex: segment.turn,\n })\n }\n segment = {}\n }\n items.forEach((item, index) => {\n // The dispatch itself, marked at its row — which is the folded `Task`\n // block, so the band grows to the whole sub-agent area when it is opened\n // and shrinks back to a tick when it is closed. Deliberately NOT part of\n // the chain below: a `Task` whose own result errored earns a red tick in\n // the response lane *and* this band in the input lane, which is the whole\n // point of the two channels — one says a sub-agent ran here, the other says\n // it came back broken. A failed child inside it still marks separately, at\n // its own fraction of the row.\n if (item.kind === 'tool_call' && subagentParents.has(item.id)) {\n marks.push({ kind: 'subagent', itemIndex: index, rowIndex: rowIndexFor(index) })\n }\n // Top-level prompts only, like the answer check below: a subagent's brief\n // is a `user` item too, and it would both paint a \"you\" mark for something\n // nobody typed and close the segment mid-turn — which mis-anchors the turn\n // mark whenever a task runs between the prompt and the answer.\n if (item.kind === 'user' && parentOf(item) === frameParentId) {\n closeSegment()\n marks.push({ kind: 'user', itemIndex: index, rowIndex: rowIndexFor(index) })\n } else if (item.kind === 'turn_result') {\n segment.turn = index\n segment.failed = item.isError\n closeSegment()\n } else if (item.kind === 'notice' && item.level === 'error') {\n marks.push({ kind: 'error', itemIndex: index, rowIndex: rowIndexFor(index) })\n } else if (\n // **The rail marks what the transcript reddens** — the whole rule, and\n // why this is not simply the per-call predicate it used to be.\n //\n // The row model already decided, twice, that a routine failure the model\n // recovered from is not a failure: `runFailed` colours a folded run by\n // its LAST call, and `taskFailed` colours a `Task` by its OWN result and\n // never a child's. Both were changed from `contains` for the same reason\n // — a normal working session came back painted red, spending the colour\n // that should have been left for the one broken thing on a grep that\n // matched nothing. The rail was deliberately exempted, on the argument\n // that its question (\"is there anything worth navigating to\") differs\n // from the row's (\"how did this end\").\n //\n // Measured against a real session, the exemption did not survive: 178\n // tool calls, 9 failed, EIGHT OF THE NINE recovered from inside their own\n // run, no failed turn and no session error — nine alarms on the rail for\n // a transcript that reddens one row. A red mark beside nothing red is\n // worse than no mark: it sends a reader hunting for damage that is not\n // there.\n //\n // One uniform test covers all three cases: a call is its row's OUTCOME\n // when it is top level and no later top-level call shares its row. For a\n // folded run that is exactly `runFailed`'s last member; for a lone call\n // it is the call; and for a `Task` it is the task itself, because its\n // children are not top level — which is `taskFailed`, spelled a third way\n // and agreeing. A failed child inside a sub-agent is therefore no longer\n // marked, the same call `taskFailed` makes. The sub-agent band still says\n // it ran and its own red tick still says it came back broken, every\n // failure is still red on its own row, and the recap still counts them\n // all.\n //\n // The disjunction is unchanged and both spellings are still needed: an\n // out-of-loop execution failure sets `status` with no `is_error` block to\n // read, and an engine can flag `is_error` on a call this reducer has not\n // settled yet.\n item.kind === 'tool_call' &&\n (item.status === 'failed' || item.result?.isError === true) &&\n rowOutcome.get(rowIndexFor(index)) === index\n ) {\n marks.push({ kind: 'toolFailed', itemIndex: index, rowIndex: rowIndexFor(index) })\n } else if (item.kind === 'assistant_text' && parentOf(item) === frameParentId) {\n if (frameParentId !== undefined) {\n // **Inside a frame every narration step is its own mark**, where the\n // conversation gets one per segment. The segment machinery has nothing\n // to work with here — a sub-agent's stream carries no prompts and no\n // `turn_result`, so every step would fold into a single mark at the\n // final report, which is the one place a reader can already get to. An\n // agent's rail is a list of what it said on the way, and that is what\n // makes a fifty-step run navigable.\n marks.push({ kind: 'turn', itemIndex: index, rowIndex: rowIndexFor(index) })\n return\n }\n // The live one included, deliberately: a turn in flight has no turn end\n // yet, which left a two-minute answer unrepresented on the rail for the\n // whole two minutes it was the only thing worth navigating to. The mark's\n // height is its row's, so it grows as the answer does with no extra\n // bookkeeping, and it cannot double up — the reducer settles this item in\n // the same action that appends the `turn_result` that closes the segment.\n segment.response = index\n }\n })\n // A history that ends mid-segment still has an answer in it.\n closeSegment()\n for (const index of bookmarks)\n if (index >= 0 && index < items.length)\n marks.push({ kind: 'bookmark', itemIndex: index, rowIndex: rowIndexFor(index) })\n if (recapRow) marks.push({ kind: 'recap', itemIndex: -1, rowIndex: recapRow.rowIndex })\n\n const scale = railScale(railH, totalSize, viewportH)\n const lanes = new Map<Lane, { mark: Mark; y: number; h: number }[]>()\n for (const mark of marks) {\n // A mark's height is its row's, at rail scale, floored at the hit target —\n // the row the mark *anchors* (for a turn, the final response), which is\n // where the reader lands and what they came to gauge the size of.\n //\n // EXCEPT an item that SHARES its row (a task block's absorbed child, a\n // folded run's member): there the row's extent is mostly other items' work,\n // and expanded it is the entire subagent area — one failed child of a\n // hundred-call task used to paint a solid red band down the whole rail.\n // Such a mark is a tick at its fractional position within the row.\n // `sizeOfRow` is the virtualizer's *measurement*, so expansion is reflected\n // with no expansion state here (which the scrubber deliberately cannot see,\n // `height.ts`'s \"unmounted is collapsed\" invariant being load-bearing):\n // collapsed, the fraction rounds onto the row's one line and siblings merge\n // exactly as before; expanded, the ticks distribute down the block —\n // approximately, since children differ in height, which a 12px rail cannot\n // show and exactness would cost the scrubber the one thing it must not know.\n // Applied here rather than per kind because a bookmark on an absorbed child\n // has the identical bug; `recap` is `itemIndex: -1`, hence the guard.\n const within = mark.itemIndex >= 0 ? positionInRow?.(mark.itemIndex) : undefined\n const rowH = sizeOfRow(mark.rowIndex)\n const h = within ? MIN_MARK : Math.max(MIN_MARK, Math.round(rowH * scale))\n const y = Math.min(\n Math.max(0, railH - h),\n Math.round(\n (offsetOfRow(mark.rowIndex) + (within ? (within.ordinal / within.count) * rowH : 0)) * scale,\n ),\n )\n const lane = LANE[mark.kind]\n const list = lanes.get(lane) ?? []\n list.push({ mark, y, h })\n lanes.set(lane, list)\n }\n const clusters: Cluster[] = []\n for (const [lane, list] of lanes) {\n list.sort((a, b) => a.y - b.y)\n let current: Cluster | null = null\n for (const { mark, y, h } of list) {\n // Merge when the gap is under a pixel; the merged mark grows and takes\n // the loudest member's colour.\n if (current && y <= current.y + current.h + 1) {\n current.h = Math.max(current.h, y + h - current.y)\n if (LOUDNESS[mark.kind] > LOUDNESS[current.kind]) current.kind = mark.kind\n current.marks.push({ mark, y })\n } else {\n current = { lane, kind: mark.kind, y, h, marks: [{ mark, y }] }\n clusters.push(current)\n }\n }\n }\n // The approval is not an item — the prompt renders below the transcript —\n // so its mark pins at the rail's foot, where the prompt is.\n if (pendingApprovals.length > 0)\n clusters.push({\n // `LANE.approval`, not a literal: this cluster is built by hand because it\n // has no item to derive a position from, and a hardcoded lane here is how\n // it silently kept the old three-lane layout after the map moved on.\n lane: LANE.approval,\n kind: 'approval',\n y: Math.max(0, railH - MIN_MARK),\n h: MIN_MARK,\n marks: [],\n })\n return clusters\n}\n\nfunction peekContent(\n cluster: Cluster,\n /** The member the pointer resolved to — see {@link nearestMember}. */\n first: Mark | undefined,\n { items, pendingApprovals, recapRow }: TerminalScrubberProps,\n): ReactNode {\n const more = cluster.marks.length > 1 ? ` · ${cluster.marks.length} marks` : ''\n let body: ReactNode = null\n if (cluster.kind === 'approval') {\n const request = pendingApprovals[0]\n body = request ? (\n <>\n <div data-tone='bright'>{request.title ?? 'Permission required'}</div>\n <div className='term-scrub-ex' data-tone='fg'>\n {`${request.displayName ?? request.toolName}(${toolInputPreview(request.input)})`}\n </div>\n </>\n ) : null\n } else if (cluster.kind === 'recap' && !first) {\n body = <div data-tone='faint'>※ {recapRow?.label}</div>\n } else if (first) {\n const item = items[first.itemIndex]\n if (first.kind === 'recap') {\n body = <div data-tone='faint'>※ {recapRow?.label}</div>\n } else if (first.kind === 'turn' || first.kind === 'turnFailed') {\n // The merged mark's peek carries both halves: the message the turn ended\n // on, and the done-line (with its reasons, when it failed).\n const turn = first.turnIndex === undefined ? undefined : items[first.turnIndex]\n body = (\n <>\n {item?.kind === 'assistant_text' ? (\n <div className='term-scrub-ex' data-tone='fg'>\n <span data-tone='dim'>● </span>\n {item.text}\n </div>\n ) : null}\n {turn?.kind === 'turn_result' ? (\n <>\n <div data-tone={turn.isError ? 'red' : 'faint'}>{doneLine(turn)}</div>\n {turn.errors?.map((message, index) => (\n <div key={index} data-tone='red'>\n {message}\n </div>\n ))}\n </>\n ) : null}\n </>\n )\n } else if (item) {\n const failure =\n first.kind === 'toolFailed' && item.kind === 'tool_call'\n ? item.result?.text.split('\\n').find((line) => line.trim() !== '')\n : undefined\n body = (\n <>\n <div\n className='term-scrub-ex'\n data-tone={first.kind === 'error' || first.kind === 'toolFailed' ? 'red' : 'fg'}>\n {first.kind === 'user' ? <span data-tone='dim'>{'❯ '}</span> : null}\n {excerpt(item)}\n </div>\n {/* Which tool failed is rarely the question — `Bash(pnpm test)` is\n what you already expected to see. The first non-blank line of what\n it said back is the thing worth peeking at. */}\n {failure ? (\n <div className='term-scrub-ex' data-tone='red'>\n {failure}\n </div>\n ) : null}\n </>\n )\n }\n }\n return (\n <>\n <div data-tone='faint'>\n {KIND_NAME[first?.kind ?? cluster.kind]}\n {more}\n </div>\n {body}\n </>\n )\n}\n\nexport function TerminalScrubber(props: TerminalScrubberProps) {\n const {\n scrollOffset,\n viewportH,\n totalSize,\n onJumpToRow,\n interactive,\n fontSize,\n lineHeight,\n } = props\n const stick = useStickToBottomContext()\n const bodyRef = useRef<HTMLDivElement | null>(null)\n const peekRef = useRef<HTMLDivElement | null>(null)\n const [railH, setRailH] = useState(0)\n const [peek, setPeek] = useState<{ cluster: Cluster; mark: Mark | undefined; y: number } | null>(\n null,\n )\n const drag = useRef<{ y: number; moved: boolean; target: EventTarget | null } | null>(null)\n\n useEffect(() => {\n const element = bodyRef.current\n if (!element) return\n const observer = new ResizeObserver(() => setRailH(element.clientHeight))\n observer.observe(element)\n setRailH(element.clientHeight)\n return () => observer.disconnect()\n }, [])\n\n // The band tracks the scroller's own scroll events, not the `scrollOffset`\n // prop: the virtualizer notifies React only when the virtual row *range*\n // changes, and scrolling inside one tall row changes none — the prop then\n // refreshes only when `isScrolling` flips at the end, which reads as the\n // band lagging the drag and snapping into place. The prop still seeds the\n // first paint, before this listener's first event.\n const [liveOffset, setLiveOffset] = useState(scrollOffset)\n useEffect(() => {\n const scroller = stick.scrollRef.current\n if (!scroller) return\n const onScroll = () => setLiveOffset(scroller.scrollTop)\n scroller.addEventListener('scroll', onScroll, { passive: true })\n setLiveOffset(scroller.scrollTop)\n return () => scroller.removeEventListener('scroll', onScroll)\n }, [stick.scrollRef])\n\n // A peek is a snapshot of the cluster it was opened on; if the transcript\n // changes underneath (a fixture/session swap, a burst of new items), drop it\n // rather than describe rows that no longer exist.\n useEffect(() => {\n setPeek(null)\n }, [props.items])\n\n // Wheel over the rail scrolls the transcript. Manual listener because it\n // must preventDefault (React's root wheel listeners are passive).\n useEffect(() => {\n if (!interactive) return\n const element = bodyRef.current\n if (!element) return\n const onWheel = (event: WheelEvent) => {\n const scroller = stick.scrollRef.current\n if (!scroller) return\n scroller.scrollTop += event.deltaY\n event.preventDefault()\n }\n element.addEventListener('wheel', onWheel, { passive: false })\n return () => element.removeEventListener('wheel', onWheel)\n }, [interactive, stick.scrollRef])\n\n // The peek can be taller than the space beside its mark — clamp it into the\n // rail after it has a measured height.\n useLayoutEffect(() => {\n const element = peekRef.current\n if (!element || !peek) return\n const height = element.offsetHeight\n const railHeight = bodyRef.current?.clientHeight ?? 0\n element.style.top = `${Math.max(4, Math.min(railHeight - height - 4, peek.y - height / 2))}px`\n }, [peek])\n\n // Memoized against content and geometry, NOT recomputed per render: the live\n // scroll offset re-renders this component on every scroll event, and\n // rebuilding the clusters there walks every item — O(session) work per\n // wheel tick for output that only changes when content or measurements do.\n // `rowIndexFor`/`offsetOfRow` are closures rebuilt every parent render and\n // deliberately not dependencies; `totalSize` stands in for the measurements\n // behind them — row heights cannot move a mark without moving the total.\n const clusters = useMemo(\n () => (railH > 0 ? buildClusters(props, railH) : []),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n // `viewportH` rides here because it is the scale's other term whenever the\n // transcript is shorter than the window — without it a resize in that\n // regime leaves every mark at the old scale.\n [\n props.items,\n props.bookmarks,\n props.recapRow,\n props.pendingApprovals,\n totalSize,\n railH,\n viewportH,\n ],\n )\n const scale = railScale(railH, totalSize, viewportH)\n const bandH = Math.max(2, Math.min(railH, Math.round(viewportH * scale)))\n // Clamped against the rail's foot as well as its head: an overscroll bounce\n // drives `liveOffset` past `totalSize - viewportH` for a frame or two, and the\n // band is the one child whose top is not already bounded by its own height.\n const bandTop = Math.max(0, Math.min(railH - bandH, Math.round(liveOffset * scale)))\n\n const scrub = (clientY: number) => {\n const rail = bodyRef.current\n const scroller = stick.scrollRef.current\n if (!rail || !scroller) return\n const rect = rail.getBoundingClientRect()\n const fraction = Math.min(1, Math.max(0, (clientY - rect.top) / rect.height))\n scroller.scrollTop = fraction * scroller.scrollHeight - scroller.clientHeight / 2\n }\n\n /** A pointer's y in rail space. */\n const railY = (clientY: number): number =>\n clientY - (bodyRef.current?.getBoundingClientRect().top ?? 0)\n\n const activate = (cluster: Cluster, clientY: number) => {\n if (cluster.kind === 'approval' && cluster.marks.length === 0) {\n void stick.scrollToBottom()\n return\n }\n const mark = nearestMember(cluster, railY(clientY))\n if (mark) onJumpToRow(mark.rowIndex)\n }\n\n const showPeek = (cluster: Cluster, clientY: number) => {\n const y = railY(clientY)\n const mark = nearestMember(cluster, y)\n setPeek((previous) =>\n previous && previous.cluster === cluster && previous.mark === mark\n ? previous\n : { cluster, mark, y: Math.min(Math.max(y, cluster.y), cluster.y + cluster.h) },\n )\n }\n\n // The rail is a scrollbar to assistive tech when it acts like one, and\n // invisible when it is passive paint (a decorative copy of information the\n // transcript itself carries).\n const maxOffset = Math.max(1, totalSize - viewportH)\n return (\n <TerminalSurface\n fontSize={fontSize}\n lineHeight={lineHeight}\n className='term-scrubber'\n data-interactive={interactive || undefined}\n {...(interactive\n ? {\n role: 'scrollbar',\n 'aria-orientation': 'vertical' as const,\n 'aria-label': 'Transcript overview',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n 'aria-valuenow': Math.min(100, Math.max(0, Math.round((liveOffset / maxOffset) * 100))),\n }\n : { 'aria-hidden': true })}>\n <div\n ref={bodyRef}\n className='term-scrubber-body'\n {...(interactive\n ? {\n onPointerDown: (event) => {\n // The rail is about to write scrollTop; this is the follow\n // spring's own off switch, same as every other jump.\n stick.stopScroll()\n drag.current = { y: event.clientY, moved: false, target: event.target }\n event.currentTarget.setPointerCapture(event.pointerId)\n },\n onPointerMove: (event) => {\n const state = drag.current\n if (!state) return\n if (!state.moved && Math.abs(event.clientY - state.y) < 3) return\n state.moved = true\n setPeek(null)\n scrub(event.clientY)\n },\n onPointerUp: (event) => {\n const state = drag.current\n drag.current = null\n if (!state || state.moved) return\n // A clean press: on a mark it is a jump; on the ground it is a\n // scrub to that spot — scrollbar semantics.\n const mark = (state.target as HTMLElement | null)?.closest?.('[data-ci]')\n const index = mark ? Number((mark as HTMLElement).dataset.ci) : Number.NaN\n if (Number.isInteger(index) && clusters[index])\n activate(clusters[index]!, event.clientY)\n else scrub(event.clientY)\n },\n }\n : null)}>\n {/* The band is the whole \"where am I\" answer. It used to carry a 2px\n blue line on its top edge as well; with the band already outlined,\n that was a second indicator of one fact, and the loudest colour on\n the rail spent on it. */}\n <div className='term-scrub-band' style={{ top: bandTop, height: bandH }} />\n {clusters.map((cluster, index) => (\n <div\n key={index}\n data-ci={index}\n className='term-scrub-mark'\n data-lane={cluster.lane}\n data-kind={cluster.kind}\n style={{ top: cluster.y, height: cluster.h }}\n {...(interactive\n ? {\n onPointerEnter: (event) => showPeek(cluster, event.clientY),\n // A chain-merged bar can span the rail; sliding along it\n // retargets the peek to the member under the pointer.\n onPointerMove: (event) => {\n if (!drag.current) showPeek(cluster, event.clientY)\n },\n onPointerLeave: () => setPeek(null),\n }\n : null)}\n />\n ))}\n {peek ? (\n <div ref={peekRef} className='term-scrub-peek' style={{ top: peek.y }}>\n {peekContent(peek.cluster, peek.mark, props)}\n </div>\n ) : null}\n </div>\n </TerminalSurface>\n )\n}\n","/**\n * The virtual row model: what one row of the transcript's virtual list *is*,\n * and the two rules everything positional must go through — which item a row\n * is spaced as, and how an item index maps onto a row index. Pure; shared by\n * the transcript shell and its hooks.\n */\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { needsBlank, type TerminalBlock } from '../terminal/items.tsx'\n\n/** One row of the virtual list: a {@link TerminalBlock} (a transcript item,\n * — under the terminal theme — a folded run of tool calls, or a task block\n * standing for a `Task` call and everything its subagent produced), or the\n * recap boundary line spliced in at `catchUp.from`. One flat array so the\n * virtualizer sees stable indices, and each row carries the key the item was\n * already React-keyed by — measurements are cached per key, so a row keeps its\n * measured height when the recap splice shifts every index after it. */\nexport type TranscriptRow =\n | TerminalBlock\n | { key: 'recap'; line: string }\n /** The sub-agent's brief, spliced in as the takeover frame's first row — what\n * the agent was asked, before what it did. Synthetic like the recap row and\n * for the same reason: it is not a transcript item (the engine puts the\n * instruction in the spawning call's `prompt`, never in the stream), but it\n * has to be a row so the virtualizer can size and key it. */\n | { key: 'brief'; text: string }\n\n/** The item a row is spaced *as*. A run stands for the calls it folded, and a\n * task block for the `Task` call it absorbed into — all tool calls, so a run,\n * a task and a lone tool call below them still read as one block. */\nexport function rowItem(row: TranscriptRow | undefined): TranscriptItem | undefined {\n if (!row) return undefined\n if ('item' in row) return row.item\n if ('run' in row) return row.run[0]\n if ('task' in row) return row.task\n return undefined\n}\n\n/**\n * Does a blank line go above this row, in the terminal theme?\n *\n * The recap row always earns one — it is a boundary, and a boundary flush\n * against the row above reads as part of it. Otherwise the pair decides\n * (`needsBlank`): consecutive tool calls are one block in the CLI and get none.\n */\nexport function gapBefore(rows: TranscriptRow[], index: number): boolean {\n const before = rowItem(rows[index - 1])\n const after = rowItem(rows[index])\n if (!before || !after) return true\n return needsBlank(before, after)\n}\n\n/**\n * Which items each task block absorbed, as itemIndex → rowIndex — the one\n * lookup {@link rowIndexForItem} cannot answer from ordering (see its comment).\n * Memoized per rows array identity: the shell builds `rows` in a `useMemo`, so\n * within one row list this is built once, and a WeakMap means a discarded list\n * takes its map with it. Memoization only — the answer is a pure function of\n * the array.\n */\nconst absorbedCache = new WeakMap<readonly TranscriptRow[], Map<number, number>>()\n\nfunction absorbedRows(rows: readonly TranscriptRow[]): Map<number, number> {\n const hit = absorbedCache.get(rows)\n if (hit) return hit\n const map = new Map<number, number>()\n rows.forEach((row, rowIndex) => {\n if ('task' in row) for (const itemIndex of row.childIndices) map.set(itemIndex, rowIndex)\n })\n absorbedCache.set(rows, map)\n return map\n}\n\n/**\n * Transcript-item index → virtual-row index — **the off-by-a-fold trap.**\n *\n * The virtualizer's rows are {@link TerminalBlock}s, not items: a folded tool\n * run occupies ONE row for `run.length` consecutive items, a task block\n * occupies ONE row for its `Task` call *plus every item its subagent produced*,\n * and the recap boundary is a row with *no* item index at all, shifting every\n * row after it by one. `virtualizer.scrollToIndex(itemIndex)` is therefore\n * wrong by construction on any folded or spliced transcript — every jump that\n * starts from an item (the scrubber's marks, a future bookmark) must come\n * through here first.\n *\n * The contract, in two halves:\n *\n * - An index a task block **absorbed** maps to that block's row, wherever the\n * child fell in the stream. Subagents run in parallel, so absorbed indices\n * interleave arbitrarily with later rows' starts — no ordering argument can\n * find their row, which is why they are answered first, from a per-row-list\n * map ({@link absorbedRows}) built once per rows array. A row's coverage is\n * its `childIndices`, never `[index, index + N)` arithmetic.\n * - Every other index maps to the **last non-recap row whose start (`index`)\n * is ≤ the target** — the original rule, still a binary search. Rows stay\n * ordered by `index`, and the ordering argument is now: between one row's\n * start and the next row's, every index is either absorbed (answered above)\n * or a member of the earlier row — note that is *weaker* than the old\n * contiguity claim, because a run can fold across an absorbed gap (two\n * top-level calls separated only by a subagent's step are adjacent on\n * screen), so `[index, index + run.length)` arithmetic no longer describes\n * a run's coverage; membership does. The recap row is skipped by giving it\n * its successor's start for navigation (both qualify at the boundary, and\n * \"last wins\" lands on the real row) while never letting it be the answer.\n *\n * Exhaustively checked against a linear reference — every fixture × every item\n * index × several splice positions — by `__wdCheckMapping` in `dev/App.tsx`,\n * and against constructed interleavings in `test/transcript-rows.test.ts`.\n */\nexport function rowIndexForItem(rows: readonly TranscriptRow[], itemIndex: number): number {\n const absorbed = absorbedRows(rows).get(itemIndex)\n if (absorbed !== undefined) return absorbed\n let lo = 0\n let hi = rows.length - 1\n let best = 0\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n const row = rows[mid]!\n let start: number\n if ('index' in row) start = row.index\n else {\n const next = rows[mid + 1]\n start = next && 'index' in next ? next.index : Number.MAX_SAFE_INTEGER\n }\n if (start <= itemIndex) {\n if ('index' in row) best = mid\n lo = mid + 1\n } else {\n hi = mid - 1\n }\n }\n return best\n}\n\n/** Where an item sits inside a row it shares with other items: its 0-based\n * ordinal in stream order, out of `count` siblings. `0 ≤ ordinal < count`. */\nexport type RowPosition = { ordinal: number; count: number }\n\nconst positionCache = new WeakMap<readonly TranscriptRow[], Map<number, RowPosition>>()\n\nfunction rowPositions(rows: readonly TranscriptRow[]): Map<number, RowPosition> {\n const hit = positionCache.get(rows)\n if (hit) return hit\n const map = new Map<number, RowPosition>()\n for (const row of rows) {\n if ('task' in row) {\n const count = row.childIndices.length\n row.childIndices.forEach((itemIndex, ordinal) => map.set(itemIndex, { ordinal, count }))\n } else if ('run' in row && row.run.length > 1) {\n const count = row.run.length\n row.indices.forEach((itemIndex, ordinal) => map.set(itemIndex, { ordinal, count }))\n }\n }\n positionCache.set(rows, map)\n return map\n}\n\n/**\n * Where an item sits inside a row that holds MORE than itself — a task block's\n * absorbed child, or a member of a folded run of two or more. `undefined` for\n * everything else, including a row's own head item (the `Task` call, a run's\n * first member is *not* exempt) and a **singleton run**: there the row's extent\n * IS the item's, and a mark spanning it is honest.\n *\n * That carve-out is load-bearing rather than tidy: `pushLeaf` makes *every*\n * top-level tool call a `RunBlock`, usually of length 1, so without it every\n * ordinary failed call's scrubber mark would shrink from its row's extent to a\n * tick and the rail would stop reading as a map — a regression traded for a fix.\n *\n * The scrubber is the consumer: a mark for a shared-row item anchors at\n * `ordinal / count` of the row's *measured* height instead of inheriting an\n * extent that is mostly other items' work (one failed child of a hundred-call\n * task painted the whole expanded block red). Memoized per rows array identity\n * exactly like {@link absorbedRows}, and pure — the answer is a function of the\n * array alone, and a discarded array takes its map with it.\n */\nexport function positionInRow(\n rows: readonly TranscriptRow[],\n itemIndex: number,\n): RowPosition | undefined {\n return rowPositions(rows).get(itemIndex)\n}\n","/**\n * The height epoch: one cache generation of computed row heights (terminal\n * theme only — cards have no calculator and keep the flat estimate). Owned by\n * the transcript shell because it owns the virtualizer the heights feed; the\n * WeakMap inside self-invalidates through the reducer's replace-on-mutation,\n * and the epoch itself is replaced wholesale when the wrap width or the cell\n * changes. Measured off the rows container: it *is* the width rows wrap in\n * (the scroller can resize without it moving — `ConversationContent` caps at\n * 48rem — and the window never hears about a splitter drag), and it inherits\n * the surface's font, which is what makes the `ch` probe honest. All DOM\n * reads happen in the effect, debounced; render never touches layout.\n */\nimport { useEffect, useState, type RefObject } from 'react'\nimport { createHeightEpoch, measureCh, type HeightEpoch } from '../terminal/height.ts'\n\nexport function useHeightEpoch(options: {\n terminal: boolean\n /** The terminal cell, when the host set one — only read as a signal that the\n * epoch must re-measure; the epoch's numbers come from the DOM. */\n fontSize?: number\n lineHeight?: number\n rowsRef: RefObject<HTMLDivElement | null>\n}): HeightEpoch | null {\n const { terminal, fontSize, lineHeight, rowsRef } = options\n const [epoch, setEpoch] = useState<HeightEpoch | null>(null)\n useEffect(() => {\n if (!terminal) return\n const element = rowsRef.current\n if (!element) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const measure = () => {\n const line = Number.parseFloat(\n getComputedStyle(element).getPropertyValue('--term-line'),\n )\n const width = element.clientWidth\n const ch = measureCh(element)\n if (!line || !width || !ch) return\n setEpoch((previous) =>\n previous && previous.width === width && previous.ch === ch && previous.line === line\n ? previous\n : createHeightEpoch(width, ch, line),\n )\n }\n const observer = new ResizeObserver(() => {\n clearTimeout(timer)\n timer = setTimeout(measure, 150)\n })\n observer.observe(element)\n measure()\n return () => {\n observer.disconnect()\n clearTimeout(timer)\n }\n // fontSize/lineHeight: a cell change re-renders every row, which usually\n // moves the container's size and fires the observer — but a transcript\n // whose height happens to survive the change would keep a stale `ch`, so\n // the props re-arm the measurement directly.\n }, [terminal, fontSize, lineHeight, rowsRef])\n return epoch\n}\n","/**\n * The jump: aim at a virtual row, land exactly. Every jump on the transcript\n * surface — the catch-up strip's, and each of the scrubber's marks — comes\n * through here, along with the re-pin the catch-up strip's dismiss uses.\n * **Instant**, like everything else that moves the scroll position: VS Code's\n * editor jumps when you click its scrollbar and so does this.\n *\n * The aim loop survives from before the height calculator, with its job\n * changed twice. It used to be the mechanism: offsets over unmeasured spans\n * were sums of flat estimates (~3300px off over 600 rows), and the loop\n * walked them in up to six passes, each pass better than the last because\n * the rows it crossed had measured. With `estimateSize` computed the first\n * aim lands within a line or two; with the travel now instant there is also\n * no in-flight animation suppressing the virtualizer's own adjustments — the\n * condition that made a *single* smooth `scrollToIndex` unable to\n * self-correct at all. What is left is convergence insurance for the two\n * things the calculator cannot know: the recap row's estimated constant, and\n * flagged content (CJK, compressed tables). It is cheap now — each pass is a\n * jump, not a journey, so a pass that was already on target costs nothing and\n * the check exits after one.\n */\nimport { useEffect, useRef, type RefObject } from 'react'\nimport type { Virtualizer } from '@tanstack/react-virtual'\nimport type { useStickToBottomContext } from 'use-stick-to-bottom'\nimport type { HeightEpoch } from '../terminal/height.ts'\nimport { gapBefore, type TranscriptRow } from './transcript-rows.ts'\n\n/** How many times a jump re-checks its landing, and how long it waits before\n * doing so. Both are much smaller than the smooth era's 6 × 300ms: a pass is\n * now a jump rather than a journey, so the only thing being waited on is a\n * layout pass in which the crossed rows measure. */\nconst AIM_PASSES = 4\nconst AIM_SETTLE_MS = 50\n\nexport function useTranscriptJumps(options: {\n rows: TranscriptRow[]\n terminal: boolean\n stickyPrompt: boolean\n epoch: HeightEpoch | null\n promptRows: readonly number[]\n scrollElement: HTMLElement | null\n rowsRef: RefObject<HTMLDivElement | null>\n virtualizer: Virtualizer<HTMLElement, HTMLDivElement>\n stick: ReturnType<typeof useStickToBottomContext>\n jumpToRecapRef?: RefObject<(() => void) | null>\n repinRef?: RefObject<(() => void) | null>\n}): (rowIndex: number, align?: 'start' | 'center') => void {\n const {\n rows,\n terminal,\n stickyPrompt,\n epoch,\n promptRows,\n scrollElement,\n rowsRef,\n virtualizer,\n stick,\n jumpToRecapRef,\n repinRef,\n } = options\n\n // The pending re-aim lives in a ref, and this is the whole reason: the\n // closure is rebuilt every render to keep `rows` fresh, so anything held in\n // its scope is torn down every render too — and a jump in flight re-renders\n // constantly, because that is what rows mounting *is*. A timer in the\n // 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\n // `align` is the caller's, and every caller on this surface now asks for\n // `'start'`: a jump target is a place to *start reading* — what you came for\n // runs downward from it — so its line lands at exactly the top edge with the\n // screen below it to read into, and centring wastes half the viewport on what\n // you have already read. The recap boundary was the one exception, argued as\n // \"a seam, and seeing a little of what came before is how you place it\"; in\n // practice the seam is a starting line too. `'center'` survives as the\n // default because it is what `scrollIntoView` means and a future caller may\n // want it, not because anything here uses it.\n //\n // The two aligns aim differently, and the split is load-bearing. `'start'`\n // computes the target offset itself — never `scrollIntoView` — because a\n // prompt row is `position: sticky` and scrollIntoView aims at the element's\n // *current* rect, which for a stuck row is wherever it is pinned: the jump\n // would be a no-op on the very rows the scrubber's left lane points at. The\n // target is the row's virtual start plus the rows container's own offset in\n // the scroll content, plus the row's gap padding (the blank line is padding\n // *on* the row, and the reader asked for the line, not its air) — which also\n // lands exactly on the sticky engage threshold, so the row arrives pinned.\n // Re-aims recompute it while rows crossed by the travel measure.\n //\n // `stopScroll()` first, because the pin spring is the other `scrollTop`\n // writer and this is the library's own switch for \"the user is leaving the\n // bottom\".\n const jumpToRow = (rowIndex: number, align: 'start' | 'center' = 'center') => {\n if (rowIndex < 0 || rowIndex >= rows.length || !scrollElement) return\n stick.stopScroll()\n clearTimeout(aimTimer.current)\n if (align === 'start') {\n // The gap padding a row's *line* sits below — the target is the line,\n // not its air.\n const linePad = (index: number) =>\n terminal && epoch && index > 0 && gapBefore(rows, index) ? epoch.line : 0\n const target = () => {\n const start = virtualizer.measurementsCache[rowIndex]?.start ?? 0\n const spacer = rowsRef.current\n ? rowsRef.current.getBoundingClientRect().top -\n scrollElement.getBoundingClientRect().top +\n scrollElement.scrollTop\n : 0\n let top = start + spacer + linePad(rowIndex)\n // A non-prompt target lands *below* the pinned prompt, not under it:\n // at this offset the turn's own prompt head is stuck at the top, and a\n // jump that puts the line at zero puts it exactly behind that band —\n // the scrubber's answer marks would all land their first line hidden.\n // The head is one line tall. A jump *to* a prompt keeps zero: it\n // becomes the pinned row itself.\n if (terminal && stickyPrompt && epoch && !promptRows.includes(rowIndex)) {\n const pinned = promptRows.some((index) => index < rowIndex)\n if (pinned) top -= epoch.line\n }\n return Math.round(top)\n }\n const aim = (attempt: number) => {\n const top = target()\n scrollElement.scrollTo({ top, behavior: 'instant' })\n if (attempt >= AIM_PASSES) return\n aimTimer.current = setTimeout(() => {\n // Off target: a row that measured under the jump moved it. (It can no\n // longer be \"still travelling\" — that was the smooth era.)\n if (Math.abs(scrollElement.scrollTop - target()) > 1) aim(attempt + 1)\n }, AIM_SETTLE_MS)\n }\n aim(0)\n return\n }\n const aim = (attempt: number) => {\n const row = scrollElement.querySelector(`[data-index=\"${rowIndex}\"]`)\n if (row) {\n row.scrollIntoView({ behavior: 'instant', block: align })\n return\n }\n if (attempt >= AIM_PASSES) return\n virtualizer.scrollToIndex(rowIndex, { align, behavior: 'auto' })\n // A frame or two, not a journey: the aim is only better once the rows\n // crossed have mounted *and* been measured, and that is a layout pass\n // away — but with the jump instant there is nothing else to wait for.\n aimTimer.current = setTimeout(() => aim(attempt + 1), AIM_SETTLE_MS)\n }\n aim(0)\n }\n\n useEffect(() => {\n if (!jumpToRecapRef) return\n jumpToRecapRef.current = () => {\n // `'start'`, like the scrubber's marks. The seam is a place to *start\n // reading* — everything you missed runs downward from it — so it belongs\n // at the top edge with the screen below it to read into. Centring it\n // spent half the viewport on rows you had already read.\n jumpToRow(\n rows.findIndex((row) => row.key === 'recap'),\n 'start',\n )\n }\n return () => {\n jumpToRecapRef.current = null\n }\n })\n\n // Re-pin to the bottom. Published the same way as the jump above, and for the\n // same reason: only the scroll context can do it, and it lives in here.\n //\n // `'instant'`, never the follow spring. The spring exists to keep up with a\n // stream a few pixels at a time; from a reader who had scrolled a long way up\n // it would animate the whole way down, which is the smooth-scroll-on-switch\n // complaint this component already carries two guards against. The target is\n // trustworthy because the bottom is `totalSize`, which the height calculator\n // makes exact — unlike a mid-list row, which is why `jumpToRow` needs its\n // aim/re-aim loop and this needs none.\n useEffect(() => {\n if (!repinRef) return\n repinRef.current = () => {\n void stick.scrollToBottom('instant')\n }\n return () => {\n repinRef.current = null\n }\n })\n\n return jumpToRow\n}\n","import { Fragment, useEffect, useMemo, useState } from 'react'\nimport type { TranscriptItem, TranscriptState } from '@workerdeck/react'\nimport { cn } from '../../lib/utils.ts'\nimport type { TerminalAffordances } from './affordances.tsx'\nimport { OpenSubagentAction, WithActions } from './affordances.tsx'\nimport {\n AssistantRow,\n FileRow,\n NoticeRow,\n ToolRunRow,\n ThinkingRow,\n ToolRow,\n TurnResultRow,\n UserRow,\n WorkingRow,\n blockNeedsBlank,\n taskChildItems,\n terminalBlocks,\n type TaskBlock,\n} from './items.tsx'\nimport { usePulse } from '../agent/pulse.tsx'\nimport { Pressable, useRevealOnOpen } from './press.tsx'\nimport { taskBrief, taskBusy, taskFailed, taskSummary } from './tool-run.ts'\nimport { BRIEF_LINES } from './height.ts'\nimport { Blank, Row } from './row.tsx'\nimport { TerminalSurface } from './surface.tsx'\n\n/**\n * The transcript, drawn as a terminal.\n *\n * The whole of the layout is here and it is four lines long, which is the point:\n * items become row blocks, a blank line goes between blocks that do not belong\n * together, and the working line is one more row at the end. There is no gap\n * scale, no density knob and no variant branch — a terminal has one line height\n * and one type size, and everything the theme can express is expressed in which\n * rows exist and what marker each carries.\n *\n * Not virtualized yet: the row blocks are the part that has to be right first,\n * and the existing `Transcript` already owns a hard-won virtualizer plus the\n * scroll-regime rules it needs. This renders into that shell when the rows are\n * settled, rather than growing a second copy of that logic.\n */\nexport interface TerminalTranscriptProps {\n state: TranscriptState\n /** Builds the download URL for a delivered file. */\n fileUrl?: (path: string) => string\n /** Cell metrics, in whole pixels. See {@link TerminalSurface}. */\n fontSize?: number\n lineHeight?: number\n /** The pointer affordances a real terminal has no way to offer — hover fill,\n * hover-revealed copy. `false` for none. See {@link TerminalAffordances}. */\n affordances?: TerminalAffordances | boolean\n className?: string\n}\n\n/**\n * One transcript item as terminal rows. Exported because the virtualized shell\n * in `agent/Transcript.tsx` renders rows through it: the theme owns how a row\n * looks, that component owns which rows are mounted, and neither needs a copy\n * of the other.\n */\nexport function TerminalItemView({\n item,\n fileUrl,\n}: {\n item: TranscriptItem\n fileUrl?: (path: string) => string\n}) {\n switch (item.kind) {\n case 'user':\n return <UserRow item={item} />\n case 'assistant_text':\n return <AssistantRow item={item} />\n case 'thinking':\n return <ThinkingRow item={item} />\n case 'tool_call':\n return <ToolRow item={item} />\n case 'turn_result':\n return <TurnResultRow item={item} />\n case 'notice':\n return <NoticeRow item={item} />\n case 'file_delivered':\n return <FileRow item={item} href={fileUrl?.(item.path)} />\n default:\n return null\n }\n}\n\n/**\n * A `Task` and everything the subagent it spawned produced, as one row.\n *\n * The same claim the tool-run fold makes, and a stronger one: a subagent is\n * *sixty* rows of somebody else's working — a brief, a dozen greps, its own\n * thinking — and none of it is what you came back to read. What you came back\n * to read is the report, and the report is the model's next sentence. So the\n * whole frame collapses to one line saying what was asked and how big the\n * answer was, and opens in full the moment it is the thing you want.\n *\n * **Always collapsed when unmounted**, and that is load-bearing rather than\n * tidy: `height.ts` predicts this row as exactly one wrapped `taskSummary`, and\n * expansion is component-local state that dies with the row. A row auto-opening\n * because its subagent happens to be running would make its own height\n * unpredictable — which is why the live signal is *in* the collapsed line (the\n * pulse, and a count that climbs) rather than in an open block.\n *\n * The children are the theme's ordinary rows, stepped in behind a rule, and\n * they fold among themselves: a subagent's consecutive tool calls are as much\n * an aside inside its frame as they are in the main thread.\n */\n/**\n * **What the agent was asked** — the sub-agent's brief, clipped to\n * {@link BRIEF_LINES} and pressable for the whole of it.\n *\n * It leads the takeover's frame and the inline task expansion alike, because\n * both are answering the same question and an answer without its question is\n * half a transcript. This is the one row here built from something other than\n * the stream, and it exists for the case the stream does not cover: a\n * **background** agent forwards no brief, where a foreground `Task` forwards a\n * real nested user item that renders as an ordinary prompt row. The callers\n * splice this in only when that row is absent. Codex draws nothing either way —\n * its spawn message is encrypted on the wire, so there is no brief to give.\n *\n * `>` and blue, the prompt's own marker and colour, because that is what this\n * is: somebody's instruction, one level in. The clip is `line-clamp`, which\n * cuts on the same wrapped lines `briefPx` counts — one rule, so the reserved\n * height and the drawn height cannot disagree.\n */\nexport function BriefRow({ text, terminal }: { text: string; terminal?: boolean }) {\n const [open, setOpen] = useState(false)\n if (!terminal) {\n return (\n <div data-slot='brief' className='px-4 py-2 text-body-sm whitespace-pre-wrap text-fg-2'>\n {text}\n </div>\n )\n }\n return (\n <div data-slot='brief'>\n <Pressable onPress={() => setOpen((v) => !v)} expanded={open}>\n <Row glyph='>' glyphTone='blue' tone='dim'>\n <span\n className={cn('whitespace-pre-wrap', !open && 'term-brief-clip')}\n style={open ? undefined : { WebkitLineClamp: BRIEF_LINES }}>\n {text}\n </span>\n </Row>\n </Pressable>\n </div>\n )\n}\n\nexport function TaskRow({\n block,\n fileUrl,\n onOpenSubagent,\n}: {\n block: TaskBlock\n fileUrl?: (path: string) => string\n /** Take over the panel with this sub-agent's own frame. Absent draws no\n * affordance at all — the plain renderer has no surface to take over, and an\n * action that opens nothing is worse than no action. */\n onOpenSubagent?: (toolUseId: string) => void\n}) {\n const [open, setOpen] = useState(false)\n const reveal = useRevealOnOpen(open)\n const children = useMemo(() => taskChildItems(block), [block])\n // Only when the sub-agent's own stream carries no brief — a foreground Task\n // forwards one as a real user item, and two spellings of one instruction is\n // worse than none. See `taskBrief`.\n const brief = children.some((item) => item.kind === 'user') ? undefined : taskBrief(block.task)\n const busy = taskBusy(block.task, children)\n const failed = taskFailed(block.task)\n const pulse = usePulse(busy)\n\n const row = (\n <div ref={reveal} className={open ? 'term-open' : undefined}>\n <Pressable onPress={() => setOpen((v) => !v)} expanded={open}>\n {/* A marker, where a folded run of calls gets none: a run is an aside,\n but delegating a piece of the work is something the model *did*, and\n the row stands for the whole of it. The body is `taskSummary`\n verbatim — it is the string `height.ts` wraps to size this row, and\n a second spelling here would be a second height. */}\n {/* Green means sub-agent, and it means it here for the same reason it\n means it on the rail: every other colour is spoken for — blue is\n you, white is the answer, red is an alarm, magenta is your bookmark,\n yellow is the session waiting on you (see `terminal.css`). The\n *body* is green and the marker is not: a green glyph already means\n \"wrote to the workspace\" a few rows down, and one colour cannot mean\n two things in the same gutter. Failure still outranks it — an alarm\n is not a category. */}\n <Row\n glyph={busy ? pulse : '●'}\n glyphTone={failed ? 'red' : busy ? 'mark' : 'dim'}\n tone={failed ? 'red' : 'green'}>\n {taskSummary(block.task, children)}\n </Row>\n </Pressable>\n {open ? (\n // `term-nested` and not the shell's cards-era `border-l-2 pl-3`: this\n // sits on the open block's wash, where that border token is invisible,\n // and its 14px would take every nested marker off the cell grid.\n <div className='term-nested'>\n {/* The brief leads the children for the same reason it leads the\n frame: the instruction, then the work. */}\n {brief ? <BriefRow text={brief} terminal /> : null}\n {block.children.map((leaf, index) => (\n <Fragment key={leaf.key}>\n {index > 0 && blockNeedsBlank(block.children[index - 1]!, leaf) ? <Blank /> : null}\n {'run' in leaf ? (\n <ToolRunRow items={leaf.run} />\n ) : (\n <TerminalItemView item={leaf.item} fileUrl={fileUrl} />\n )}\n </Fragment>\n ))}\n </div>\n ) : null}\n </div>\n )\n // Wrapped only when there is somewhere to go. `WithActions` is a no-op when\n // affordances are off, but wrapping unconditionally would still put an\n // \"open\" glyph on a renderer that cannot honour it.\n return onOpenSubagent === undefined ? (\n row\n ) : (\n <WithActions actions={<OpenSubagentAction onOpen={() => onOpenSubagent(block.task.id)} />}>\n {row}\n </WithActions>\n )\n}\n\n/** When the current run began — the clock the working line counts from. Held\n * here, not in the row, because the row comes and goes within a single turn (it\n * hides the moment text streams) and a clock restarting at every tool call\n * would be measuring the wrong thing. */\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/** Is the model between outputs? Only then does the working line show — while\n * text is streaming, the text itself is the evidence. */\nfunction working(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 true\n}\n\nexport function TerminalTranscript({\n state,\n fileUrl,\n fontSize,\n lineHeight,\n affordances,\n className,\n}: TerminalTranscriptProps) {\n const runStartedAt = useRunStart(state.status)\n const blocks = useMemo(() => terminalBlocks(state.items), [state.items])\n return (\n <TerminalSurface\n fontSize={fontSize}\n lineHeight={lineHeight}\n affordances={affordances}\n // One cell of breathing room at each edge, and the value a full-bleed\n // band cancels so its wash reaches the scroller's edge.\n bleed='1ch'\n className={cn('term-transcript', className)}>\n {blocks.map((block, index) => (\n <Fragment key={block.key}>\n {index > 0 && blockNeedsBlank(blocks[index - 1]!, block) ? <Blank /> : null}\n {'run' in block ? (\n <ToolRunRow items={block.run} />\n ) : 'item' in block ? (\n <TerminalItemView item={block.item} fileUrl={fileUrl} />\n ) : (\n <TaskRow block={block} fileUrl={fileUrl} />\n )}\n </Fragment>\n ))}\n {working(state) ? (\n <>\n {state.items.length > 0 ? <Blank /> : null}\n <WorkingRow\n label={state.status === 'starting' ? 'Starting…' : 'Working…'}\n startedAt={runStartedAt}\n tokens={state.contextUsage?.totalTokens}\n />\n </>\n ) : null}\n </TerminalSurface>\n )\n}\n","import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { defaultRangeExtractor, useVirtualizer, type Range } from '@tanstack/react-virtual'\nimport { useStickToBottomContext } from 'use-stick-to-bottom'\nimport type { MessageAttachment, PermissionRequest } 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 { resolveAffordances, type TerminalAffordances } from '../terminal/affordances.tsx'\nimport { ToolRunRow, WorkingRow, parentOf, terminalBlocks } from '../terminal/items.tsx'\nimport { subagentItems, type ToolCallItem } from '../terminal/blocks.ts'\nimport { taskBrief } from '../terminal/tool-run.ts'\nimport { taskBusy } from '../terminal/tool-run.ts'\nimport { briefPx, estimateBlockPx } from '../terminal/height.ts'\nimport { TerminalScrubber } from '../terminal/scrubber.tsx'\nimport { gapBefore, positionInRow, rowIndexForItem, type TranscriptRow } from './transcript-rows.ts'\nimport { useHeightEpoch } from './use-height-epoch.ts'\nimport { useTranscriptJumps } from './use-transcript-jumps.ts'\nimport { Row } from '../terminal/row.tsx'\nimport { TerminalSurface } from '../terminal/surface.tsx'\nimport { BriefRow, TaskRow, TerminalItemView } from '../terminal/TerminalTranscript.tsx'\nimport {\n ROW_GAP,\n TranscriptVariantProvider,\n type TranscriptDensity,\n type TranscriptVariant,\n} from './transcript-variant.tsx'\n\nfunction TurnResultRow({ item }: { item: Extract<TranscriptItem, { kind: 'turn_result' }> }) {\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 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 terminal,\n}: {\n item: TranscriptItem\n fileUrl?: (path: string) => string\n attachmentUrl?: (attachmentId: string) => string\n hostImage?: (path: string) => Promise<string | undefined>\n terminal?: boolean\n}) {\n // The terminal theme is a renderer, not a branch: it draws every kind itself,\n // so the switch below is never reached under it.\n if (terminal) return <TerminalItemView item={item} fileUrl={fileUrl} />\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, terminal }: { line: string; since?: number; terminal?: boolean }) {\n const away = since === undefined ? undefined : formatRelativeTime(since)\n const text = away ? `${line} · last here ${away}` : line\n\n // Under the terminal theme it is a Row like everything else, and that is not\n // cosmetic: the cards markup measures 42px against an 18px line, so it was the\n // one row in the transcript sitting off the grid — and it shifted *every row\n // below it* by the remainder, which is precisely the failure the whole-multiple\n // rule exists to prevent. It was invisible until a fixture carried a recap\n // splice. Being a real row also makes it exactly computable, so the height\n // calculator loses its last estimated constant.\n if (terminal) {\n return (\n <div data-slot='recap'>\n <Row glyph='※' glyphTone='faint' tone='faint'>\n recap: {text}\n </Row>\n </div>\n )\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 * **Nothing on this surface animates its scroll position.** VS Code does not —\n * click its editor scrollbar and it jumps — and neither does a terminal, which\n * is the article this transcript is drawing. Every travel a reader ever\n * complained about here was an animation we asked for.\n *\n * What used to be here was `useSettled`: a latch deciding smooth-vs-instant for\n * the follow spring, with a quiet window, a \"silence before the first row does\n * not count\" guard and a live-status gate — all of it apparatus for a\n * smooth-scroll bug (the attach replays hundreds of rows, and animating that\n * turned opening a session into a several-second journey). With no smooth mode\n * left there is nothing for it to decide, so the whole thing is gone rather\n * than pinned to `false`.\n *\n * The two remaining writers of `scrollTop` — the follow spring and the\n * virtualizer's size-change correction — are unchanged and still split by\n * regime; `Conversation` itself is now hardwired to `instant` on both `initial`\n * and `resize`.\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 return (\n <div className='mb-1 flex flex-wrap justify-start gap-1.5'>\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/**\n * The terminal theme's root, when that is the variant, and a passthrough when it\n * is not.\n *\n * It has to live *inside* the scroller's content element rather than around the\n * whole `Conversation`: `--term-line` and `1ch` are inherited, and the rows are\n * rendered by the virtualizer several levels down. Wrapping the scroller instead\n * would work identically for the cells and break the full-bleed bands, whose\n * negative margins are measured against this element's padding.\n */\nfunction TerminalShell({\n active,\n fontSize,\n lineHeight,\n affordances,\n children,\n}: {\n active: boolean\n fontSize?: number\n lineHeight?: number\n affordances?: TerminalAffordances | boolean\n children: ReactNode\n}) {\n if (!active) return <>{children}</>\n return (\n <TerminalSurface\n fontSize={fontSize}\n lineHeight={lineHeight}\n affordances={affordances}\n bleed='1ch'\n className='term-transcript'>\n {children}\n </TerminalSurface>\n )\n}\n\n/** Already read: present, legible, and visibly behind you. */\nfunction read(boundary: number | undefined, index: number): boolean {\n return boundary !== undefined && index < boundary\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.\n *\n * **Except inside that sub-agent's own frame**, where those same items are the\n * top level and there is no main thread to be an aside from — stepping every row\n * in would draw a rule down the whole surface saying \"this happened somewhere\n * else\" about the only thing on screen. */\nfunction nestedClass(item: TranscriptItem, frameParentId?: string): string | undefined {\n const parent = 'parentToolUseId' in item ? item.parentToolUseId : undefined\n const nested = parent != null && parent !== frameParentId\n return nested ? 'border-l-2 border-border pl-3' : undefined\n}\n\n// The virtual row model — what a row *is*, the spacing rule, and the\n// item-index → row-index mapping — lives in `transcript-rows.ts`; re-exported\n// here because this file is where consumers have always found them.\nexport { rowIndexForItem, type TranscriptRow } from './transcript-rows.ts'\n\n/**\n * A prompt row's sticky lane — the strip spanning its turn, leading with the\n * one-line pinned **head** (see the pinned-prompt comment in\n * {@link TranscriptRows}).\n *\n * The head starts `visibility: hidden` and shows only while actually stuck —\n * an overlay that is visible in flow would sit on the real row's first line\n * and swallow its selection highlight, which reads as \"the first line cannot\n * be selected\". CSS cannot ask \"am I stuck?\", so a 1px sentinel at the head's\n * engage threshold (the line's own y) feeds an IntersectionObserver: sentinel\n * above the scrollport top → stuck. Transition-only callbacks — this adds no\n * per-scroll work, and the pin itself is still the compositor's.\n */\nfunction StickyPromptLane({\n top,\n height,\n gapClass,\n scrollRoot,\n index,\n measureRef,\n content,\n}: {\n top: number\n height: number\n gapClass?: string | false\n scrollRoot: HTMLElement | null\n index: number\n measureRef: (element: HTMLDivElement | null) => void\n content: ReactNode\n}) {\n const headRef = useRef<HTMLDivElement | null>(null)\n const sentinelRef = useRef<HTMLDivElement | null>(null)\n useEffect(() => {\n const head = headRef.current\n const sentinel = sentinelRef.current\n if (!head || !sentinel || !scrollRoot) return\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return\n // Above the scrollport, not merely out of it — a lane still below the\n // viewport has its sentinel non-intersecting too.\n const stuck =\n !entry.isIntersecting &&\n entry.boundingClientRect.top < (entry.rootBounds?.top ?? 0)\n head.toggleAttribute('data-stuck', stuck)\n },\n { root: scrollRoot },\n )\n observer.observe(sentinel)\n return () => observer.disconnect()\n }, [scrollRoot])\n return (\n <div data-sticky-lane='' className='absolute inset-x-0' style={{ top, height }}>\n {/* The head rides in its own absolutely positioned sub-lane rather than\n in flow with a cancelled footprint: sticky confinement clamps the\n *margin* box, and a negative bottom margin shrinks that box to zero\n height — the head then overshoots the lane's end by its own height,\n which put two pinned prompts on screen at once during the handoff.\n Out of flow, the border box is what gets clamped, and the push-off\n lands exactly at the lane's bottom edge. */}\n <div data-sticky-headlane='' aria-hidden>\n <div ref={headRef} data-sticky-head='' className={gapClass || undefined}>\n {content}\n </div>\n </div>\n <div\n ref={sentinelRef}\n aria-hidden\n className='absolute left-0 w-px'\n style={{ top: gapClass ? 'var(--term-line)' : 0, height: 1 }}\n />\n <div ref={measureRef} data-index={index} className={gapClass || undefined}>\n {content}\n </div>\n </div>\n )\n}\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 terminal,\n replaying,\n stickyPrompt,\n frameParentId,\n onOpenSubagent,\n gap,\n fontSize,\n lineHeight,\n items,\n pendingApprovals,\n scrubber,\n scrubberMarks,\n affordances,\n fileUrl,\n attachmentUrl,\n hostImage,\n jumpToRecapRef,\n repinRef,\n reveal,\n}: {\n rows: TranscriptRow[]\n boundary: number | undefined\n since: number | undefined\n /** The terminal theme draws its own rows; see {@link TranscriptItemView}. */\n terminal: boolean\n /** The replay hold (see {@link TranscriptProps.replaying}) — read here only\n * for its falling edge, which needs a pre-paint pin. */\n replaying: boolean\n /** Pin the prompt of the turn being read to the top of the scroller. */\n stickyPrompt: boolean\n /** The inter-row gap for this variant and density (`ROW_GAP`). */\n gap: { className?: string; px: number }\n /** The terminal cell, when the host set one — only read as a signal that the\n * height epoch below must re-measure; the epoch's numbers come from the DOM. */\n fontSize?: number\n lineHeight?: number\n /** The transcript items — the scrubber's marks and peeks render from these,\n * never from the DOM (the row a mark points at is usually unmounted). */\n items: readonly TranscriptItem[]\n pendingApprovals: readonly PermissionRequest[]\n /** Mount the overview-ruler rail (terminal theme only). */\n scrubber?: boolean\n scrubberMarks?: readonly number[]\n /** Set when these rows are a sub-agent's frame — the id everything here was\n * produced inside. Only `nestedClass` needs it: inside the frame those items\n * are the top level and must not be stepped in. */\n frameParentId?: string\n onOpenSubagent?: (toolUseId: string) => void\n affordances?: TerminalAffordances | boolean\n fileUrl?: (path: string) => string\n attachmentUrl?: (attachmentId: string) => string\n hostImage?: (path: string) => Promise<string | undefined>\n jumpToRecapRef?: RefObject<(() => void) | null>\n repinRef?: RefObject<(() => void) | null>\n reveal?: { toolUseId: string; nonce: number }\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 // Which rows *could* be the pinned prompt. Recomputed only when the row list\n // changes, so the per-scroll work below is a walk over prompts rather than\n // over the transcript.\n const promptRows = useMemo(\n // Top-level prompts only: a subagent's brief is a `user` item too, and one\n // that escaped absorption (an orphan) must not become the pinned prompt —\n // it is not what the answer on screen belongs to.\n () =>\n rows.flatMap((row, index) =>\n 'item' in row && row.item.kind === 'user' && parentOf(row.item) === undefined\n ? [index]\n : [],\n ),\n [rows],\n )\n // The pinned row must stay mounted even when it is far above the window, so\n // it is forced into the virtual range — see `rangeExtractor` below, which\n // reads these refs rather than closing over render values: it is called from\n // inside the virtualizer's own range pass, where a closure would be a stale\n // render's.\n const pinRef = useRef<{ enabled: boolean; promptRows: readonly number[] }>({\n enabled: false,\n promptRows: [],\n })\n pinRef.current = { enabled: terminal && stickyPrompt, promptRows }\n useEffect(() => {\n setScrollElement(stick.scrollRef.current)\n }, [stick.scrollRef])\n\n // A composer that grows steals a line from the transcript.\n //\n // The composer and the transcript are siblings in the panel's flex column, so\n // typing a newline shrinks this scroller by one line. \"At the bottom\" is a\n // `scrollTop`, and that number stops meaning the bottom the moment the\n // viewport changes height — so the last row, the one you were reading, slides\n // under the fold as you type.\n //\n // `use-stick-to-bottom` cannot catch this: its ResizeObserver observes the\n // **content** element (`.observe(content)` in its `useStickToBottom`), and\n // here the content is unchanged and the *scroller* moved. Nor does the browser\n // help — scrollTop is untouched, so no scroll event fires and nothing\n // recomputes. Hence an observer of our own, on the scroller's own box.\n //\n // The guard is the whole feature: re-pin **only when already pinned**, or\n // every newline yanks a reader who had deliberately scrolled up. That state\n // lives in here, which is why this is in `Transcript` and not `SessionPanel`.\n // Reading `stick.state.isAtBottom` (the library's live object, not the\n // rendered boolean) is safe precisely because of the paragraph above: no\n // scroll event fired, so the flag still holds the pre-resize answer.\n //\n // This is not a third writer of `scrollTop` — it presses the follow spring's\n // own button, instantly, which is what the spring would have done had it\n // noticed. The pinned-suppresses-corrections regime below is untouched.\n useEffect(() => {\n if (!scrollElement) return\n let last = scrollElement.clientHeight\n const observer = new ResizeObserver(() => {\n const height = scrollElement.clientHeight\n if (height === last) return\n last = height\n if (stick.state.isAtBottom) void stick.scrollToBottom('instant')\n })\n observer.observe(scrollElement)\n return () => observer.disconnect()\n }, [scrollElement, stick])\n\n // The replay hold's reveal must paint already at the bottom, and the follow\n // spring cannot make that true: even `scrollToBottom('instant')` defers its\n // write behind a `requestAnimationFrame`, one frame after the reveal's paint\n // — so the first visible frame showed the tail a burst shy of the bottom and\n // then hopped (measured: revealTop 33037 against final 34459 on the 600-row\n // fixture). A layout effect runs after the commit that removed the hold's\n // visibility and before its paint, so this write lands in the very frame the\n // transcript appears. It presses the library's own `state.scrollTop` setter\n // — which records the write in `ignoreScrollToTop`, so the scroll handler\n // knows it for its own — not a raw `scrollTop`, and only on the hold's\n // falling edge, only while pinned; it is the pin's own move made a frame\n // early, not a third writer.\n const wasReplaying = useRef(replaying)\n useLayoutEffect(() => {\n const was = wasReplaying.current\n wasReplaying.current = replaying\n if (!was || replaying) return\n if (!stick.state.isAtBottom) return\n stick.state.scrollTop = stick.state.calculatedTargetScrollTop\n }, [replaying, stick])\n\n // The height epoch — see `use-height-epoch.ts`. Owned here because this\n // component owns the virtualizer the heights feed.\n const rowsRef = useRef<HTMLDivElement | null>(null)\n const epoch = useHeightEpoch({ terminal, fontSize, lineHeight, rowsRef })\n\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; a\n // measurement replaces them the moment a row mounts. Under the terminal\n // theme they are *computed* (`terminal/height.ts`): the theme's one line\n // height and one cell make a row's height derivable from its item, so the\n // scrollbar is honest before rows mount and `scrollToIndex` sums real\n // sizes instead of accumulating error over unmeasured spans. The gap rides\n // the same estimate because it is real height on the same measured element\n // — one line, decided per pair by `gapBefore`, exactly as the renderer\n // applies the class. The recap row is a Row here too, so it is one line and\n // exact — it used to be the cards markup, which measured 42px against an\n // 18px line and pushed every row below it off the grid.\n // Cards keep the flat constant: they vary too much for any constant to be\n // right, so it is merely the order of magnitude — and the calculator has\n // no claim there (padding scales, borders, a proportional face).\n estimateSize: (index) => {\n if (terminal && epoch) {\n const row = rows[index]\n const gapPx = index > 0 && gapBefore(rows, index) ? epoch.line : 0\n if (row && 'text' in row && row.key === 'brief')\n // Collapsed by default and clipped to BRIEF_LINES, so its height is\n // known before it mounts — the same discipline the task row keeps by\n // always being collapsed when unmounted. Expanding is local state on\n // a mounted row, which the virtualizer re-measures.\n return briefPx(row.text, epoch) + gapPx\n if (row && !('line' in row))\n return estimateBlockPx(row, epoch) + gapPx\n return epoch.line + gapPx // recap: one Row, one line\n }\n return (terminal ? 36 : 100) + gap.px\n },\n overscan: 8,\n getItemKey: (index) => rows[index].key,\n // The sticky row, forced into the range. This is the virtualizer's own\n // sticky-header seam: without it the pinned prompt's lane unmounts the\n // moment it leaves the window, which is exactly when it is doing its job.\n // The active prompt — the last one starting at or above the fold — is\n // computed *here*, from the instance's own offset, rather than in the\n // render body: the range pass runs before the render that would refresh a\n // ref, so a value computed outside this callback is one scroll event\n // stale, and a long programmatic jump would leave the pinned row unmounted\n // until the next scroll. (`virtualizer` is safe to close over: the hook\n // returns one stable instance for the component's lifetime.)\n rangeExtractor: useCallback((range: Range) => {\n const indexes = new Set(defaultRangeExtractor(range))\n const { enabled, promptRows: prompts } = pinRef.current\n if (enabled) {\n const offset = virtualizer.scrollOffset ?? 0\n let pinned = -1\n for (const index of prompts) {\n if ((virtualizer.measurementsCache[index]?.start ?? Infinity) <= offset) pinned = index\n else break\n }\n if (pinned >= 0) indexes.add(pinned)\n }\n return [...indexes].sort((a, b) => a - b)\n }, []),\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 // A new epoch means every remembered size is against the wrong metrics —\n // the *measurements* included, which were taken at the old width. Dropping\n // both together is the whole point: better estimates layered under\n // stale-width measurements would be worse than either alone.\n //\n // The second half is not optional: `measure()` clears the size cache and a\n // row re-enters it only when its ResizeObserver fires — which needs a *size\n // change*. A mounted row whose height happens to survive the width change\n // (short lines that never rewrap) would keep its estimate forever, and\n // wherever the estimate is off the transcript grows a phantom tail — 2,052px\n // of scrollable nothing after one sidebar toggle, on a real session. So the\n // mounted rows are fed straight back in.\n useEffect(() => {\n if (!terminal || !epoch) return\n virtualizer.measure()\n const container = rowsRef.current\n if (!container) return\n // Two sharp edges in the re-feed, both learned the hard way. Order:\n // `resizeItem` diffs a measure against `measurementsCache`, and straight\n // after `measure()` that array is still the pre-wipe one — an unchanged\n // row diffs to zero against its own old measurement and the write is\n // skipped; recomputing first (any measurement read does it) rebuilds the\n // array from estimates, so the diff is real again. And `resizeItem`\n // directly, not `measureElement(element)`: the latter is gated on the\n // scroll state and silently drops a measure that lands while a scroll is\n // still hot — which a resize's own scroll anchoring makes routine.\n virtualizer.getTotalSize()\n for (const element of container.querySelectorAll<HTMLElement>('[data-index]')) {\n const index = Number(element.getAttribute('data-index'))\n if (Number.isInteger(index) && index >= 0)\n virtualizer.resizeItem(index, element.getBoundingClientRect().height)\n }\n }, [terminal, epoch, virtualizer])\n\n // The jump machinery — the aim loop, the catch-up strip's jump, the re-pin\n // — lives in `use-transcript-jumps.ts`; every jump on this surface comes\n // through the one function it returns.\n const jumpToRow = useTranscriptJumps({\n rows,\n terminal,\n stickyPrompt,\n epoch,\n promptRows,\n scrollElement,\n rowsRef,\n virtualizer,\n stick,\n jumpToRecapRef,\n repinRef,\n })\n\n // Reveal a tool call from outside the transcript — a sub-agent picked in a\n // sessions list, whose `Task` row is the thing the reader asked for.\n //\n // Keyed on the **nonce**, never on the id: asking for the same sub-agent twice\n // is a second request, and a props-equal effect would answer only the first.\n // The lookup goes through `rowIndexForItem` for the reason that function\n // exists — a row covers a *membership*, not a contiguous span, so a Task's id\n // resolves to the folded row that absorbed it rather than to a position. That\n // also makes a nested child's id work, which is what a client holding only a\n // `parentToolUseId` can offer.\n //\n // `'start'`, like the recap seam and the scrubber's marks: the sub-agent's\n // work runs *downward* from its row, so the reader wants the screen below it.\n const revealNonce = reveal?.nonce\n const revealId = reveal?.toolUseId\n useEffect(() => {\n if (revealId === undefined) return\n const itemIndex = items.findIndex(\n (item) => item.kind === 'tool_call' && item.id === revealId,\n )\n // Not here: a compaction, a `/clear`, or simply a client whose list knows\n // about a Task this transcript has not replayed yet. Staying put beats\n // jumping somewhere arbitrary.\n if (itemIndex < 0) return\n jumpToRow(rowIndexForItem(rows, itemIndex), 'start')\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the nonce IS the trigger\n }, [revealNonce])\n\n // The scrubber. Interactivity follows the hover affordance — with\n // `affordances={false}` the rail is passive paint (pointer-events off) and\n // the native scrollbar stays; interactive, the rail IS the scrollbar, so the\n // native one is hidden via an attribute on the scroll element.\n const scrubInteractive = resolveAffordances(affordances).hover\n const recapIndex = rows.findIndex((row) => row.key === 'recap')\n const recapRow =\n recapIndex >= 0\n ? { rowIndex: recapIndex, label: (rows[recapIndex] as { line: string }).line }\n : undefined\n useEffect(() => {\n if (!terminal || !scrubber || !scrubInteractive || !scrollElement) return\n scrollElement.setAttribute('data-term-scrubber-host', '')\n return () => scrollElement.removeAttribute('data-term-scrubber-host')\n }, [terminal, scrubber, scrubInteractive, scrollElement])\n\n /**\n * The pinned prompt: the prompt's **first line**, held at the top of the\n * scroller.\n *\n * One line, not the row: a pasted twenty-line prompt pinned whole covers the\n * viewport and buries the very answer being read under it. So what pins is a\n * **head** — one line tall, `overflow: hidden` — whose content is the same\n * row rendered again, laid exactly over the real row's first line. It is a\n * duplicate, which an earlier design here rejected — but that rejection was\n * of a *separate header* with its own padding and its own idea of the\n * gutter. This copy is the same component in the same column at the same\n * width, so it aligns with the row beneath by construction, and while the\n * row is in flow the overlay is pixel-identical and invisible. It takes no\n * pointer events and is `aria-hidden`: the real row owns interaction and\n * the accessibility tree; the head is paint.\n *\n * The pin itself is the browser's, not ours. Each prompt row renders inside a\n * **lane**: an absolutely positioned strip spanning from the prompt's start to\n * the next prompt's (its turn), with the head `position: sticky` inside it\n * (its flow footprint cancelled by a negative bottom margin, so the real row\n * sits at the lane's top as if the head were not there). The compositor does\n * the pinning and the lane's bottom edge does the push-off — `sticky` is\n * inert on an absolutely positioned element, but works unchanged on a child\n * *of* one, confined to the lane's box. An earlier version clamped the row's\n * transform from render instead, and paid for it every frame: any JS-written\n * pin — React render or a raw scroll handler — runs behind the compositor\n * thread, so the row wobbled under momentum scroll. With the lane there is\n * no per-scroll JS and no lag.\n *\n * Which row — the last prompt at or above the fold, the question the answer\n * on screen belongs to — falls out of the geometry: only the lane spanning\n * the viewport top has its child stuck; every earlier lane has pushed its row\n * off its bottom edge, every later one hasn't reached the top.\n *\n * The one job left to JS is keeping that row *mounted* once it scrolls far\n * above the virtual window — which is exactly when it is working. That lives\n * in the `rangeExtractor` above.\n */\n const measurements = virtualizer.measurementsCache\n\n return (\n <div\n ref={rowsRef}\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 // The inter-row gap, folded into each row so the measured height\n // carries it: flex `gap` cannot reach absolutely positioned rows,\n // and a pixel constant for the virtualizer's `gap` option would\n // drift from the rem the layout is set in. On the measured wrapper,\n // not the row div, so a nested row's left border still breaks\n // across the gap as it did under flex. Skipped for the first row —\n // a gap above it would be padding, not spacing.\n // Every variant but terminal spaces every row alike. Terminal\n // asks whether the pair belongs together — a tool call and its\n // output get no blank line, exactly as the CLI leaves none.\n const gapClass =\n virtualRow.index > 0 && (!terminal || gapBefore(rows, virtualRow.index))\n ? gap.className\n : undefined\n const content =\n 'run' in row ? (\n <div className={cn(read(boundary, row.index) && 'opacity-45')}>\n <ToolRunRow items={row.run} />\n </div>\n ) : 'item' in row ? (\n <div className={cn(read(boundary, row.index) && 'opacity-45', nestedClass(row.item, frameParentId))}>\n <TranscriptItemView\n item={row.item}\n fileUrl={fileUrl}\n attachmentUrl={attachmentUrl}\n hostImage={hostImage}\n terminal={terminal}\n />\n </div>\n ) : 'text' in row && row.key === 'brief' ? (\n <BriefRow text={row.text} terminal={terminal} />\n ) : 'line' in row ? (\n <RecapRow line={row.line} since={since} terminal={terminal} />\n ) : (\n // A task block. No `nestedClass` here: the rule belongs *inside*\n // the row, around the children it opens onto — the collapsed line\n // is the main thread's, and stepping it in would say a `Task` call\n // happened somewhere else.\n <div className={cn(read(boundary, row.index) && 'opacity-45')}>\n <TaskRow block={row} fileUrl={fileUrl} onOpenSubagent={onOpenSubagent} />\n </div>\n )\n // A prompt row's sticky lane — see the pinned-prompt comment above.\n // The lane is sized to the turn; the sticky **head** (one clipped\n // line, the same content again) comes first with its flow footprint\n // cancelled, and the *measured* element is the real row after it, so\n // the virtualizer's heights are untouched by either. Both carry the\n // gap class: the row because the gap is part of its measured height,\n // the head so its one visible line sits on the same y while in flow —\n // the pin parks that padding above the viewport edge when stuck.\n // Positioned with `top`, NOT the translate every other row gets:\n // `position: sticky` is resolved at layout time and a transform is\n // paint-only, so under a translate the head would stick against the\n // lane's un-translated box at the top of the list — observed as the\n // row clamped to its lane's bottom edge, never pinning at all.\n // Same predicate as `promptRows` above — the lane and the forced range\n // must agree on which rows are prompts.\n if (\n terminal &&\n stickyPrompt &&\n 'item' in row &&\n row.item.kind === 'user' &&\n parentOf(row.item) === undefined\n ) {\n const next = promptRows.find((index) => index > virtualRow.index)\n const laneEnd =\n next === undefined\n ? virtualizer.getTotalSize()\n : (measurements[next]?.start ?? virtualRow.start)\n return (\n <StickyPromptLane\n key={row.key}\n top={virtualRow.start}\n height={Math.max(laneEnd - virtualRow.start, 0)}\n gapClass={gapClass}\n scrollRoot={scrollElement}\n index={virtualRow.index}\n measureRef={virtualizer.measureElement}\n content={content}\n />\n )\n }\n return (\n <div\n key={row.key}\n ref={virtualizer.measureElement}\n data-index={virtualRow.index}\n className={cn('absolute inset-x-0 top-0', gapClass)}\n style={{ transform: `translateY(${virtualRow.start}px)` }}>\n {content}\n </div>\n )\n })}\n {/* The overview ruler, portalled beside the scroll element rather than\n rendered as content: it must not scroll with the rows it maps. It\n lives here, not in the shell above, because everything it draws from\n — the virtualizer's offsets, the epoch, the row list, the jump — is\n this component's. The portal target is the Conversation root\n (`relative`), the same containing block the scroll button uses. */}\n {terminal && scrubber && scrollElement?.parentElement\n ? createPortal(\n <TerminalScrubber\n items={items}\n pendingApprovals={pendingApprovals}\n recapRow={recapRow}\n bookmarks={scrubberMarks ?? []}\n frameParentId={frameParentId}\n rowIndexFor={(itemIndex) => rowIndexForItem(rows, itemIndex)}\n positionInRow={(itemIndex) => positionInRow(rows, itemIndex)}\n // The public memoized measurements array — `getTotalSize()` just\n // above refreshed it, and with the calculator feeding\n // `estimateSize` these starts are honest for unmounted rows too.\n offsetOfRow={(rowIndex) => virtualizer.measurementsCache[rowIndex]?.start ?? 0}\n sizeOfRow={(rowIndex) => virtualizer.measurementsCache[rowIndex]?.size ?? 0}\n totalSize={virtualizer.getTotalSize()}\n scrollOffset={virtualizer.scrollOffset ?? 0}\n viewportH={virtualizer.scrollRect?.height ?? 0}\n // To the top: a mark is where you start reading, not the middle of\n // what you want to see.\n onJumpToRow={(rowIndex) => jumpToRow(rowIndex, 'start')}\n interactive={scrubInteractive}\n fontSize={fontSize}\n lineHeight={lineHeight}\n />,\n scrollElement.parentElement,\n )\n : null}\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 `terminal`,\n * which is not a set of branches in these components but its own renderer.\n * See {@link TranscriptVariant}.\n */\n variant?: TranscriptVariant\n /**\n * How much air each row gets: `comfortable` (default — a blank line between\n * messages, as the Claude Code CLI does) or `compact`. Independent of\n * {@link TranscriptVariant}. See {@link TranscriptDensity}.\n */\n density?: TranscriptDensity\n /** Terminal theme only: the character cell, in whole pixels. See\n * {@link TerminalSurface}. */\n fontSize?: number\n lineHeight?: number\n /** Terminal theme only: the pointer affordances a real terminal cannot offer.\n * `false` for none. See {@link TerminalAffordances}. */\n affordances?: TerminalAffordances | boolean\n /** Terminal theme only: hold the prompt of the turn being read at the top of\n * the scroller. The *real* row is pinned, not a copy — see `TranscriptRows`. */\n stickyPrompt?: boolean\n /**\n * Terminal theme only: mount the overview-ruler scrubber — a 2ch rail of\n * marks (your prompts, each turn's response and result as one mark, errors,\n * the pending approval, the catch-up boundary) that replaces the native\n * scrollbar. Ignored under `cards`: the rail's positions ride the height\n * calculator, which has no claim there. With `affordances={false}` the rail\n * degrades to passive paint — no drag, peek or click — and the native\n * scrollbar stays. See {@link TerminalScrubber}.\n */\n scrubber?: boolean\n /**\n * Bookmarked item indices, painted as full-width marks on the rail. Paint\n * only, deliberately: the store — and the affordance that writes it — is\n * the client's, the way the unread watermarks are, not the panel's.\n */\n scrubberMarks?: readonly number[]\n /**\n * The attach replay is still landing (`useClaudeSession().replaying`): rows\n * render, measure and pin exactly as normal but nothing paints, and a loading\n * line shows in their place; when it flips false the settled tail appears in\n * one frame. Hiding is by *visibility*, never by not mounting — see the\n * comment at the render site. Optional: an embedder that never passes it\n * gets today's behaviour, and a short or empty session holds for no visible\n * time at all (the frame between attach and replay-complete).\n */\n replaying?: boolean\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 /**\n * Filled with a closure that re-pins the transcript to the bottom, so a host\n * can resume following after the reader has scrolled away. The panel presses\n * it on send: choosing to say something is choosing to watch what happens\n * next, and a transcript left parked where you were reading makes a sent\n * message look like it did nothing at all.\n */\n repinRef?: RefObject<(() => void) | null>\n /**\n * Scroll a tool call into view — bump `nonce` to ask again for the same one.\n *\n * The seam a *list* needs: sub-agent work is nested inside the `Task` call\n * that spawned it, so \"open that sub-agent\" can only ever mean \"take me to its\n * row\". A `parentToolUseId` works here as well as the Task's own id, since the\n * lookup resolves an absorbed child to the row that folded it.\n *\n * A prop rather than a ref (the shape `jumpToRecapRef` uses) because the asker\n * is outside this webview entirely and the request travels as data; a ref\n * would need a live closure at the other end of a postMessage bridge.\n */\n reveal?: { toolUseId: string; nonce: number }\n /**\n * Render **only** the work one sub-agent did, rather than the conversation —\n * the sub-agent takeover's frame.\n *\n * Membership is `subagentItems` (`terminal/blocks.ts`), which is also the rule\n * iOS will mirror: everything the agent produced, and not the spawning `Task`\n * call itself, which *is* the frame rather than a row in it.\n *\n * Features are switched off internally whenever it is set, and the gate lives\n * here rather than at the call site on purpose: each is keyed to a\n * **full-transcript item index**, so a host that passed a frame and a catch-up\n * boundary together would not be making a strange choice, it would be making\n * an incoherent one. Those are the catch-up boundary and its recap row, the\n * sticky prompt, `reveal`, and the scrubber's **bookmarks** (host indices in\n * full-transcript space).\n *\n * The **scrubber itself stays**, and the distinction is the point: the rail\n * derives every one of its inputs from the rows it is given, and inside a\n * frame those are the sub-agent's own — so it marks that agent's prompts,\n * answers and failures at that agent's offsets. It was originally gated with\n * the marks, on the reasonable-looking argument that they are one feature;\n * they are two, and a fifty-tool agent run is exactly where a rail earns its\n * keep. What stays besides is everything that makes a long stream readable —\n * virtualization, the height epoch, the follow spring, the replay hold.\n */\n frame?: { parentToolUseId: string }\n /**\n * Raise the takeover from a `Task` row. Absent draws no affordance, which is\n * what the plain renderer and the cards variant get: cards folds nothing, so\n * it has no task blocks to hang this on, and reaches the takeover from the\n * sessions list instead.\n */\n onOpenSubagent?: (toolUseId: string) => void\n className?: string\n}\n\nexport function Transcript({\n state,\n fileUrl,\n attachmentUrl,\n canBrowseFiles,\n hostImage,\n variant = 'cards',\n density = 'comfortable',\n fontSize,\n lineHeight,\n affordances,\n stickyPrompt = false,\n scrubber,\n scrubberMarks,\n replaying = false,\n catchUp,\n jumpToRecapRef,\n repinRef,\n reveal,\n frame,\n onOpenSubagent,\n className,\n}: TranscriptProps) {\n const terminal = variant === 'terminal'\n // The frame's own item list, and the single place the takeover's content is\n // decided. Everything below reads `items` rather than `state.items`, so the\n // row build, the empty state and the loader all describe the same surface.\n const items = useMemo(\n () => (frame ? subagentItems(state.items, frame.parentToolUseId) : state.items),\n [state.items, frame],\n )\n // The spawning call, for the header's claim and to tell \"not here yet\" from\n // \"not in this transcript\" — see the frame placeholder below.\n const frameTask = useMemo(\n () =>\n frame\n ? state.items.find(\n (item): item is ToolCallItem =>\n item.kind === 'tool_call' && item.id === frame.parentToolUseId,\n )\n : undefined,\n [state.items, frame],\n )\n const gap = ROW_GAP[variant][density]\n const runStartedAt = useRunStart(state.status)\n // A boundary at (or past) the end means nothing is new — no row, no dimming.\n //\n // That \"past the end\" arm now also covers a `/clear`. The mark a client\n // stored is an item index, and `conversation_reset` empties `items` while\n // `activityCount` stays monotonic (it is an unread cursor, not an item count\n // — a count that went backwards would silence the badge for good against a\n // monotonic watermark store). So a session returned to after a clear has a\n // boundary well past its few fresh rows and gets **no recap row**, which is\n // the honest answer: an index into a conversation that no longer exists\n // cannot say what you missed. Clamping it would land on `items.length` and\n // read as \"nothing is new\" — the same outcome, told less truthfully.\n const boundary =\n !frame && catchUp && catchUp.from > 0 && catchUp.from < state.items.length\n ? catchUp.from\n : undefined\n const recap = useMemo(\n () => (boundary === undefined ? undefined : recapLine(summarizeSince(state, boundary))),\n [state, boundary],\n )\n // What this agent was asked, when the stream does not already say. A\n // foreground `Task` forwards its brief as a real nested user message and it is\n // already the frame's first row; a background agent forwards nothing, and\n // without this the takeover shows an answer with the question missing. Hence\n // the guard rather than an unconditional splice — drawn both ways, the reader\n // would see the same instruction twice.\n const brief = useMemo(\n () =>\n frame && frameTask && !items.some((item) => item.kind === 'user')\n ? taskBrief(frameTask)\n : undefined,\n [frame, frameTask, items],\n )\n const rows = useMemo<TranscriptRow[]>(() => {\n const fold = (from: number, to: number) =>\n terminalBlocks(items.slice(from, to), from, terminal)\n const lead: TranscriptRow[] = brief ? [{ key: 'brief' as const, text: brief }] : []\n if (boundary === undefined || !recap) return [...lead, ...fold(0, items.length)]\n // Each side of the boundary folds separately, so a shell run never spans it:\n // \"what happened while you were away\" must not hide inside a count that also\n // covers what you have already read.\n return [\n ...fold(0, boundary),\n { key: 'recap' as const, line: recap },\n ...fold(boundary, items.length),\n ]\n }, [items, boundary, recap, terminal, brief])\n return (\n <TranscriptVariantProvider value={variant}>\n {/* The replay hold hides by VISIBILITY, never by not mounting. The rows\n must exist and lay out while hidden: the virtualizer measures them,\n the height epoch builds, and the follow pin settles on the real\n bottom — so the reveal is the removal of one style, a single paint of\n an already-settled tail, and the catch-up jump always fires against a\n measured list. (Unmounting instead would replay the entire\n mount-measure-correct churn, visibly, at reveal time.) `visibility`\n is the one hiding property a descendant can turn back ON, which is\n how the loading line below stays visible inside a hidden root — and\n the root is the right scope because the scrubber and the scroll\n button portal/position into it, not into the scroller. */}\n <Conversation className={cn(replaying && 'invisible', className)}>\n <ConversationContent className={cn(terminal && 'gap-0 p-0')}>\n <TerminalShell\n active={terminal}\n fontSize={fontSize}\n lineHeight={lineHeight}\n affordances={affordances}>\n {frame ? (\n // The frame's own empty states, and they are two different facts.\n // A task that is present with nothing under it yet is simply an\n // agent that has not spoken — the loader below says so. A task the\n // transcript does not have is either still replaying (say nothing,\n // the hold is up) or genuinely absent: a `/clear` retired the\n // conversation it lived in, or the id was never this session's.\n // **Never auto-exit on that** — navigating out from under a reader\n // is worse than one honest line they can leave when they choose.\n frameTask === undefined && !replaying ? (\n <div className={cn(terminal ? 'term-row text-fg-4' : 'p-4 text-body-sm text-fg-4')}>\n This sub-agent's work is not in this transcript.\n </div>\n ) : null\n ) : 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 ) : null}\n {frame && frameTask === undefined && !replaying ? null : (\n <TranscriptRows\n rows={rows}\n boundary={boundary}\n since={catchUp?.since}\n terminal={terminal}\n replaying={replaying}\n stickyPrompt={!frame && stickyPrompt}\n gap={gap}\n fontSize={fontSize}\n lineHeight={lineHeight}\n items={items}\n pendingApprovals={state.pendingApprovals}\n /* The rail rides the frame's OWN rows, so inside a takeover it\n marks the sub-agent's prompts, answers and failures — the thing\n a long agent run most needs, and coherent because every input\n it takes (`items`, `rowIndexFor`, `offsetOfRow`) is the frame's.\n The **bookmarks are not**: those indices are the host's, in\n full-transcript space, and painting them here would put marks\n at meaningless offsets. See `frame`'s doc for the rest. */\n scrubber={scrubber}\n scrubberMarks={frame ? undefined : scrubberMarks}\n affordances={affordances}\n fileUrl={fileUrl}\n attachmentUrl={attachmentUrl}\n hostImage={hostImage}\n jumpToRecapRef={jumpToRecapRef}\n repinRef={repinRef}\n reveal={frame ? undefined : reveal}\n frameParentId={frame?.parentToolUseId}\n onOpenSubagent={frame ? undefined : onOpenSubagent}\n />\n )}\n {(frame ? frameTask !== undefined && taskBusy(frameTask, items) : showLoader(state)) ? (\n terminal ? (\n // The CLI's own working line, and it is a *row of the transcript*\n // rather than a spinner floating over it — one blank line down,\n // like every other block.\n <>\n {state.items.length > 0 ? <div className='term-blank' aria-hidden /> : null}\n <WorkingRow\n label={state.status === 'starting' ? 'Starting…' : 'Working…'}\n startedAt={runStartedAt}\n tokens={state.contextUsage?.totalTokens}\n />\n </>\n ) : (\n <Loader\n label={state.status === 'starting' ? 'Starting session…' : undefined}\n startedAt={runStartedAt}\n tokens={state.contextUsage?.totalTokens}\n />\n )\n ) : null}\n </TerminalShell>\n </ConversationContent>\n <ConversationScrollButton />\n {/* What shows while the hold is on — and for a normal attach that is\n *nothing*. `wd-hold-appear` (theme.css) keeps it at `opacity: 0`\n and fades it in only after 600ms, so a healthy ~0.5s hold unmounts\n it before it ever paints. An unconditional line here was worse than\n no hold at all: it appeared and vanished inside half a second, which\n is the flicker the hold exists to remove, relocated to the top of\n the panel. Only a genuinely slow attach earns a placeholder, which\n is the case where a reader would otherwise think the panel is dead.\n An overlay rather than a flow row\n because the hidden content is at full height and pinned to its\n bottom; a row in flow would sit at the bottom edge. It mirrors\n `ConversationContent`'s wrapper (not the component itself — a\n second `StickToBottom.Content` would steal the library's content\n ref) so the paddings line up with the real rows'. */}\n {replaying ? (\n <div\n data-slot='transcript-hold'\n aria-hidden\n className='wd-hold-appear visible pointer-events-none absolute inset-0 overflow-hidden'>\n <div\n className={cn(\n 'mx-auto w-full max-w-[var(--wd-content-max-w,48rem)]',\n !terminal && 'px-4 py-4',\n )}>\n {terminal ? (\n <TerminalSurface\n fontSize={fontSize}\n lineHeight={lineHeight}\n affordances={false}\n bleed='1ch'\n className='term-transcript'>\n <WorkingRow label='Loading…' />\n </TerminalSurface>\n ) : (\n <Loader label='Loading session…' />\n )}\n </div>\n </div>\n ) : null}\n </Conversation>\n </TranscriptVariantProvider>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { UsageWindowRow } from '@workerdeck/protocol'\nimport { RotateCcw } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport {\n formatAgoPrecise,\n formatCountdown,\n formatRateLimitWindowLong,\n rateLimitWindowSeconds,\n} from '../../lib/format.ts'\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.\n *\n * `active` is what a dialog passes its `open`: a closed panel's clock is a timer\n * re-rendering something nobody can see.\n */\nexport function useMinuteClock(active = true): number {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n if (!active) return\n setNow(Date.now())\n const timer = setInterval(() => setNow(Date.now()), 60_000)\n return () => clearInterval(timer)\n }, [active])\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 engine reports a reset time and a percentage and\n * never a duration; a window whose key doesn't say gets no marker rather than a\n * guessed one.\n *\n * Its own component rather than the Usage dialog's private helper because two\n * surfaces draw these now — the session panel's Usage panel, from the session's\n * readings merged with the gateway's, and the dashboard's profile page, straight\n * off `ProfileInfo.usage` with no session in sight. Same account, same bars.\n */\nexport function UsageMeters({\n windows,\n now,\n className,\n}: {\n /** In reading order — protocol's `orderUsageWindows`. */\n windows: UsageWindowRow[]\n /** A shared clock, when the caller already ticks one. Omitted, this ticks its\n * own for as long as it is mounted. */\n now?: number\n className?: string\n}) {\n const ownClock = useMinuteClock(now === undefined)\n const clock = now ?? ownClock\n return (\n <div className={cn('flex flex-col gap-5', className)}>\n {windows.map((window) => (\n <UsageMeter key={window.key} window={window} now={clock} />\n ))}\n </div>\n )\n}\n\nfunction UsageMeter({ window, now }: { window: UsageWindowRow; now: number }) {\n const { key: windowKey, info, updatedAt, inferredReset } = window\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 {/* Said plainly rather than drawn as a fact: the gateway zeroed this\n because the reading's own reset time passed with nothing newer, so 0\n is a floor — the account may have been spent elsewhere since. */}\n {inferredReset ? <span>window reset · nothing reported since</span> : null}\n {/* Per window, because they no longer share one clock: a reading learned\n from a sibling session minutes ago can sit beside one this session\n last heard about yesterday. */}\n {updatedAt && !inferredReset ? <span>{formatAgoPrecise(updatedAt, now)}</span> : null}\n </div>\n </div>\n )\n}\n","import type { ProfileEngine, UsageWindowRow } from '@workerdeck/protocol'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader } from '../ui/Dialog.tsx'\nimport { formatAgoPrecise, formatCost } from '../../lib/format.ts'\nimport { UsageMeters, useMinuteClock } from './UsageMeters.tsx'\n\nexport interface UsageDialogProps {\n /**\n * Windows in reading order — session, weekly, then per-model weeklies.\n *\n * `updatedAt`/`inferredReset` ride each window because the readings no longer\n * share one clock: the panel merges the gateway's per-profile state over this\n * session's, so a `five_hour` learned from a sibling session two minutes ago\n * can sit beside a `seven_day` this session last heard about yesterday.\n * `inferredReset` marks the ones the *gateway* zeroed because their own reset\n * time passed with nothing newer — a floor, not a report.\n */\n rateLimits: UsageWindowRow[]\n /** claude.ai plan behind the windows ('max', 'pro', …), when there is one. */\n subscriptionType?: string\n engine: ProfileEngine\n totalCostUsd: number\n /** When the *freshest* of the windows was read, for the footer line. Readings\n * arrive one per turn at best (and for an idle session, not at all), so a\n * stale one is normal and worth saying out loud. */\n updatedAt?: number\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * The session's view of the plan: every window as a meter (see\n * {@link UsageMeters}), the plan tier, and what this session itself has cost.\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 <UsageMeters windows={rateLimits} now={now} />\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","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 {\n PROTOCOL_VERSION,\n mergeUsage,\n orderUsageWindows,\n subagentLabel,\n usageInfos,\n type ModelOption,\n type PermissionMode,\n type RateLimitInfo,\n} from '@workerdeck/protocol'\nimport {\n useAttachments,\n useClaudeSession,\n useHostFileSearch,\n useProfileUsage,\n useToolCallHost,\n type ConnectionState,\n type ProducedFileRef,\n type TranscriptState,\n type UseToolCallHostOptions,\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 { SubagentStrip } from './SubagentStrip.tsx'\nimport { subagentItems, type ToolCallItem } from '../terminal/blocks.ts'\nimport { QuestionPrompt, parseUserQuestions } from './QuestionPrompt.tsx'\nimport { TerminalPermissionPrompt } from '../terminal/PermissionPrompt.tsx'\nimport { TerminalQuestionPrompt } from '../terminal/QuestionPrompt.tsx'\nimport { TerminalSurface } from '../terminal/surface.tsx'\nimport type { TerminalAffordances } from '../terminal/affordances.tsx'\n\nimport { SessionInfoDialog } from './SessionInfoDialog.tsx'\nimport { StatusBar } from './StatusBar.tsx'\nimport { Transcript } from './Transcript.tsx'\nimport { ToolResultFetchProvider } from './tool-result-fetch.tsx'\nimport { ToolResultImageProvider, useToolResultImages } from './tool-result-image.tsx'\nimport {\n TranscriptDensityProvider,\n TranscriptVariantProvider,\n type TranscriptDensity,\n type TranscriptFont,\n type TranscriptVariant,\n} from './transcript-variant.tsx'\nimport { UsageDialog } from './UsageDialog.tsx'\n\n/**\n * The box the pending prompts sit in.\n *\n * Under the terminal theme they are rows of the same run as the transcript, so\n * they need that theme's cell — and its `--term-bleed`, so an approval's diff\n * bands reach the same edges the transcript's do. Every other variant keeps the\n * centred content column the panel uses everywhere else.\n */\nfunction PromptSurface({\n terminal,\n metrics,\n affordances,\n children,\n}: {\n terminal: boolean\n metrics?: TerminalMetrics\n affordances?: TerminalAffordances | boolean\n children: ReactNode\n}) {\n if (!terminal) {\n return (\n <div className='mx-auto flex w-full max-w-[var(--wd-content-max-w,48rem)] flex-col gap-2'>\n {children}\n </div>\n )\n }\n return (\n <TerminalSurface\n fontSize={metrics?.fontSize}\n lineHeight={metrics?.lineHeight}\n affordances={affordances}\n bleed='1ch'\n className='term-transcript'>\n {children}\n </TerminalSurface>\n )\n}\n\n/**\n * The character cell the terminal theme draws on, in **whole pixels**.\n *\n * One object rather than two props because the panel mounts three separate\n * `TerminalSurface`s — the transcript, the pending prompts, the composer — and\n * they must agree: a prompt drawn at a different line height from the rows above\n * it is three surfaces on three grids, which is the failure this theme is built\n * to make impossible. Passing one value through one prop is what keeps them from\n * drifting.\n *\n * Absent means the CLI's own 13/18. A host that follows an editor font size\n * (VS Code) hands that down instead.\n */\nexport type TerminalMetrics = { fontSize?: number; lineHeight?: number }\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 /**\n * Which end of the panel the status bar sits at. Default `top`.\n *\n * `bottom` is the editor convention — VS Code's status bar runs along the\n * foot of the window — and suits a host where the panel *is* the editor area\n * and the chrome above it already belongs to the app. Placement only; the bar\n * is the same bar, with the same `⋯` menu in its trailing slot, so this\n * composes with {@link statusSurface} rather than competing with it (external\n * still means \"there isn't one\").\n */\n statusPlacement?: 'top' | 'bottom'\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, the chat convention)\n * or `'terminal'`, the CLI's own form: every row on a character cell, no boxes\n * anywhere, diffs as full-width bands. An embedder in a dock (the VS Code\n * panel) wants `'terminal'`; a full-width dashboard may prefer cards.\n */\n transcriptVariant?: TranscriptVariant\n /**\n * Terminal theme only: the pointer affordances a real terminal cannot offer —\n * the hover fill, the hover-revealed copy. `false` for none. None of them\n * costs layout, so turning them off changes no glyph's position. See\n * {@link TerminalAffordances}.\n */\n affordances?: TerminalAffordances | boolean\n /**\n * Terminal theme only: the character cell, in whole pixels. See\n * {@link TerminalMetrics} — it reaches all three of the panel's terminal\n * surfaces, which is why it is one prop and not two per surface.\n */\n terminalMetrics?: TerminalMetrics\n /**\n * Terminal theme only: replace the scrollbar with the **overview ruler** — a\n * `2ch` rail of coloured marks in three lanes (what you typed, the answer and\n * its turn end, errors and a waiting approval), which you can hover to peek,\n * click to jump, and drag to scrub.\n *\n * Its premise is the terminal theme's own: one line height and one cell make\n * every row's height derivable, so a mark's position is *computed* rather than\n * guessed from rows that have not mounted. That is why it is not offered under\n * `cards` — there the flag is inert.\n *\n * `false` keeps the native scrollbar. So does `affordances={false}`, which\n * leaves the marks painted but inert rather than removing a reader's only way\n * to scroll.\n */\n scrubber?: boolean\n /**\n * Bookmarked **item indices**, painted full-width on the rail. Paint only —\n * the panel neither stores bookmarks nor offers a way to set one, because who\n * owns that store is the embedder's question (a private pin belongs with the\n * client's watermarks; a shared one is session metadata on the gateway).\n */\n scrubberMarks?: readonly number[]\n /**\n * Scroll a tool call into view; bump `nonce` to ask again for the same one.\n * See {@link TranscriptProps.reveal} — this is the panel's pass-through, and\n * exists so a surface *outside* the panel (a sessions list showing a session's\n * running sub-agents) can say \"take me to that one\" without opening a second\n * attach to find out where it is.\n */\n reveal?: { toolUseId: string; nonce: number }\n /**\n * Open a **sub-agent takeover**: the panel body becomes that agent's own work,\n * with a way back. Bump `nonce` to ask again for the same one.\n *\n * A *request*, not a controlled value — the panel owns which agent is open,\n * exactly as it owns `panel`. Dismissal has to work with zero host wiring\n * (Back and Escape are the panel's own affordances), and the two hosts in\n * scope reach this across a postMessage bridge where a controlled prop would\n * need a live closure at the far end. Same shape and same reason as\n * {@link SessionPanelProps.reveal}.\n *\n * Hosts must clear their request on a session switch: a stale one replayed at\n * remount would open a frame the new transcript cannot answer.\n *\n * Claude-only in practice, and gated by data rather than by a flag — codex and\n * provider sessions have no `parentToolUseId`, so they grow no task blocks and\n * no sub-agent rows, and nothing can raise this.\n */\n openSubagent?: { toolUseId: string; nonce: number }\n /**\n * Terminal theme only: hold the prompt of the turn you are reading at the top\n * of the transcript, as the Claude Code CLI does. The **real row** is pinned\n * rather than a copy drawn above it, so it lines up with the rows beneath by\n * construction — see `TranscriptRows`.\n */\n stickyPrompt?: boolean\n /**\n * How much air the transcript gives each row — `'comfortable'` (default: a\n * blank line between messages, as the Claude Code CLI leaves) or `'compact'`\n * (rows tight against one another). Independent of `transcriptVariant`: the\n * variant follows from the surface, density is the reader's preference, and a\n * dock is allowed to be roomy.\n */\n transcriptDensity?: TranscriptDensity\n /**\n * The panel's typeface — `'sans'` (default, the host's UI font) or `'mono'`,\n * which repoints the sans token at the mono stack **for this subtree only**.\n *\n * The third reader preference beside variant and density, and the same kind of\n * thing: how a transcript should read is a property of the person reading it.\n * Scoped to the panel because that is the whole claim — a monospace agent view\n * next to an ordinary app, not a monospace app.\n */\n transcriptFont?: TranscriptFont\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 * `'status'` draws them in the panel's OWN status bar, beside the readings\n * they act on; the composer collapses to a single line either way. That is\n * VS Code's arrangement without VS Code — a host whose panel carries a status\n * bar of its own (`statusPlacement: 'bottom'`) gets the same streamlined\n * shape without having to host the pickers itself.\n * `'external'` draws neither: the embedder renders the pickers in its own\n * chrome (VS Code's window status bar, where a click opens a QuickPick) and\n * drives them through {@link onControls}.\n *\n * `'status'` needs a status bar to put them in — with\n * `statusSurface: 'external'` there is none, and the two together would hide\n * the controls entirely, so that combination falls back to the composer.\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' | 'status'\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 /**\n * A viewer, not a seat at the session: transcript, status bar and panels as\n * usual, but no composer and no approval prompts.\n *\n * For a surface that is *about* a run rather than in it — the dashboard's job\n * detail, where the session belongs to the queue and typing into it would be a\n * second operator arriving mid-run. Deliberately not \"disabled controls\": a\n * greyed-out composer says the session is busy, an absent one says this screen\n * does not drive it. The attach is still live and read paths are untouched,\n * so the transcript streams and the file tree browses.\n *\n * It does **not** claim to be an authorization boundary. Anything holding this\n * client can still send; what it removes is the affordance, and the honest\n * enforcement lives on the gateway.\n */\n readOnly?: boolean\n /**\n * Options for the browser tool host this panel runs on its own attach — or\n * `false` to run none at all.\n *\n * The panel hosts server-bridged tool calls itself, because the bridge asks\n * the *first attached client* and the panel owns the session's one attach: an\n * embedder subscribing to the same handle separately would find this host\n * already answering, and refusing, anything outside its allow-list. So the\n * options come through here.\n *\n * Merged over the defaults, which host `eval_script` alone. Widening `tools`\n * is a real grant — this tab will execute what the gateway asks it to for\n * every name in the list — so it names them explicitly rather than accepting\n * a wildcard.\n */\n toolHost?: UseToolCallHostOptions | false\n /**\n * Keep this session's transcript warm across remounts (default true), so\n * switching back to a recently viewed session paints instantly and replays\n * only what it missed — `UseClaudeSessionOptions.cacheTranscript`. Set\n * `false` for an embedder whose principal varies on one gateway URL by means\n * the client cannot see.\n */\n cacheTranscript?: boolean\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 * Put the caret in the composer.\n *\n * For an embedder whose own chrome is how you arrive at a session — clicking a\n * row in VS Code's sidebar — where revealing the panel and being able to type\n * are the same intention. The panel cannot infer it: from in here, a session\n * appearing looks identical whether someone asked for it or it was restored.\n */\n focusComposer: () => 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 statusPlacement = 'top',\n onOpenPanel,\n onVitals,\n transcriptVariant = 'cards',\n transcriptDensity = 'comfortable',\n transcriptFont = 'sans',\n affordances,\n terminalMetrics,\n scrubber = false,\n scrubberMarks,\n reveal,\n openSubagent,\n stickyPrompt = false,\n controlsSurface = 'internal',\n onControls,\n focusComposerOnClick = false,\n unseen,\n readOnly = false,\n toolHost,\n cacheTranscript,\n className,\n}: SessionPanelProps) {\n const external = panelSurface === 'external'\n const statusExternal = statusSurface === 'external'\n // Both non-internal surfaces take the pickers out of the composer, which is\n // what collapses it to a single line.\n const controlsInStatus = controlsSurface === 'status' && !statusExternal\n const controlsExternal = controlsSurface === 'external' || controlsInStatus\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 /**\n * The sub-agent takeover: which `Task` call the body is currently showing, or\n * undefined for the conversation.\n *\n * Deliberately **not** a `Panel` member. Those route outward under\n * `panelSurface: 'external'` so a host can draw them natively — and a host\n * cannot draw this one: it is a transcript, and the transcript is in here. It\n * is a mode of the conversation surface, not a screen over it.\n */\n const [subagentId, setSubagentId] = useState<string | undefined>(undefined)\n /**\n * Where to land on the way back — the row of the Task you entered from,\n * asked for through the ordinary reveal seam.\n *\n * The alternative was keeping the main transcript mounted-but-hidden to\n * preserve its scroll offset, which costs two live virtualizers and an\n * absolute-position layout on a panel whose height just changed (the composer\n * came back). Re-revealing is both cheaper and more honest: you return to the\n * row you left from rather than to wherever you happened to be scrolled.\n */\n const [returnReveal, setReturnReveal] = useState<\n { toolUseId: string; nonce: number } | undefined\n >(undefined)\n const {\n state,\n connection,\n replaying,\n protocolMismatch,\n models,\n effectiveModel,\n handle,\n send,\n approve,\n deny,\n interrupt,\n setModel,\n setPermissionMode,\n reconnectNow,\n loadFullResult,\n } = useClaudeSession(client, sessionId, { onProtocolError: setProtocolError, cacheTranscript })\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 // Belt to the remount contract, for the same reason: a takeover left standing\n // across a switch would frame one session's Task id against another's items.\n useEffect(() => {\n setSubagentId(undefined)\n setReturnReveal(undefined)\n }, [sessionId])\n\n // The host asking. Keyed on the nonce, so asking twice for the same agent\n // works — and so a request that arrives while another frame is open swaps it\n // in place rather than being ignored.\n const openSubagentNonce = openSubagent?.nonce\n const openSubagentId = openSubagent?.toolUseId\n useEffect(() => {\n if (openSubagentId === undefined) return\n setSubagentId(openSubagentId)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [openSubagentNonce])\n\n // A *reveal* asked for while framed means the reader wants the conversation:\n // latest intent wins. Leaving the frame up and scrolling something invisible\n // underneath it would be the worst of both.\n const revealNonce = reveal?.nonce\n useEffect(() => {\n if (revealNonce === undefined) return\n setSubagentId(undefined)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [revealNonce])\n\n const leaveSubagent = useCallback(() => {\n setSubagentId((current) => {\n if (current !== undefined) setReturnReveal({ toolUseId: current, nonce: Date.now() })\n return undefined\n })\n }, [])\n\n // Escape leaves the frame — the keyboard half of Back. `defaultPrevented`\n // keeps a dialog's own Escape (and the composer's) ahead of it: this is the\n // outermost thing Escape can mean here, so it goes last.\n useEffect(() => {\n if (subagentId === undefined) return\n const onKey = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || event.defaultPrevented) return\n leaveSubagent()\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [subagentId, leaveSubagent])\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, toolHost === false ? { enabled: false } : toolHost)\n const terminal = transcriptVariant === 'terminal'\n\n // What the strip reads. The frame's items and its spawning call both come from\n // the transcript, which is the only complete source: `SessionInfo.subagents`\n // keeps eight settled records and is explicitly not a session's Task history.\n const subagentFrameItems = useMemo(\n () => (subagentId === undefined ? [] : subagentItems(state.items, subagentId)),\n [state.items, subagentId],\n )\n const subagentTask = useMemo(\n () =>\n subagentId === undefined\n ? undefined\n : state.items.find(\n (item): item is ToolCallItem => item.kind === 'tool_call' && item.id === subagentId,\n ),\n [state.items, subagentId],\n )\n // The one thing the rollup is allowed to do: name an agent whose `Task` call\n // the transcript does not have. A label is not content.\n const subagentFallbackLabel = useMemo(() => {\n const record = state.session?.subagents?.find((sub) => sub.toolUseId === subagentId)\n return record ? subagentLabel(record) : 'Sub-agent'\n }, [state.session, subagentId])\n const capabilities = state.capabilities\n\n // Plan usage as the *gateway* knows it, merged over this session's own\n // reading — one derivation, feeding the bar, the terminal status line, the\n // Usage panel and the vitals an external chrome renders from, because four\n // surfaces disagreeing about the same percentage is worse than any of them\n // being stale. A session's own `rate_limit` readings land only at a turn's\n // edges, so an idle session's numbers age silently and a sibling session's\n // spend never shows up here at all; `mergeUsage` is where that rule is\n // written down. Skipped entirely for an engine that reports no windows.\n const { usage: profileUsage } = useProfileUsage(client, state.session?.profile, {\n enabled: capabilities.rateLimits,\n })\n const usage = useMemo(\n () =>\n mergeUsage(\n { rateLimits: state.rateLimits, updatedAt: state.rateLimitsUpdatedAt },\n profileUsage,\n ),\n [state.rateLimits, state.rateLimitsUpdatedAt, profileUsage],\n )\n // Undefined rather than an empty map when nothing has been reported: absent is\n // *unknown*, and a surface that can't tell the two apart draws 0%.\n const rateLimits: Record<string, RateLimitInfo> | undefined = useMemo(\n () => (Object.keys(usage).length > 0 ? usageInfos(usage) : undefined),\n [usage],\n )\n const usageUpdatedAt = useMemo(() => {\n const stamps = Object.values(usage).map((w) => w.updatedAt)\n return stamps.length > 0 ? Math.max(...stamps) : undefined\n }, [usage])\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,\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 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 focusComposer: () => composerRef.current?.focus(),\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 // Protocol's ordering, over the merged readings — each row keeping its own\n // date, since they no longer share one clock.\n const windows = useMemo(() => orderUsageWindows(usage), [usage])\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 // The other picture route, and the other store: an image *part* of a tool\n // result, which an opted-in replay delivered as a reference rather than half a\n // megabyte of base64. Bounded, unlike `useHostImage` — see the module.\n const resultImages = useToolResultImages(client, sessionId)\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 // Filled by the transcript; pressed on send. See `handleSend`.\n const repinTranscript = 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 // ...and sending is the statement that you want to watch what happens next,\n // so following resumes here too. Scrolling up escapes the bottom lock, and\n // without this the reply streams in off screen: the transcript stays parked\n // where you were reading and sending looks like it did nothing. Ordered\n // before `send` only for readability — the re-pin is instant and the first\n // row is a round trip away either way.\n repinTranscript.current?.()\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 // Built once and placed at one end or the other — the bar has a `⋯` menu and\n // three open handlers, and two copies of that in the tree would be two things\n // to keep in step.\n // Built once, placed by `controlsSurface`: the composer's toolbar row, or the\n // status bar beside the readings they act on, or nowhere at all when the host\n // draws them in its own chrome.\n const sessionControls = (\n <>\n {/* Codex reports no `capabilities` event, so its models arrive from the\n profile catalog instead — without that fallback its picker would be\n permanently empty and the session unswitchable. */}\n {models.length ? (\n <ModelSelect\n models={models}\n model={effectiveModel}\n onModelChange={setModel}\n disabled={ended}\n // The bar is one line of label-sized text; a 24px trigger would set\n // its height instead of fitting in it.\n className={controlsInStatus ? 'h-5' : undefined}\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 a\n // protocol_error.\n modes={capabilities.permissionModes}\n canBypass={state.session?.canBypassPermissions}\n disabled={ended}\n className={controlsInStatus ? 'h-5' : undefined}\n />\n ) : null}\n </>\n )\n\n const statusBar = statusExternal ? null : (\n <StatusBar\n state={state}\n rateLimits={rateLimits}\n connection={connection}\n placement={statusPlacement}\n controls={controlsInStatus && !readOnly ? sessionControls : undefined}\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\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 || readOnly) 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 composer\n // and the pending prompts live outside the scroller but belong to the same\n // run, and they read it from this context rather than a prop chain.\n <TranscriptVariantProvider value={transcriptVariant}>\n <TranscriptDensityProvider value={transcriptDensity}>\n {/* The panel owns the session's one attach, so it is the only thing that\n can answer a row asking for the rest of a truncated tool result. Rows\n rendered anywhere else fall back to the context's no-op, which is\n correct for them: nothing truncates a replay they never asked for. */}\n <ToolResultFetchProvider value={loadFullResult}>\n <ToolResultImageProvider value={resultImages}>\n <div\n data-slot='session-panel'\n // The typeface is a cascade fact, not a React one — one attribute here,\n // and the `[data-agent-font]` rule in theme.css does the rest. Nothing\n // outside this subtree can pick it up.\n data-agent-font={transcriptFont}\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 {statusPlacement === 'top' ? statusBar : null}\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 {/* The takeover's one line: who is on screen, and the way back. Above\n the transcript because it is a frame around it, not a row in it —\n and because Back must be reachable without scrolling a stream that\n is still growing. */}\n {subagentId !== undefined ? (\n <SubagentStrip\n task={subagentTask}\n items={subagentFrameItems}\n label={subagentFallbackLabel}\n onBack={leaveSubagent}\n terminal={terminal}\n fontSize={terminalMetrics?.fontSize}\n lineHeight={terminalMetrics?.lineHeight}\n />\n ) : null}\n <Transcript\n /* A remount, so the frame opens pinned to its own bottom with a fresh\n virtualizer and height epoch — which is the right landing for both\n cases: the live tail of a running agent, and the final report of a\n settled one. */\n key={subagentId ?? 'session'}\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 density={transcriptDensity}\n fontSize={terminalMetrics?.fontSize}\n lineHeight={terminalMetrics?.lineHeight}\n affordances={affordances}\n stickyPrompt={stickyPrompt}\n scrubber={scrubber}\n scrubberMarks={scrubberMarks}\n replaying={replaying}\n catchUp={\n catchUp && newCount > 0\n ? { from: catchUp.itemCount, since: catchUp.since }\n : undefined\n }\n /* On the way back, land on the Task you came from — see `returnReveal`.\n The host's own reveal still wins when it is the newer intent. */\n reveal={returnReveal ?? reveal}\n frame={subagentId === undefined ? undefined : { parentToolUseId: subagentId }}\n onOpenSubagent={setSubagentId}\n jumpToRecapRef={jumpToRecap}\n repinRef={repinTranscript}\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. Held with the transcript\n while the replay lands — its count is `state.items.length`, which\n during the replay is a number visibly climbing toward its answer. */}\n {catchUp && newCount > 0 && !replaying && subagentId === undefined ? (\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\n aria-hidden\n className={cn('select-none', terminal ? 'text-fg-3' : '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 {/* A read-only surface has no answer to give: the status bar still says\n `awaiting approval`, and the place that can act on it is wherever the\n session is actually driven. */}\n {!readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? (\n <div className={cn(terminal ? 'pb-2' : 'px-3 pb-2')}>\n <PromptSurface terminal={terminal} metrics={terminalMetrics} affordances={affordances}>\n {state.pendingApprovals.map((request) => {\n const isQuestion =\n request.toolName === 'AskUserQuestion' &&\n parseUserQuestions(request.input).length > 0\n if (terminal) {\n return isQuestion ? (\n <TerminalQuestionPrompt\n key={request.id}\n request={request}\n onAnswer={approve}\n onDismiss={(id) => deny(id, 'Question dismissed by user')}\n />\n ) : (\n <TerminalPermissionPrompt\n key={request.id}\n request={request}\n onApprove={approve}\n onDeny={deny}\n />\n )\n }\n return isQuestion ? (\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 </PromptSurface>\n </div>\n ) : null}\n {/* No composer while a sub-agent is framed: you cannot talk to one, and a\n live-looking input that silently addresses its *parent* is worse than\n no input at all. Its interrupt goes with it — Back is one press away,\n and a second interrupt control would be a second thing that stops\n something the reader is not looking at.\n\n The approval prompts above deliberately do NOT go with it. A\n sub-agent's own tool calls raise session-level permission requests,\n so hiding them here would let the takeover deadlock the very agent it\n is showing until the reader happened to press Back. */}\n {readOnly || subagentId !== undefined ? 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={controlsExternal ? undefined : sessionControls}\n fontSize={terminalMetrics?.fontSize}\n lineHeight={terminalMetrics?.lineHeight}\n affordances={affordances}\n />\n )}\n {/* Below the composer, along the foot of the panel — the editor\n convention. Last in the flex column, so it is the bottom edge. */}\n {statusPlacement === 'bottom' ? statusBar : null}\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={usageUpdatedAt}\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 </ToolResultImageProvider>\n </ToolResultFetchProvider>\n </TranscriptDensityProvider>\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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cAAc,MAAM,SAAS;CACnC,MAAM,eAAe,MAAM,MAAM,GAAG,mBAAmB;AAEvD,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,YAAY,UAAU;YAAzC,CACE,qBAAC,OAAD;GAAK,WAAU;GAAuB,MAAK;GAAO,cAAW;aAA7D,EACI,cAAc,eAAe,OAAO,KAAK,SACzC,oBAACA,YAAD;IAEQ;IACG;IACC;IACD;IACT,EALK,KAAK,GAKV,CACF,EACD,eACC,oBAAC,OAAD;IAAK,MAAK;cACR,oBAAC,UAAD;KACE,KAAK;KACL,MAAK;KACL,eAAe,aAAa,MAAM,CAAC,EAAE;KACrC,WAAW,GACT,gKACA,UAAU,sBAAsB,oBACjC;eACA,WAAW,cAAc,IAAI,YAAY;KACnC,CAAA;IACL,CAAA,CAEJ;MAEL,YACC,oBAAC,OAAD;GACE,KAAK;GACL,WAAW,GACT,0HACD;aACD,oBAAC,OAAD;IAAK,WAAU;IAAuB,MAAK;IAAO,cAAW;cAC1D,MAAM,MAAM,mBAAmB,CAAC,KAAK,SACpC,oBAACA,YAAD;KAEQ;KACG;KACC;KACD;KACT,EALK,KAAK,GAKV,CACF;IACE,CAAA;GACF,CAAA,CAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClNV,SAAgB,WAAW,EACzB,OACA,UACA,UACA,aACA,WACA,WAAW,OACX,UACA,kBACA,UACA,UACA,aACA,WACA,cACA,aACA,SACA,QACA,QACA,YAAY,IACZ,WACA,YAAY,OACZ,WAAW,OACX,cAAc,WACd,gBAAgB,YAChB,SAAS,EAAE,EACX,gBAAgB,SAChB,cACA,eACA,cACA,QAAQ,EAAE,EACV,eAAe,SACf,cACA,aACA,WACA,QACA,YACA,eACA,YACA,WACA,oBAAoB,iBACpB,OAC0D;CAC1D,MAAM,EACJ,WACA,eACA,aACA,oBACA,kBACA,yBACA,aACA,eACA,aACA,iBACA,kBACA,gBACA,QACA,aACA,kBACE,cAAc;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAGF,q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"}