@workerdeck/ui 0.7.0 → 0.10.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.
Files changed (38) hide show
  1. package/README.md +44 -3
  2. package/build/index.d.mts +767 -21
  3. package/build/index.mjs +3268 -348
  4. package/build/index.mjs.map +1 -1
  5. package/package.json +5 -4
  6. package/src/components/agent/CodeEditor.tsx +300 -0
  7. package/src/components/agent/Composer.tsx +379 -56
  8. package/src/components/agent/ContextDialog.tsx +99 -0
  9. package/src/components/agent/EditorTabs.tsx +165 -0
  10. package/src/components/agent/FileTree.tsx +287 -0
  11. package/src/components/agent/FileViewer.tsx +148 -0
  12. package/src/components/agent/HostFilesDialog.tsx +218 -0
  13. package/src/components/agent/McpDialog.tsx +363 -0
  14. package/src/components/agent/ModelSelect.tsx +34 -6
  15. package/src/components/agent/PermissionModeSelect.tsx +99 -22
  16. package/src/components/agent/PermissionPrompt.tsx +72 -6
  17. package/src/components/agent/PromptTokenText.tsx +39 -0
  18. package/src/components/agent/SessionEmptyState.tsx +65 -0
  19. package/src/components/agent/SessionInfoDialog.tsx +163 -0
  20. package/src/components/agent/SessionPanel.tsx +380 -40
  21. package/src/components/agent/SessionWorkspace.tsx +282 -0
  22. package/src/components/agent/SkillsDialog.tsx +195 -0
  23. package/src/components/agent/StatusBar.tsx +85 -18
  24. package/src/components/agent/ToolCallCard.tsx +80 -4
  25. package/src/components/agent/Transcript.tsx +109 -12
  26. package/src/components/agent/UsageDialog.tsx +168 -0
  27. package/src/components/prompt-area/prompt-area-engine.ts +53 -0
  28. package/src/components/prompt-area/types.ts +15 -0
  29. package/src/components/prompt-area/use-prompt-area.ts +20 -0
  30. package/src/components/ui/CopyButton.tsx +8 -1
  31. package/src/components/ui/Dialog.tsx +92 -0
  32. package/src/components/ui/Menu.tsx +55 -0
  33. package/src/components/ui/Splitter.tsx +133 -0
  34. package/src/components/ui/Tooltip.tsx +22 -5
  35. package/src/index.ts +53 -1
  36. package/src/lib/clipboard.ts +56 -0
  37. package/src/lib/format.ts +48 -0
  38. package/src/lib/tool-icon.ts +74 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["SelectPrimitive","AlertDialogPrimitive","TooltipPrimitive","SonnerToaster","rangeStart","FileText","FileCard"],"sources":["../src/lib/utils.ts","../src/components/ui/Button.tsx","../src/components/ui/Badge.tsx","../src/components/ui/Card.tsx","../src/components/ui/Input.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/Select.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Tooltip.tsx","../src/components/ui/Sonner.tsx","../src/components/ui/CopyButton.tsx","../src/components/ui/Spinner.tsx","../src/components/ui/CodeBlock.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/prompt-area/segment-helpers.ts","../src/components/agent/Composer.tsx","../src/components/agent/ModelSelect.tsx","../src/components/agent/PermissionModeSelect.tsx","../src/components/agent/PermissionPrompt.tsx","../src/components/agent/QuestionPrompt.tsx","../src/lib/format.ts","../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/Response.tsx","../src/components/agent/Reasoning.tsx","../src/components/agent/ToolCallCard.tsx","../src/components/agent/Transcript.tsx","../src/components/agent/SessionPanel.tsx","../src/components/agent/SessionList.tsx"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n// Register the custom text-* font-size utilities as a font-size class group, or\n// tailwind-merge collapses a size class and a color class (e.g. `text-label\n// text-muted-foreground`) into one bucket and silently drops the size.\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-xl',\n 'display-lg',\n 'display-md',\n 'display-sm',\n 'heading-1',\n 'heading-2',\n 'heading-3',\n 'body',\n 'body-sm',\n 'label',\n 'code',\n ],\n },\n ],\n },\n },\n})\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","import { type ButtonHTMLAttributes, forwardRef } from 'react'\nimport { type VariantProps, cva } from 'class-variance-authority'\nimport { cn } from '../../lib/utils.ts'\n\nconst buttonVariants = cva(\n \"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-1 focus-visible:ring-offset-bg disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-accent-hover',\n outline: 'border-border bg-surface hover:border-border-strong hover:bg-surface-hover',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-surface-hover',\n ghost: 'text-fg-2 hover:bg-surface-hover hover:text-foreground',\n destructive: 'text-danger hover:bg-danger-bg',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n 'default': 'h-8 gap-1.5 px-3',\n 'xs': \"h-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3\",\n 'sm': 'h-7 gap-1 px-2.5 text-xs',\n 'lg': 'h-9 gap-1.5 px-3.5',\n 'icon': 'size-8',\n 'icon-sm': 'size-7',\n },\n },\n defaultVariants: { variant: 'default', size: 'default' },\n },\n)\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant = 'default', size = 'default', type = 'button', ...props }, ref) => (\n <button\n ref={ref}\n type={type}\n data-slot='button'\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n ),\n)\nButton.displayName = 'Button'\n\nexport { buttonVariants }\n","import { type HTMLAttributes } from 'react'\nimport { type VariantProps, cva } from 'class-variance-authority'\nimport { cn } from '../../lib/utils.ts'\n\nconst badgeVariants = cva(\n 'inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-label font-medium whitespace-nowrap',\n {\n variants: {\n variant: {\n neutral: 'border-border bg-surface text-fg-2',\n accent: 'border-transparent bg-accent-bg text-fg-1',\n success: 'border-transparent bg-success-bg text-success',\n warning: 'border-transparent bg-warning-bg text-warning',\n danger: 'border-transparent bg-danger-bg text-danger',\n info: 'border-transparent bg-info-bg text-info',\n },\n mono: {\n true: 'font-mono text-code',\n false: '',\n },\n },\n defaultVariants: { variant: 'neutral', mono: false },\n },\n)\n\nexport interface BadgeProps\n extends HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof badgeVariants> {\n /** Render a leading status dot in the variant's color. */\n dot?: boolean\n}\n\nexport function Badge({ className, variant = 'neutral', mono, dot, children, ...props }: BadgeProps) {\n return (\n <span data-slot='badge' className={cn(badgeVariants({ variant, mono, className }))} {...props}>\n {dot ? <span aria-hidden className='size-1.5 rounded-full bg-current' /> : null}\n {children}\n </span>\n )\n}\n\nexport { badgeVariants }\n","import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type 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 TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { Select as SelectPrimitive } from '@base-ui/react/select'\nimport { Check, ChevronsUpDown } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Select = SelectPrimitive.Root\nexport const SelectValue = SelectPrimitive.Value\nexport const SelectItemText = SelectPrimitive.ItemText\n\nexport const SelectTrigger: FunctionComponent<SelectPrimitive.Trigger.Props> = ({\n className,\n children,\n ...props\n}) => (\n <SelectPrimitive.Trigger\n data-slot='select-trigger'\n className={cn(\n 'inline-flex h-7 items-center justify-between gap-1.5 rounded-md border border-border bg-bg px-2 text-body-sm text-text',\n 'transition-colors outline-none hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'data-popup-open:border-border-strong disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}>\n {children}\n <SelectPrimitive.Icon className='text-fg-4'>\n <ChevronsUpDown className='size-3.5' />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n)\n\nexport const SelectContent: FunctionComponent<\n SelectPrimitive.Popup.Props &\n Pick<SelectPrimitive.Positioner.Props, 'align' | 'alignItemWithTrigger' | 'side' | 'sideOffset'>\n> = ({\n className,\n align = 'start',\n alignItemWithTrigger = false,\n side = 'bottom',\n sideOffset = 6,\n ...props\n}) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Positioner\n align={align}\n alignItemWithTrigger={alignItemWithTrigger}\n side={side}\n sideOffset={sideOffset}\n className='isolate z-60 outline-none'>\n <SelectPrimitive.Popup\n data-slot='select-content'\n className={cn(\n 'max-h-[min(24rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto',\n 'rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',\n className,\n )}\n {...props}\n />\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n)\n\nexport const SelectItem: FunctionComponent<SelectPrimitive.Item.Props> = ({\n className,\n children,\n ...props\n}) => (\n <SelectPrimitive.Item\n data-slot='select-item'\n className={cn(\n 'flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 text-body-sm text-text outline-none select-none',\n 'data-highlighted:bg-surface-hover',\n className,\n )}\n {...props}>\n <span className='flex min-w-0 flex-1 flex-col gap-0.5'>{children}</span>\n <SelectPrimitive.ItemIndicator className='mt-0.5 text-fg-1'>\n <Check className='size-3.5' />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n)\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { type FunctionComponent, type ReactNode } from 'react'\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip'\nimport { cn } from '../../lib/utils.ts'\n\nexport const TooltipProvider = TooltipPrimitive.Provider\n\nexport const TooltipContent: FunctionComponent<\n TooltipPrimitive.Popup.Props & Pick<TooltipPrimitive.Positioner.Props, 'side' | 'sideOffset'>\n> = ({ className, side = 'top', sideOffset = 6, ...props }) => (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Positioner side={side} sideOffset={sideOffset} className='isolate z-60'>\n <TooltipPrimitive.Popup\n data-slot='tooltip-content'\n className={cn(\n 'rounded-md border border-border bg-surface px-2 py-1 text-label text-fg-2 shadow-(--shadow-md) outline-none',\n className,\n )}\n {...props}\n />\n </TooltipPrimitive.Positioner>\n </TooltipPrimitive.Portal>\n)\n\n/** Convenience wrapper: <Tip content=\"...\"><Button/></Tip> */\nexport function Tip({ content, children }: { content: ReactNode; children: ReactNode }) {\n return (\n <TooltipPrimitive.Root>\n <TooltipPrimitive.Trigger render={<span className='inline-flex' />}>\n {children}\n </TooltipPrimitive.Trigger>\n <TooltipContent>{content}</TooltipContent>\n </TooltipPrimitive.Root>\n )\n}\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","import { useState } from 'react'\nimport { Check, Copy } from 'lucide-react'\nimport { Button, type ButtonProps } from './Button.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface CopyButtonProps extends Omit<ButtonProps, 'onClick' | 'children'> {\n value: string\n}\n\nexport function CopyButton({ value, className, variant = 'ghost', size = 'icon-sm', ...props }: 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 void navigator.clipboard.writeText(value).then(() => {\n setCopied(true)\n setTimeout(() => setCopied(false), 1500)\n })\n }}\n {...props}>\n {copied ? <Check className='size-3.5 text-success' /> : <Copy className='size-3.5' />}\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 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({ code, label, copyable = true, className }: CodeBlockProps) {\n return (\n <div\n data-slot='code-block'\n className={cn('overflow-hidden rounded-md border border-border bg-code-bg', className)}>\n {label !== undefined || copyable ? (\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 { 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// Chip removal\n// ---------------------------------------------------------------------------\n\n/**\n * Removes a chip at the given segment index and merges adjacent text segments.\n *\n * @param segments - Current document segments\n * @param index - The segment index to remove\n * @returns New segments array with the chip removed\n */\nexport function removeChipAtIndex(segments: Segment[], index: number): Segment[] {\n if (index < 0 || index >= segments.length) return segments\n if (segments[index].type !== 'chip') return segments\n\n const result = [...segments.slice(0, index), ...segments.slice(index + 1)]\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Reverts an auto-resolved chip at the given segment index back to plain text.\n * The text includes the trigger character + display text (e.g., \"#readme\").\n *\n * @param segments - Current document segments\n * @param index - The segment index to revert\n * @returns New segments with the chip replaced by text, or null if not applicable\n */\nexport function revertChipAtIndex(\n segments: Segment[],\n index: number,\n): { segments: Segment[]; revertedText: string } | null {\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n if (seg.type !== 'chip' || !seg.autoResolved) return null\n\n const revertedText = `${seg.trigger}${seg.displayText}`\n const result = [\n ...segments.slice(0, index),\n { type: 'text' as const, text: revertedText },\n ...segments.slice(index + 1),\n ]\n return { segments: mergeAdjacentTextSegments(result), revertedText }\n}\n\n// ---------------------------------------------------------------------------\n// Paste: resolve trigger patterns in segments\n// ---------------------------------------------------------------------------\n\n/**\n * Scans text segments for trigger patterns and auto-resolves them into chips.\n * Only resolves triggers that have `resolveOnSpace: true`.\n *\n * Trigger patterns must appear at word boundaries: start of text, after\n * whitespace, or after a newline. This avoids false positives like email\n * addresses (user@example.com).\n */\nexport function resolveTriggersInSegments(\n segments: Segment[],\n triggers: TriggerConfig[],\n): Segment[] {\n const autoResolveTriggers = triggers.filter((t) => t.resolveOnSpace)\n if (autoResolveTriggers.length === 0) return segments\n\n const triggerByChar = buildTriggerCharMap(autoResolveTriggers)\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n result.push(seg)\n continue\n }\n\n const parts = splitTextByTriggerPatterns(seg.text, triggerByChar)\n result.push(...parts)\n }\n\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Splits a text string into text and chip segments based on trigger patterns.\n * A trigger pattern is: (start-of-string | whitespace) + trigger_char + word_chars\n * followed by whitespace or end-of-string.\n */\nfunction splitTextByTriggerPatterns(\n text: string,\n triggerByChar: Map<string, TriggerConfig>,\n): Segment[] {\n if (!text) return []\n\n const segments: Segment[] = []\n let i = 0\n\n while (i < text.length) {\n const char = text[i]\n\n if (triggerByChar.has(char)) {\n const isAtBoundary = i === 0 || isInlineWhitespace(text[i - 1])\n\n if (isAtBoundary) {\n const trigger = triggerByChar.get(char)\n if (trigger && isValidTriggerPosition(text, i, trigger.position)) {\n let end = i + 1\n while (end < text.length && !isInlineWhitespace(text[end])) {\n end++\n }\n\n const query = text.slice(i + 1, end)\n if (query.length > 0) {\n // Treat both undefined and '' from onSelect as \"no custom label\"\n // and fall back to the query — an empty displayText would render\n // a blank chip.\n const displayText = trigger.onSelect?.({ value: query, label: query }) || query\n segments.push({\n type: 'chip',\n trigger: char,\n value: query,\n displayText,\n autoResolved: true,\n })\n i = end\n continue\n }\n }\n }\n }\n\n const start = i\n i++\n while (i < text.length && !(triggerByChar.has(text[i]) && isInlineWhitespace(text[i - 1]))) {\n i++\n }\n segments.push({ type: 'text', text: text.slice(start, i) })\n }\n\n return segments\n}\n\n// ---------------------------------------------------------------------------\n// Text range replacement\n// ---------------------------------------------------------------------------\n\n/**\n * Replaces a range of plain text within the segments array.\n * Handles segment boundaries correctly, preserving chip segments.\n *\n * @param segments - Current document segments\n * @param start - Start offset in plain text\n * @param end - End offset in plain text\n * @param replacement - The replacement text\n * @returns New segments array with the replacement applied\n */\nexport function replaceTextRange(\n segments: Segment[],\n start: number,\n end: number,\n replacement: string,\n): Segment[] {\n const newSegments: Segment[] = []\n let offset = 0\n let inserted = false\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipText = `${seg.trigger}${seg.displayText}`\n const chipStart = offset\n const chipEnd = offset + chipText.length\n\n // For insertion (start === end), insert before this chip if position matches\n if (!inserted && start === end && chipStart === start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n\n if (chipEnd <= start || chipStart >= end) {\n newSegments.push(seg)\n }\n // Chips within the range are removed\n offset = chipEnd\n } else {\n const textStart = offset\n const textEnd = offset + seg.text.length\n\n // Check if this segment contains the insertion/replacement point\n const isBefore = start === end ? textEnd < start : textEnd <= start\n const isAfter = start === end ? textStart > end : textStart >= end\n\n if (isBefore) {\n // Entirely before the range\n newSegments.push(seg)\n } else if (isAfter) {\n // Entirely after the range\n newSegments.push(seg)\n } else {\n // Overlaps with the range (or contains the insertion point)\n const beforeText = seg.text.slice(0, Math.max(0, start - textStart))\n const afterText = seg.text.slice(Math.min(seg.text.length, end - textStart))\n\n if (beforeText) {\n newSegments.push({ type: 'text', text: beforeText })\n }\n // Insert replacement only once (when we first enter the range)\n if (!inserted && textStart <= start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n if (afterText) {\n newSegments.push({ type: 'text', text: afterText })\n }\n }\n\n offset = textEnd\n }\n }\n\n // Fallback: if replacement wasn't inserted (e.g., insertion at very end)\n if (!inserted && replacement) {\n newSegments.push({ type: 'text', text: replacement })\n }\n\n return mergeAdjacentTextSegments(newSegments)\n}\n\n// ---------------------------------------------------------------------------\n// Markdown formatting shortcuts\n// ---------------------------------------------------------------------------\n\n/**\n * Toggles markdown wrap markers around a selected text range.\n * If the selection is already wrapped with the marker, unwraps it.\n * If not wrapped, wraps it.\n *\n * @param segments - Current document segments\n * @param selectionStart - Start offset in plain text\n * @param selectionEnd - End offset in plain text\n * @param marker - The markdown marker (e.g., '**' for bold, '*' for italic)\n * @returns New segments and selection offsets, or null if selection is collapsed\n */\nexport function toggleMarkdownWrap(\n segments: Segment[],\n selectionStart: number,\n selectionEnd: number,\n marker: string,\n): { segments: Segment[]; selectionStart: number; selectionEnd: number } | null {\n if (selectionStart === selectionEnd) return null\n\n const plainText = segmentsToPlainText(segments)\n const markerLen = marker.length\n\n // Check if already wrapped\n const hasOpeningMarker =\n selectionStart >= markerLen &&\n plainText.slice(selectionStart - markerLen, selectionStart) === marker\n const hasClosingMarker =\n selectionEnd + markerLen <= plainText.length &&\n plainText.slice(selectionEnd, selectionEnd + markerLen) === marker\n\n let isWrapped = hasOpeningMarker && hasClosingMarker\n\n // For single-char markers (e.g., '*'), ensure we're not matching\n // inside a multi-char marker (e.g., '**')\n if (isWrapped && markerLen === 1) {\n const charBeforeOpening =\n selectionStart > markerLen ? plainText[selectionStart - markerLen - 1] : ''\n const charAfterClosing =\n selectionEnd + markerLen < plainText.length ? plainText[selectionEnd + markerLen] : ''\n if (charBeforeOpening === marker || charAfterClosing === marker) {\n isWrapped = false\n }\n }\n\n if (isWrapped) {\n // Unwrap: remove closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd + markerLen, '')\n const afterOpening = replaceTextRange(\n afterClosing,\n selectionStart - markerLen,\n selectionStart,\n '',\n )\n return {\n segments: afterOpening,\n selectionStart: selectionStart - markerLen,\n selectionEnd: selectionEnd - markerLen,\n }\n }\n\n // Wrap: insert closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd, marker)\n const afterOpening = replaceTextRange(afterClosing, selectionStart, selectionStart, marker)\n return {\n segments: afterOpening,\n selectionStart: selectionStart + markerLen,\n selectionEnd: selectionEnd + markerLen,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Inline markdown parsing\n// ---------------------------------------------------------------------------\n\nexport type MarkdownToken =\n | { type: 'plain'; text: string }\n | { type: 'bold'; text: string }\n | { type: 'italic'; text: string }\n | { type: 'bold-italic'; text: string }\n | { type: 'url'; text: string }\n\n/**\n * Parses text for simple inline markdown: bold, italic, bold-italic, and URLs.\n * Does NOT handle block-level markdown (lists, headings, etc.).\n */\nexport function parseInlineMarkdown(text: string): MarkdownToken[] {\n if (!text) return []\n\n const tokens: MarkdownToken[] = []\n // Regex patterns for inline markdown elements:\n // 1. ***text*** or ___text___ -> bold-italic\n // 2. **text** or __text__ -> bold\n // 3. *text* or _text_ -> italic\n // 4. https://... or http://... -> URL\n const pattern = /(\\*{3}(.+?)\\*{3})|(\\*{2}(.+?)\\*{2})|(\\*(.+?)\\*)|(https?:\\/\\/[^\\s),]+)/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n while ((match = pattern.exec(text)) !== null) {\n // Add any plain text before this match\n if (match.index > lastIndex) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex, match.index) })\n }\n\n if (match[1] && match[2]) {\n // ***bold-italic***\n tokens.push({ type: 'bold-italic', text: match[2] })\n } else if (match[3] && match[4]) {\n // **bold**\n tokens.push({ type: 'bold', text: match[4] })\n } else if (match[5] && match[6]) {\n // *italic*\n tokens.push({ type: 'italic', text: match[6] })\n } else if (match[7]) {\n // URL\n tokens.push({ type: 'url', text: match[7] })\n }\n\n lastIndex = match.index + match[0].length\n }\n\n // Add any remaining plain text\n if (lastIndex < text.length) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex) })\n }\n\n return tokens\n}\n\n// ---------------------------------------------------------------------------\n// Segment comparison\n// ---------------------------------------------------------------------------\n\n/**\n * Shallow equality check for two segment arrays.\n * Compares type, text, trigger, value, displayText, and autoResolved fields.\n * Avoids JSON.stringify overhead for the common case.\n */\nexport function segmentsEqual(a: Segment[], b: Segment[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n\n for (let i = 0; i < a.length; i++) {\n const sa = a[i]\n const sb = b[i]\n if (sa.type !== sb.type) return false\n if (sa.type === 'text') {\n if (sb.type !== 'text' || sa.text !== sb.text) return false\n } else {\n if (\n sb.type !== 'chip' ||\n sa.trigger !== sb.trigger ||\n sa.value !== sb.value ||\n sa.displayText !== sb.displayText ||\n sa.autoResolved !== sb.autoResolved\n )\n return false\n }\n }\n return true\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merges adjacent text segments into single text segments.\n * Also removes empty text segments.\n */\nexport function mergeAdjacentTextSegments(segments: Segment[]): Segment[] {\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'text' && seg.text === '') continue\n\n const last = result[result.length - 1]\n if (seg.type === 'text' && last?.type === 'text') {\n // Merge with previous text segment\n result[result.length - 1] = { type: 'text', text: last.text + seg.text }\n } else {\n result.push(seg)\n }\n }\n\n return result\n}\n","/**\n * List auto-formatting logic for the PromptArea component.\n * Pure — no DOM dependencies, fully testable in Node.\n */\nimport type { Segment } from './types.ts'\nimport { replaceTextRange, segmentsToPlainText } from './prompt-area-engine.ts'\n\n/**\n * Information about a list line at a given cursor position.\n */\nexport type ListContext = {\n /** Offset in plain text where the line begins */\n lineStart: number\n /** The full prefix including indentation (e.g., \" • \") */\n prefix: string\n /** Number of indentation levels (each = 2 spaces) */\n indent: number\n /** Type of list */\n listType: 'bullet' | 'numbered'\n /** For bullet lists, the marker char actually used (`•`, `-`, or `*`) */\n marker?: string\n /** For numbered lists, the number */\n number?: number\n /** Offset in plain text where content after the prefix starts */\n contentStart: number\n}\n\n/**\n * Parsed shape of a single list line — the SINGLE source of truth for what\n * counts as a list line (both `getListContext` and the renumber engine derive\n * from this, so the bullet/number regexes live in exactly one place).\n *\n * Offsets (`numberStart`/`numberEnd`) are relative to the START of the line.\n */\ntype ParsedListLine =\n | { kind: 'bullet'; indent: number; marker: string; prefixLen: number }\n | {\n kind: 'numbered'\n indent: number\n number: number\n /** Offset of the first digit within the line. */\n numberStart: number\n /** Offset just past the last digit within the line (exclusive). */\n numberEnd: number\n prefixLen: number\n }\n\n/** Classifies a single line as a bullet/numbered list item, or null. */\nfunction parseListLine(line: string): ParsedListLine | null {\n const bulletMatch = line.match(/^(\\s*)([•\\-*]) /)\n if (bulletMatch) {\n return {\n kind: 'bullet',\n indent: Math.floor(bulletMatch[1].length / 2),\n marker: bulletMatch[2],\n prefixLen: bulletMatch[0].length,\n }\n }\n\n const numberMatch = line.match(/^(\\s*)(\\d+)\\. /)\n if (numberMatch) {\n const numberStart = numberMatch[1].length\n return {\n kind: 'numbered',\n indent: Math.floor(numberMatch[1].length / 2),\n number: parseInt(numberMatch[2], 10),\n numberStart,\n numberEnd: numberStart + numberMatch[2].length,\n prefixLen: numberMatch[0].length,\n }\n }\n\n return null\n}\n\n/**\n * Detects if the cursor is in a list line and returns context about it.\n *\n * @param text - The full plain text content\n * @param cursorPos - The cursor position (character offset from start)\n * @returns List context if the cursor is in a list line, null otherwise\n */\nexport function getListContext(text: string, cursorPos: number): ListContext | null {\n const lineStart = text.lastIndexOf('\\n', cursorPos - 1) + 1\n const lineEnd = text.indexOf('\\n', cursorPos)\n const line = text.slice(lineStart, lineEnd === -1 ? text.length : lineEnd)\n\n const parsed = parseListLine(line)\n if (!parsed) return null\n\n return {\n lineStart,\n prefix: line.slice(0, parsed.prefixLen),\n indent: parsed.indent,\n listType: parsed.kind,\n ...(parsed.kind === 'bullet' ? { marker: parsed.marker } : { number: parsed.number }),\n contentStart: lineStart + parsed.prefixLen,\n }\n}\n\n/**\n * Detects if the user just typed a list trigger pattern (e.g., \"- \" or \"* \")\n * and returns the segments with the replacement applied.\n */\nexport function autoFormatListPrefix(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const lineStart = plainText.lastIndexOf('\\n', cursorPos - 1) + 1\n const lineText = plainText.slice(lineStart, cursorPos)\n\n const match = lineText.match(/^(\\s*)[-*] $/)\n if (!match) return null\n\n const indent = match[1]\n const replacement = `${indent}• `\n const rangeStart = lineStart\n const rangeEnd = lineStart + lineText.length\n\n const newSegments = replaceTextRange(segments, rangeStart, rangeEnd, replacement)\n return {\n segments: newSegments,\n cursorOffset: lineStart + replacement.length,\n }\n}\n\n/**\n * Handles Enter key in a list line — continues the list or exits.\n */\nexport function insertListContinuation(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n const lineEnd = plainText.indexOf('\\n', cursorPos)\n const lineContent = plainText.slice(ctx.contentStart, lineEnd === -1 ? plainText.length : lineEnd)\n\n if (lineContent.trim() === '') {\n // Enter on an empty item: outdent one level if nested (Notion/Docs style),\n // otherwise remove the prefix and exit the list to plain text.\n if (ctx.indent > 0) {\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart + 2, '')\n return {\n segments: newSegments,\n cursorOffset: Math.max(ctx.lineStart, cursorPos - 2),\n }\n }\n const newSegments = replaceTextRange(\n segments,\n ctx.lineStart,\n ctx.lineStart + ctx.prefix.length,\n '',\n )\n return {\n segments: newSegments,\n cursorOffset: ctx.lineStart,\n }\n }\n\n const indent = ' '.repeat(ctx.indent)\n let nextPrefix: string\n if (ctx.listType === 'bullet') {\n nextPrefix = `${indent}${ctx.marker ?? '•'} `\n } else {\n const nextNum = (ctx.number ?? 1) + 1\n nextPrefix = `${indent}${nextNum}. `\n }\n\n const insertion = `\\n${nextPrefix}`\n const newSegments = replaceTextRange(segments, cursorPos, cursorPos, insertion)\n return {\n segments: newSegments,\n cursorOffset: cursorPos + insertion.length,\n }\n}\n\n/** Returns the indent level of the list line directly above `lineStart`, or null. */\nfunction getPrevListLineLevel(text: string, lineStart: number): number | null {\n if (lineStart === 0) return null\n const prevLineStart = text.lastIndexOf('\\n', lineStart - 2) + 1\n const parsed = parseListLine(text.slice(prevLineStart, lineStart - 1))\n return parsed ? parsed.indent : null\n}\n\n/**\n * Indents a list item by one level (adds 2 spaces before the prefix), capped at\n * one level deeper than the line above. An item can only nest under a preceding\n * sibling, so the first item of a list — or an item already one level below its\n * parent — cannot indent further (returns null). This keeps sub-items visually\n * connected to a parent instead of drifting arbitrarily deep.\n */\nexport function indentListItem(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n const prevLevel = getPrevListLineLevel(plainText, ctx.lineStart)\n const maxLevel = prevLevel === null ? 0 : prevLevel + 1\n if (ctx.indent >= maxLevel) return null\n\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart, ' ')\n return {\n segments: newSegments,\n cursorOffset: cursorPos + 2,\n }\n}\n\n/**\n * Outdents a list item by one level (removes 2 spaces from before the prefix).\n */\nexport function outdentListItem(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx || ctx.indent === 0) return null\n\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart + 2, '')\n return {\n segments: newSegments,\n cursorOffset: Math.max(ctx.lineStart, cursorPos - 2),\n }\n}\n\n/**\n * Removes the list prefix from the current line (e.g., on Backspace).\n */\nexport function removeListPrefix(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n if (cursorPos > ctx.contentStart) return null\n\n const newSegments = replaceTextRange(\n segments,\n ctx.lineStart,\n ctx.contentStart,\n ' '.repeat(ctx.indent),\n )\n return {\n segments: newSegments,\n cursorOffset: ctx.lineStart + ctx.indent * 2,\n }\n}\n\n/** A line that opens or closes a fenced code block (```), optionally indented. */\nconst FENCE_LINE = /^\\s*```/\n\n/** Swaps the leading list marker on a single line (\"- \" ↔ \"• \"). */\nfunction swapListPrefixLine(line: string, markdownEnabled: boolean): string {\n return markdownEnabled ? line.replace(/^(\\s*)- /, '$1• ') : line.replace(/^(\\s*)• /, '$1- ')\n}\n\n/**\n * Returns the set of line indices that sit inside a *balanced* fenced code\n * block (a ```…``` pair), so their leading \"- \"/\"• \" markers are preserved\n * verbatim. An unterminated (unpaired) fence marker is NOT protective — its\n * following lines still normalize — so a stray \"```\" in prose does not silently\n * suppress bullet normalization for the rest of the text.\n */\nfunction fenceProtectedLineIndices(lines: string[]): Set<number> {\n const protectedLines = new Set<number>()\n let openIndex = -1\n lines.forEach((line, i) => {\n if (!FENCE_LINE.test(line)) return\n if (openIndex === -1) {\n openIndex = i\n } else {\n for (let k = openIndex; k <= i; k++) protectedLines.add(k)\n openIndex = -1\n }\n })\n return protectedLines\n}\n\n/**\n * Normalizes markdown list prefixes in a raw text string (single source of\n * truth for the bullet-glyph swap, shared by segment normalization and paste):\n * - When markdown is enabled, converts \"- \" at line starts to \"• \"\n * - When markdown is disabled, converts \"• \" at line starts to \"- \"\n *\n * Lines inside a balanced ```…``` block are preserved verbatim.\n */\nexport function normalizeListPrefixText(text: string, markdownEnabled: boolean): string {\n const lines = text.split('\\n')\n const protectedLines = fenceProtectedLineIndices(lines)\n return lines\n .map((line, i) => (protectedLines.has(i) ? line : swapListPrefixLine(line, markdownEnabled)))\n .join('\\n')\n}\n\n/**\n * Normalizes markdown list prefixes across text segments. See\n * {@link normalizeListPrefixText} for the per-line rule. Fence detection spans\n * the whole document (text segments flattened to a global line sequence), so a\n * code block split into per-line text segments on paste still has its \"- \" lines\n * preserved, while an unterminated fence does not suppress later bullets.\n */\nexport function normalizeListPrefixes(segments: Segment[], markdownEnabled: boolean): Segment[] {\n const globalLines: string[] = []\n segments.forEach((seg) => {\n if (seg.type === 'text') globalLines.push(...seg.text.split('\\n'))\n })\n const protectedLines = fenceProtectedLineIndices(globalLines)\n\n let globalIndex = 0\n let changed = false\n const result = segments.map((seg) => {\n if (seg.type !== 'text') return seg\n const newText = seg.text\n .split('\\n')\n .map((line) => {\n const out = protectedLines.has(globalIndex)\n ? line\n : swapListPrefixLine(line, markdownEnabled)\n globalIndex++\n return out\n })\n .join('\\n')\n if (newText === seg.text) return seg\n changed = true\n return { ...seg, text: newText }\n })\n return changed ? result : segments\n}\n\n// ---------------------------------------------------------------------------\n// Ordered-list renumbering\n//\n// The visible number is a projection of position (like BlockNote/ProseMirror/\n// Notion), recomputed on every structural edit rather than trusted as stored\n// text. A per-indent-level counter STACK models nested lists: descending starts\n// a fresh counter at 1, ascending continues the shallower level and clears\n// deeper ones. Every run restarts at 1, so `1. 1. 1.` rebuilds to `1. 2. 3.`\n// and Tab-indenting an item restarts its sublist at 1.\n// ---------------------------------------------------------------------------\n\n/**\n * A single rewritten number's digit run, in the INPUT text's coordinates.\n * `[oldStart, oldEnd)` spans only the digits (never the indentation or `. `).\n */\nexport type NumberEdit = { oldStart: number; oldEnd: number; newText: string }\n\n/**\n * Whether the text holds a genuine ordered-list run worth renumbering — a run\n * of 2+ consecutive same-level numbered lines that either starts at 1 or is\n * already a contiguous `n, n+1, …` sequence. Used to gate the paste path so a\n * copied list fragment (`3. 4. 5.` → renumber, or a broken `1. 1. 1.`) is\n * rebuilt, while incidental numeric-leading prose that `parseListLine` would\n * otherwise treat as a list — `1985. Born / 2020. Died`, `5. / 10. / 15.` — is\n * left untouched.\n */\nexport function hasOrderedListRun(text: string): boolean {\n let runLevel: number | null = null\n let runStart = 0\n let prevNumber = 0\n let runLength = 0\n let sequential = true\n\n for (const line of text.split('\\n')) {\n const parsed = parseListLine(line)\n if (parsed?.kind === 'numbered' && parsed.indent === runLevel) {\n sequential = sequential && parsed.number === prevNumber + 1\n prevNumber = parsed.number\n runLength++\n if (runLength >= 2 && (runStart === 1 || sequential)) return true\n } else if (parsed?.kind === 'numbered') {\n // Start a fresh run at this line's level.\n runLevel = parsed.indent\n runStart = parsed.number\n prevNumber = parsed.number\n runLength = 1\n sequential = true\n } else {\n runLevel = null\n runLength = 0\n }\n }\n\n return false\n}\n\n/**\n * Recomputes ordered-list numbering across the whole text. Returns the new text\n * plus the list of changed digit runs (ascending by `oldStart`) for cursor\n * remapping. When nothing changes, returns the SAME text reference and an empty\n * `edits` array — the no-op guard that keeps this off the typing hot path.\n */\nexport function renumberOrderedListLines(text: string): { text: string; edits: NumberEdit[] } {\n // Cheap pre-gate: with no ordered-list line there is nothing to renumber, so\n // skip the per-line scan and throwaway rebuild. This runs on every structural\n // edit (Enter, Tab, bold/italic wrap) and each paste, most of which never\n // touch a numbered list.\n if (!/^[ \\t]*\\d+\\. /m.test(text)) return { text, edits: [] }\n\n const counters = new Map<number, number>()\n const edits: NumberEdit[] = []\n const lines = text.split('\\n')\n let out = ''\n let lineStart = 0\n\n const clearDeeperThan = (level: number, inclusive: boolean) => {\n for (const key of counters.keys()) {\n if (inclusive ? key >= level : key > level) counters.delete(key)\n }\n }\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const parsed = parseListLine(line)\n\n if (!parsed) {\n // A blank or plain (non-list) line breaks every open list run.\n counters.clear()\n out += line\n } else if (parsed.kind === 'bullet') {\n // A bullet interrupts numbered runs at its level and deeper; a shallower\n // numbered list continues across it.\n clearDeeperThan(parsed.indent, true)\n out += line\n } else {\n const level = parsed.indent\n clearDeeperThan(level, false)\n const current = counters.get(level)\n const n = current === undefined ? 1 : current + 1\n counters.set(level, n)\n\n const newDigits = String(n)\n if (newDigits === String(parsed.number)) {\n out += line\n } else {\n edits.push({\n oldStart: lineStart + parsed.numberStart,\n oldEnd: lineStart + parsed.numberEnd,\n newText: newDigits,\n })\n out += line.slice(0, parsed.numberStart) + newDigits + line.slice(parsed.numberEnd)\n }\n }\n\n if (i < lines.length - 1) out += '\\n'\n lineStart += line.length + 1 // + 1 for the consumed '\\n'\n }\n\n return edits.length === 0 ? { text, edits } : { text: out, edits }\n}\n\n/**\n * Remaps a caret/selection offset (in the renumber INPUT's coordinates) across\n * the digit-run edits. A single scalar delta is wrong: many spans each change\n * width, so the shift depends on how many changed spans lie strictly before the\n * offset, with a clamp when the offset sits inside a resized number.\n */\nexport function remapOffset(old: number, edits: NumberEdit[]): number {\n let shift = 0\n for (const e of edits) {\n if (e.oldEnd <= old) {\n shift += e.newText.length - (e.oldEnd - e.oldStart) // fully before\n } else if (e.oldStart >= old) {\n break // this and every later span is after the offset\n } else {\n return e.oldStart + shift + Math.min(old - e.oldStart, e.newText.length) // inside → clamp\n }\n }\n return old + shift\n}\n\n/**\n * Segment-level renumber used by the edit-commit path. Applies the digit-run\n * edits to the segments (right-to-left so earlier offsets stay valid) and\n * returns the changed spans for {@link remapOffset}. Digit runs are pure text at\n * line starts, so chips are never touched.\n */\nexport function renumberOrderedListSegments(segments: Segment[]): {\n segments: Segment[]\n edits: NumberEdit[]\n} {\n const { edits } = renumberOrderedListLines(segmentsToPlainText(segments))\n if (edits.length === 0) return { segments, edits }\n\n let result = segments\n for (let i = edits.length - 1; i >= 0; i--) {\n const e = edits[i]\n result = replaceTextRange(result, e.oldStart, e.oldEnd, e.newText)\n }\n return { segments: result, edits }\n}\n","/**\n * Type-safe DOM helper functions for PromptArea.\n *\n * These replace all `as` type assertions with proper type guards,\n * following the codebase rule: \"Never use `any` or `as` assertions.\"\n */\n\nimport type { ChipSegment } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Type Guards\n// ---------------------------------------------------------------------------\n\n/**\n * Type guard: checks if a DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return node instanceof HTMLElement\n}\n\n/**\n * Type guard: checks if a DOM node is a chip element\n * (an HTMLElement with data-chip-trigger attribute).\n */\nexport function isChipElement(node: Node): node is HTMLElement {\n return node instanceof HTMLElement && node.dataset.chipTrigger !== undefined\n}\n\n/**\n * Type guard: checks if a DOM node is a BR element.\n */\nexport function isBRElement(node: Node): node is HTMLBRElement {\n return node instanceof HTMLBRElement\n}\n\n/**\n * Type guard: checks if a DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node instanceof Text\n}\n\n/**\n * Checks whether a chip element was auto-resolved (created by pressing space\n * on resolveOnSpace triggers, rather than explicit dropdown selection).\n */\nexport function getChipAutoResolved(node: Node): boolean {\n return isChipElement(node) && node.dataset.chipAutoResolved === 'true'\n}\n\n/**\n * Type guard: checks if a DOM node is a URL link element\n * (an HTMLAnchorElement with data-url attribute).\n */\nexport function isLinkElement(node: Node): node is HTMLAnchorElement {\n return node instanceof HTMLAnchorElement && node.dataset.url === 'true'\n}\n\n// ---------------------------------------------------------------------------\n// Safe JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Safely parses a JSON string, returning `unknown` instead of `any`.\n * Returns `undefined` if parsing fails.\n */\nexport function safeJsonParse(json: string): unknown {\n try {\n // JSON.parse returns `any` by default. We narrow it to `unknown`\n // which is the safest pattern — callers must validate before use.\n const parsed: unknown = JSON.parse(json)\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Safely serializes a value to JSON, returning undefined on failure.\n */\nexport function safeJsonStringify(value: unknown): string | undefined {\n try {\n return JSON.stringify(value)\n } catch {\n return undefined\n }\n}\n\n// ---------------------------------------------------------------------------\n// DOM reading helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Reads the chip trigger character from a chip element's dataset.\n * Returns undefined if the node is not a chip element.\n */\nexport function getChipTrigger(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipTrigger\n}\n\n/**\n * Reads the chip value from a chip element's dataset.\n */\nexport function getChipValue(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipValue\n}\n\n/**\n * Reads the chip display text from a chip element's dataset.\n */\nexport function getChipDisplay(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipDisplay ?? node.textContent ?? undefined\n}\n\n/**\n * Reads and safely parses the chip data from a chip element's dataset.\n */\nexport function getChipData(node: Node): unknown {\n if (!isChipElement(node)) return undefined\n const raw = node.dataset.chipData\n if (!raw) return undefined\n return safeJsonParse(raw)\n}\n\n/**\n * Length of a chip's plain-text representation (`trigger + displayText`).\n *\n * This is the single definition used wherever DOM offsets are mapped to the\n * plain-text model (cursor mapping, selection sizing). The fallbacks mirror\n * how chips are rendered: `chipDisplay` is always set, but we degrade to\n * `textContent` for resilience against externally-mutated nodes.\n */\nexport function chipNodeTextLength(node: HTMLElement): number {\n const trigger = node.dataset.chipTrigger ?? ''\n const display = node.dataset.chipDisplay ?? node.textContent ?? ''\n return trigger.length + display.length\n}\n\n/**\n * Reads a chip element into a `ChipSegment`, mirroring how chips are written\n * in `renderSegmentsToDOM`. Returns null when the node is not a chip or is\n * missing a required attribute (trigger, value, or display text).\n *\n * This is the single chip reader shared by the DOM->model sync, chip-click\n * delegation, and clipboard serialization, so they cannot diverge on which\n * fields are required or how optional `data` / `autoResolved` are attached.\n */\nexport function chipNodeToSegment(node: Node): ChipSegment | null {\n if (!isChipElement(node)) return null\n\n const trigger = getChipTrigger(node)\n const value = getChipValue(node)\n const displayText = getChipDisplay(node)\n if (!trigger || value === undefined || !displayText) return null\n\n const data = getChipData(node)\n const autoResolved = getChipAutoResolved(node)\n\n return {\n type: 'chip',\n trigger,\n value,\n displayText,\n ...(data !== undefined ? { data } : {}),\n ...(autoResolved ? { autoResolved: true } : {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// DOM manipulation helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Finds the index of a direct child node within a parent element.\n * Returns -1 if not found.\n */\nexport function indexOfChildNode(parent: HTMLElement, child: Node): number {\n const children = parent.childNodes\n for (let i = 0; i < children.length; i++) {\n if (children[i] === child) return i\n }\n return -1\n}\n\n/**\n * Whether a direct editor child node produces a segment when read by\n * `readSegmentsFromDOM` in use-prompt-area.ts. This is the single predicate\n * shared with `domChildIndexToSegmentIndex` below, so a DOM child index can\n * never map to a different segment index than the one the reader actually\n * produces — decoration elements (the URL `<a>` from `decorateURLsInEditor`,\n * the markdown `<span data-md>` from `decorateMarkdownInEditor`) fall through\n * to the reader's \"unknown element\" branch and DO produce a text segment, so\n * they must count here too, not just chips/text/`<br>`. A chip element only\n * counts if `chipNodeToSegment` would actually accept it — the reader skips a\n * chip missing a required attribute (trigger/value/display), so this must too.\n */\nexport function childProducesSegment(child: Node): boolean {\n if (child.nodeType === Node.TEXT_NODE) return (child.textContent ?? '') !== ''\n if (isBRElement(child)) return !child.dataset.sentinel\n if (isChipElement(child)) return chipNodeToSegment(child) !== null\n if (isHTMLElement(child)) return (child.textContent ?? '') !== ''\n return false\n}\n\n/**\n * Maps the index of a direct child node within the editor to the index of the\n * corresponding segment in the model array, by counting `childProducesSegment`\n * matches up to (but not including) `childIndex`.\n *\n * Keeping this in one place ensures chip-removal, chip-revert, and chip\n * in-place replacement all agree on the exact same mapping rules as the reader.\n */\nexport function domChildIndexToSegmentIndex(editor: HTMLElement, childIndex: number): number {\n let segIdx = 0\n for (let i = 0; i < childIndex; i++) {\n if (childProducesSegment(editor.childNodes[i])) segIdx++\n }\n return segIdx\n}\n\n/**\n * Gets the direct child of `ancestor` that contains `descendant`.\n * Walks up from descendant until we find a node whose parent is ancestor.\n * Returns null if descendant is not inside ancestor.\n */\nexport function getDirectChildContaining(ancestor: HTMLElement, descendant: Node): Node | null {\n let node: Node | null = descendant\n while (node !== null) {\n if (node.parentNode === ancestor) return node\n node = node.parentNode\n }\n return null\n}\n\n/**\n * Unwraps a block element (div, p) by replacing it with its child nodes\n * plus a trailing BR. Used for browser DOM normalization.\n */\nexport function unwrapBlockElement(parent: HTMLElement, block: HTMLElement): void {\n const fragment = document.createDocumentFragment()\n\n // Move all children to fragment\n while (block.firstChild) {\n fragment.appendChild(block.firstChild)\n }\n\n // Add a BR after the unwrapped content\n fragment.appendChild(document.createElement('br'))\n\n parent.replaceChild(fragment, block)\n}\n\n/**\n * Normalizes the editor DOM after browser mutations.\n *\n * Browsers insert various wrapper elements on Enter/paste:\n * - Chrome wraps new lines in <div>\n * - Safari may use <div><br></div>\n * - Some use <p> tags\n *\n * This function unwraps all non-chip block elements, leaving only:\n * - Text nodes\n * - <br> elements\n * - Chip <span> elements (with data-chip-trigger)\n */\nexport function normalizeEditorDOM(editor: HTMLElement): boolean {\n let changed = false\n const blockTags = new Set(['DIV', 'P', 'SECTION', 'ARTICLE', 'BLOCKQUOTE'])\n\n // Iterate backwards since we're modifying the DOM\n for (let i = editor.childNodes.length - 1; i >= 0; i--) {\n const child = editor.childNodes[i]\n\n // Skip non-element nodes, chip elements, and BR elements\n if (!(child instanceof HTMLElement)) continue\n if (child.dataset.chipTrigger !== undefined) continue\n if (child instanceof HTMLBRElement) continue\n\n const tag = child.tagName\n if (blockTags.has(tag)) {\n unwrapBlockElement(editor, child)\n changed = true\n } else if (\n tag === 'FONT' ||\n tag === 'B' ||\n tag === 'I' ||\n tag === 'U' ||\n tag === 'STRONG' ||\n tag === 'EM' ||\n tag === 'A' ||\n tag === 'SPAN'\n ) {\n // Unwrap inline formatting/decoration elements (browser-inserted or markdown decorations)\n const text = child.textContent ?? ''\n if (text) {\n editor.replaceChild(document.createTextNode(text), child)\n } else {\n editor.removeChild(child)\n }\n changed = true\n }\n }\n\n // Merge adjacent text nodes\n editor.normalize()\n\n return changed\n}\n\n// ---------------------------------------------------------------------------\n// URL decoration\n// ---------------------------------------------------------------------------\n\n/** URL pattern for detecting URLs in text content */\nconst URL_PATTERN = /https?:\\/\\/[^\\s),]+/g\n\n/**\n * Walks direct-child text nodes in the editor and wraps URL text in\n * `<a>` elements for visual styling and clickability.\n *\n * This is a DOM-only decoration — it does NOT modify the segment model.\n * The `<a>` elements are stripped by `normalizeEditorDOM` on every input cycle,\n * so they are re-applied fresh each time.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateURLsInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n // Collect text nodes first (avoid modifying while iterating)\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n URL_PATTERN.lastIndex = 0\n const matches: Array<{ url: string; index: number }> = []\n let match: RegExpExecArray | null\n\n while ((match = URL_PATTERN.exec(text)) !== null) {\n // Trim trailing punctuation that's likely not part of the URL\n let url = match[0]\n while (url.length > 0 && /[.;:!?]$/.test(url)) {\n url = url.slice(0, -1)\n }\n if (url.length > 0) {\n matches.push({ url, index: match.index })\n }\n }\n\n if (matches.length === 0) continue\n\n const parent = textNode.parentNode\n if (!parent) continue\n\n // Validate URLs upfront – only keep those with safe protocols (CWE-79)\n const safeMatches: Array<{ url: string; href: string; index: number }> = []\n for (const { url, index } of matches) {\n try {\n const parsed = new URL(url)\n if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {\n safeMatches.push({ url, href: parsed.href, index })\n }\n } catch {\n // skip malformed URLs\n }\n }\n\n if (safeMatches.length === 0) continue\n\n decorated = true\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { url, href, index } of safeMatches) {\n // Text before this URL\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n\n // Create the link element\n const anchor = document.createElement('a')\n anchor.href = href\n anchor.target = '_blank'\n anchor.rel = 'noopener noreferrer'\n anchor.dataset.url = 'true'\n anchor.className = 'text-accent hover:text-accent-hover underline cursor-pointer'\n anchor.textContent = url\n fragment.appendChild(anchor)\n\n lastIndex = index + url.length\n }\n\n // Text after the last URL\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n// ---------------------------------------------------------------------------\n// Markdown inline decoration\n// ---------------------------------------------------------------------------\n\n/** Pattern to find ***bold-italic***, **bold**, and *italic* markdown spans */\nconst MARKDOWN_INLINE_PATTERN = /(\\*{3})(.+?)\\*{3}|(\\*{2})(.+?)\\*{2}|(\\*)(.+?)\\*/g\n\n/**\n * Walks direct-child text nodes in the editor and wraps markdown-formatted\n * text (`**bold**`, `*italic*`, `***bold-italic***`) in styled `<span>` elements.\n *\n * This is a DOM-only decoration — it does NOT modify the segment model.\n * The `<span>` elements are stripped by `normalizeEditorDOM` on every input cycle,\n * so they are re-applied fresh each time.\n *\n * The `*` markers stay visible in the text; only the CSS styling changes.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateMarkdownInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n // Collect text nodes first (avoid modifying while iterating)\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n MARKDOWN_INLINE_PATTERN.lastIndex = 0\n const matches: Array<{\n fullMatch: string\n marker: string\n content: string\n index: number\n className: string\n }> = []\n let match: RegExpExecArray | null\n\n while ((match = MARKDOWN_INLINE_PATTERN.exec(text)) !== null) {\n if (match[1] && match[2]) {\n // ***bold-italic***\n matches.push({\n fullMatch: match[0],\n marker: match[1],\n content: match[2],\n index: match.index,\n className: 'font-bold italic',\n })\n } else if (match[3] && match[4]) {\n // **bold**\n matches.push({\n fullMatch: match[0],\n marker: match[3],\n content: match[4],\n index: match.index,\n className: 'font-bold',\n })\n } else if (match[5] && match[6]) {\n // *italic*\n matches.push({\n fullMatch: match[0],\n marker: match[5],\n content: match[6],\n index: match.index,\n className: 'italic',\n })\n }\n }\n\n if (matches.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { fullMatch, marker, content, index, className } of matches) {\n // Text before this match\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n\n // Parent span — textContent still returns full match (e.g. \"**world**\")\n const span = document.createElement('span')\n span.dataset.md = 'true'\n\n // Opening marker (visually hidden)\n const openMarker = document.createElement('span')\n openMarker.className = 'prompt-area-md-marker'\n openMarker.textContent = marker\n\n // Styled content\n const styledContent = document.createElement('span')\n styledContent.className = className\n styledContent.textContent = content\n\n // Closing marker (visually hidden)\n const closeMarker = document.createElement('span')\n closeMarker.className = 'prompt-area-md-marker'\n closeMarker.textContent = marker\n\n span.appendChild(openMarker)\n span.appendChild(styledContent)\n span.appendChild(closeMarker)\n fragment.appendChild(span)\n\n lastIndex = index + fullMatch.length\n }\n\n // Text after the last match\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/** Matches a `•` bullet glyph at the start of a line (optionally indented). */\nconst LIST_BULLET_PATTERN = /(^|\\n)([ \\t]*)•/g\n\n/**\n * Walks direct-child text nodes and wraps each line-leading `•` bullet glyph in\n * a `<span class=\"prompt-area-list-bullet\">` so CSS can size it up (the raw\n * U+2022 glyph renders much smaller than the surrounding text).\n *\n * Like {@link decorateMarkdownInEditor}, this is a DOM-only decoration: the span\n * is stripped by {@link normalizeEditorDOM} on every input cycle, so the `•`\n * stays a plain character in the segment model and is re-decorated each render.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateBulletsInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent?.includes('•')) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n LIST_BULLET_PATTERN.lastIndex = 0\n const bulletIndices: number[] = []\n let match: RegExpExecArray | null\n while ((match = LIST_BULLET_PATTERN.exec(text)) !== null) {\n bulletIndices.push(match.index + match[1].length + match[2].length)\n }\n\n if (bulletIndices.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const index of bulletIndices) {\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n const span = document.createElement('span')\n span.dataset.md = 'true'\n span.className = 'prompt-area-list-bullet'\n span.textContent = '•'\n fragment.appendChild(span)\n lastIndex = index + 1 // the bullet is a single character\n }\n\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/**\n * Matches the line-leading whitespace run of an indented list line (bullet or\n * numbered). The lookahead keeps the list prefix itself out of the capture, so\n * only the indentation is wrapped.\n */\nconst LIST_INDENT_PATTERN = /(^|\\n)([ \\t]+)(?=(?:[•\\-*] |\\d+\\. ))/g\n\n/**\n * Wraps each list line's leading indentation in an inline-block\n * `<span class=\"prompt-area-list-indent\">` sized per nesting level, so nested\n * items read with a wide, Notion-like indent instead of the raw 2-space gap.\n *\n * Like the other decorations this is display-only: the span keeps the original\n * whitespace as its textContent (so plain-text length and caret offsets are\n * unchanged) and is stripped by {@link normalizeEditorDOM} each input cycle.\n * Must run BEFORE the node-splitting passes ({@link decorateURLsInEditor},\n * {@link decorateMarkdownInEditor}, {@link decorateBulletsInEditor}) so every\n * direct-child text node is still a whole line — otherwise a mid-line split\n * fragment beginning with whitespace would let the `^` anchor false-match\n * non-line-leading whitespace.\n *\n * @returns Whether any decorations were applied\n */\nexport function decorateListIndentInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node)) textNodes.push(node)\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n LIST_INDENT_PATTERN.lastIndex = 0\n const runs: { start: number; end: number }[] = []\n let match: RegExpExecArray | null\n while ((match = LIST_INDENT_PATTERN.exec(text)) !== null) {\n const start = match.index + match[1].length\n runs.push({ start, end: start + match[2].length })\n }\n\n if (runs.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { start, end } of runs) {\n if (start > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)))\n }\n const whitespace = text.slice(start, end)\n const level = Math.floor(whitespace.length / 2)\n const span = document.createElement('span')\n span.dataset.md = 'true'\n span.className = 'prompt-area-list-indent'\n span.style.width = `calc(var(--prompt-area-indent-size, 1.5em) * ${level})`\n span.textContent = whitespace\n fragment.appendChild(span)\n lastIndex = end\n }\n\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/**\n * Applies every display-only decoration to the editor in one pass: URL links\n * always, plus markdown emphasis, list indentation, and list bullets when\n * markdown mode is on. Each decoration is stripped by {@link normalizeEditorDOM}\n * on the next input cycle and re-applied here, so the segment model is never\n * mutated.\n *\n * List indentation runs FIRST, while each direct-child text node is still a\n * whole line: the URL and markdown passes split text nodes mid-line, and a tail\n * fragment starting with whitespace would let the indent regex's `^` anchor\n * false-match non-line-leading whitespace (e.g. `see http://x 1. y`).\n */\nexport function decorateEditor(editor: HTMLElement, markdownEnabled: boolean): void {\n // Whole-line passes run FIRST, while each direct-child text node is still a\n // full line. The URL and markdown passes split text nodes mid-line, so a tail\n // fragment beginning with \"•\" would let the bullet regex's `^` anchor\n // false-match a mid-line separator (e.g. `**bold** • middle`).\n if (markdownEnabled) {\n decorateListIndentInEditor(editor)\n decorateBulletsInEditor(editor)\n }\n decorateURLsInEditor(editor)\n if (markdownEnabled) decorateMarkdownInEditor(editor)\n}\n\n// ---------------------------------------------------------------------------\n// Selection helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the first Range from the current window selection, or null if\n * there is no selection or it has no ranges.\n */\nexport function getSelectionRange(): Range | null {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n return sel.getRangeAt(0)\n}\n","/**\n * Cursor/selection utilities for the PromptArea contentEditable.\n *\n * Invariants:\n * - All functions are synchronous. Never return a promise or cross a microtask\n * boundary after a DOM mutation — that can cause the browser to fire\n * `selectionchange` and reset the caret.\n * - Never cache a `Selection` or `Range` across calls. Ranges become detached\n * after DOM mutations. Each function calls `window.getSelection()` or\n * `getSelectionRange()` fresh.\n * - Chip nodes are treated atomically via `isChipElement` — we never descend\n * into a contentEditable=false subtree when mapping offsets.\n */\nimport {\n chipNodeTextLength,\n getDirectChildContaining,\n getSelectionRange,\n indexOfChildNode,\n isBRElement,\n isChipElement,\n isHTMLElement,\n} from './dom-helpers.ts'\n\nexport type SavedCursor = {\n nodeIndex: number\n offset: number\n}\n\nexport function saveCursorPosition(editor: HTMLElement): SavedCursor | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const node = range.startContainer\n if (node === editor) {\n return { nodeIndex: range.startOffset, offset: 0 }\n }\n\n // Walk up to find the direct child of editor using type-safe helper\n const directChild = getDirectChildContaining(editor, node)\n if (!directChild) return null\n\n const nodeIndex = indexOfChildNode(editor, directChild)\n return { nodeIndex, offset: range.startOffset }\n}\n\nexport function restoreCursorPosition(editor: HTMLElement, saved: SavedCursor): void {\n const sel = window.getSelection()\n if (!sel) return\n\n const childNodes = editor.childNodes\n if (childNodes.length === 0) return\n\n const range = document.createRange()\n\n if (saved.nodeIndex >= childNodes.length) {\n const lastChild = childNodes[childNodes.length - 1]\n if (lastChild.nodeType === Node.TEXT_NODE) {\n range.setStart(lastChild, (lastChild.textContent ?? '').length)\n } else {\n range.setStartAfter(lastChild)\n }\n } else {\n const targetNode = childNodes[saved.nodeIndex]\n if (targetNode.nodeType === Node.TEXT_NODE) {\n const maxOffset = (targetNode.textContent ?? '').length\n range.setStart(targetNode, Math.min(saved.offset, maxOffset))\n } else {\n range.setStartAfter(targetNode)\n }\n }\n\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\nexport function getCursorOffset(editor: HTMLElement): number | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const preRange = document.createRange()\n preRange.selectNodeContents(editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n\n return getTextLengthInRange(preRange)\n}\n\n/**\n * Create a collapsed Range at the given plain-text offset inside the editor.\n * Returns null if the offset can't be mapped to a DOM position.\n */\nexport function createRangeAtOffset(editor: HTMLElement, targetOffset: number): Range | null {\n const pos = findDOMPosition(editor, targetOffset)\n if (!pos) return null\n\n const range = document.createRange()\n range.setStart(pos.node, pos.offset)\n range.collapse(true)\n return range\n}\n\nexport function setCursorAtOffset(editor: HTMLElement, targetOffset: number): void {\n const sel = window.getSelection()\n if (!sel) return\n\n const pos = findDOMPosition(editor, targetOffset)\n if (pos) {\n const range = document.createRange()\n range.setStart(pos.node, pos.offset)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n return\n }\n\n // Fallback: place cursor at end\n const range = document.createRange()\n range.selectNodeContents(editor)\n range.collapse(false)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\nexport function getTextLengthInRange(range: Range): number {\n const fragment = range.cloneContents()\n let length = 0\n\n const walk = (node: Node): void => {\n if (node.nodeType === Node.TEXT_NODE) {\n length += (node.textContent ?? '').length\n } else if (isChipElement(node)) {\n length += chipNodeTextLength(node)\n } else if (isHTMLElement(node) && node.tagName === 'BR') {\n if (node.dataset.sentinel) return // skip sentinel <br>\n length += 1\n } else if (isHTMLElement(node)) {\n node.childNodes.forEach(walk)\n }\n }\n\n fragment.childNodes.forEach(walk)\n return length\n}\n\n/**\n * Returns the start and end plain-text offsets of the current selection.\n * Returns null if there's no selection or it's outside the editor.\n */\nexport function getSelectionOffsets(editor: HTMLElement): { start: number; end: number } | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const startRange = document.createRange()\n startRange.selectNodeContents(editor)\n startRange.setEnd(range.startContainer, range.startOffset)\n const start = getTextLengthInRange(startRange)\n\n if (range.collapsed) return { start, end: start }\n\n const endRange = document.createRange()\n endRange.selectNodeContents(editor)\n endRange.setEnd(range.endContainer, range.endOffset)\n const end = getTextLengthInRange(endRange)\n\n return { start, end }\n}\n\n/**\n * Sets a (potentially non-collapsed) selection at the given plain-text offsets.\n * Used to restore selection after markdown wrap/unwrap operations.\n */\nexport function setSelectionAtOffsets(\n editor: HTMLElement,\n startOffset: number,\n endOffset: number,\n): void {\n const sel = window.getSelection()\n if (!sel) return\n\n if (startOffset === endOffset) {\n setCursorAtOffset(editor, startOffset)\n return\n }\n\n const startPos = findDOMPosition(editor, startOffset)\n const endPos = findDOMPosition(editor, endOffset)\n if (!startPos || !endPos) return\n\n const range = document.createRange()\n range.setStart(startPos.node, startPos.offset)\n range.setEnd(endPos.node, endPos.offset)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\n/**\n * Maps a plain-text offset to a DOM node + offset pair.\n * Recurses into decoration elements (markdown spans, URL anchors).\n */\nexport function findDOMPosition(\n container: HTMLElement,\n targetOffset: number,\n): { node: Node; offset: number } | null {\n let remaining = targetOffset\n\n for (let i = 0; i < container.childNodes.length; i++) {\n const child = container.childNodes[i]\n\n if (child.nodeType === Node.TEXT_NODE) {\n const len = (child.textContent ?? '').length\n if (remaining <= len) {\n return { node: child, offset: remaining }\n }\n remaining -= len\n } else if (isChipElement(child)) {\n const chipLen = chipNodeTextLength(child)\n if (remaining <= chipLen) {\n // Position after the chip element\n return { node: container, offset: i + 1 }\n }\n remaining -= chipLen\n } else if (isBRElement(child)) {\n if (child.dataset.sentinel) continue // skip sentinel <br>\n if (remaining <= 1) {\n return { node: container, offset: i + 1 }\n }\n remaining -= 1\n } else if (isHTMLElement(child)) {\n // Decoration element (markdown span, URL anchor) — recurse\n const textLen = (child.textContent ?? '').length\n if (remaining <= textLen) {\n const result = findDOMPosition(child, remaining)\n if (result) return result\n }\n remaining -= textLen\n }\n }\n\n // Fallback: end of container\n return { node: container, offset: container.childNodes.length }\n}\n","/**\n * Clipboard-related DOM utilities for the PromptArea component.\n * Handles serializing selections and inserting pasted segments at the cursor.\n *\n * Not merged into dom-helpers.ts to avoid overcrowding that file with\n * clipboard I/O concerns alongside its DOM traversal and chip-accessor helpers.\n */\nimport type { Segment, ChipSegment } from './types.ts'\nimport {\n chipNodeToSegment,\n getChipDisplay,\n getChipTrigger,\n getSelectionRange,\n isChipElement,\n isHTMLElement,\n} from './dom-helpers.ts'\nimport { mergeAdjacentTextSegments } from './prompt-area-engine.ts'\nimport { getTextLengthInRange } from './cursor-helpers.ts'\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Visitor callbacks for {@link walkFragmentNodes}. Each fires for the\n * corresponding node kind encountered during a depth-first walk.\n */\ntype FragmentVisitor = {\n /** A text node — receives its text content (may be empty). */\n onText: (text: string) => void\n /** A chip element (has `data-chip-trigger`). */\n onChip: (node: HTMLElement) => void\n /** A `<br>` line break. */\n onBreak: () => void\n}\n\n/**\n * Depth-first walk of a selection fragment, classifying each node as text,\n * chip, or line break and recursing into any other element (decoration spans,\n * anchors, browser-inserted wrappers).\n *\n * Both fragment serializers share this single traversal so they cannot drift\n * on which nodes count as chips/breaks or how nested decorations are unwrapped.\n */\nfunction walkFragmentNodes(fragment: DocumentFragment, visitor: FragmentVisitor): void {\n const walk = (node: Node): void => {\n if (node.nodeType === Node.TEXT_NODE) {\n visitor.onText(node.textContent ?? '')\n } else if (isChipElement(node)) {\n visitor.onChip(node)\n } else if (isHTMLElement(node) && node.tagName === 'BR') {\n visitor.onBreak()\n } else {\n node.childNodes.forEach(walk)\n }\n }\n\n fragment.childNodes.forEach(walk)\n}\n\n/**\n * Serializes a DocumentFragment (from selection) to plain text,\n * converting chip elements to their `trigger + displayText` form.\n */\nexport function serializeFragmentToPlainText(fragment: DocumentFragment): string {\n let text = ''\n\n walkFragmentNodes(fragment, {\n onText: (value) => {\n text += value\n },\n onChip: (node) => {\n text += (getChipTrigger(node) ?? '') + (getChipDisplay(node) ?? '')\n },\n onBreak: () => {\n text += '\\n'\n },\n })\n\n return text\n}\n\n/**\n * Serializes a DocumentFragment to an array of Segment objects,\n * preserving chip data for internal copy/paste.\n */\nexport function serializeFragmentToSegments(fragment: DocumentFragment): Segment[] {\n const segments: Segment[] = []\n\n walkFragmentNodes(fragment, {\n onText: (value) => {\n if (value) segments.push({ type: 'text', text: value })\n },\n onChip: (node) => {\n const chip = chipNodeToSegment(node)\n if (chip) segments.push(chip)\n },\n onBreak: () => {\n segments.push({ type: 'text', text: '\\n' })\n },\n })\n\n return segments\n}\n\n/**\n * Parses segment JSON from the clipboard. Returns null if invalid.\n */\nexport function parseSegmentsFromClipboard(json: string): Segment[] | null {\n try {\n const parsed: unknown = JSON.parse(json)\n if (!Array.isArray(parsed)) return null\n\n const segments: Segment[] = []\n for (const item of parsed) {\n if (!isRecord(item)) return null\n\n if (item.type === 'text' && typeof item.text === 'string') {\n segments.push({ type: 'text', text: item.text })\n } else if (\n item.type === 'chip' &&\n typeof item.trigger === 'string' &&\n typeof item.value === 'string' &&\n typeof item.displayText === 'string'\n ) {\n const chip: ChipSegment = {\n type: 'chip',\n trigger: item.trigger,\n value: item.value,\n displayText: item.displayText,\n ...(item.data !== undefined ? { data: item.data } : {}),\n ...(item.autoResolved ? { autoResolved: true } : {}),\n }\n segments.push(chip)\n } else {\n return null\n }\n }\n\n return segments\n } catch {\n return null\n }\n}\n\n/**\n * Inserts pasted segments at the current cursor position within existing segments.\n * Splits any text segment that straddles the cursor so the pasted content lands\n * exactly at the cursor and nothing before or after is lost.\n */\nexport function insertSegmentsAtCursor(\n currentSegments: Segment[],\n pastedSegments: Segment[],\n editor: HTMLElement,\n): Segment[] {\n const range = getSelectionRange()\n if (!range) return [...currentSegments, ...pastedSegments]\n\n const preRange = document.createRange()\n preRange.selectNodeContents(editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n const cursorOffset = getTextLengthInRange(preRange)\n\n const result: Segment[] = []\n let offset = 0\n let inserted = false\n\n const insertOnce = (): void => {\n if (!inserted) {\n result.push(...pastedSegments)\n inserted = true\n }\n }\n\n for (const seg of currentSegments) {\n if (seg.type === 'chip') {\n const chipLen = seg.trigger.length + seg.displayText.length\n if (offset >= cursorOffset) insertOnce()\n result.push(seg)\n offset += chipLen\n continue\n }\n\n const segEnd = offset + seg.text.length\n if (segEnd <= cursorOffset) {\n // Entirely before the cursor\n result.push(seg)\n } else if (offset >= cursorOffset) {\n // Entirely after the cursor\n insertOnce()\n result.push(seg)\n } else {\n // Cursor falls inside this text segment — split it.\n const splitAt = cursorOffset - offset\n const before = seg.text.slice(0, splitAt)\n const after = seg.text.slice(splitAt)\n if (before) result.push({ type: 'text', text: before })\n insertOnce()\n if (after) result.push({ type: 'text', text: after })\n }\n offset = segEnd\n }\n\n insertOnce()\n return mergeAdjacentTextSegments(result)\n}\n","/**\n * Hand-rolled, dependency-free HTML -> Markdown converter.\n *\n * Used by the paste handler: when the editor is in markdown mode and the\n * clipboard carries rich `text/html` (web pages, Notion, Google Docs, GitHub,\n * Slack, etc.), we convert it to markdown SOURCE text so the paste keeps its\n * formatting. The resulting string flows through the same insertion path as a\n * plain-text paste, and the editor's inline decorators render `*`/`**`/`***`\n * and bare URLs automatically.\n *\n * Design constraints (see .size-limit.json): no runtime deps. Parsing uses the\n * ambient `DOMParser`, walking is a small recursive switch. Type-safe: no\n * `any`, DOM narrowed via the guards in `dom-helpers.ts`.\n */\nimport { isHTMLElement, isTextNode } from './dom-helpers.ts'\n\n// ---------------------------------------------------------------------------\n// Inline style / emphasis detection\n// ---------------------------------------------------------------------------\n\n/** Reads a single declaration value from an inline `style` attribute string. */\nfunction getStyleValue(style: string, prop: string): string {\n const match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style)\n return match ? match[1].trim().toLowerCase() : ''\n}\n\n/** Whether a CSS font-weight value is bold (`bold`, `bolder`, or >= 600). */\nfunction isBoldWeight(value: string): boolean {\n if (value === 'bold' || value === 'bolder') return true\n const numeric = Number.parseInt(value, 10)\n return !Number.isNaN(numeric) && numeric >= 600\n}\n\n/**\n * Computes the markdown emphasis markers for an element from BOTH its tag and\n * its inline style. Google Docs emits `<span style=\"font-weight:700\">` rather\n * than `<b>`, and wraps everything in `<b style=\"font-weight:normal\">`, so an\n * explicit `font-weight`/`font-style` always wins over the tag name.\n */\nfunction inlineEmphasis(node: HTMLElement): { prefix: string; suffix: string } {\n const tag = node.tagName\n const style = node.getAttribute('style') ?? ''\n\n const weight = getStyleValue(style, 'font-weight')\n const bold = weight ? isBoldWeight(weight) : tag === 'B' || tag === 'STRONG'\n\n const fontStyle = getStyleValue(style, 'font-style')\n const italic = fontStyle\n ? fontStyle === 'italic' || fontStyle === 'oblique'\n : tag === 'I' || tag === 'EM'\n\n const decoration = getStyleValue(style, 'text-decoration')\n const strike =\n tag === 'S' || tag === 'DEL' || tag === 'STRIKE' || decoration.includes('line-through')\n\n const prefix = (strike ? '~~' : '') + (bold ? '**' : '') + (italic ? '*' : '')\n const suffix = (italic ? '*' : '') + (bold ? '**' : '') + (strike ? '~~' : '')\n return { prefix, suffix }\n}\n\n// ---------------------------------------------------------------------------\n// Text handling\n// ---------------------------------------------------------------------------\n\n/** Collapses HTML whitespace runs (incl. `&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 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 const displayText = activeTrigger.config.onSelect?.(suggestion) ?? suggestion.label\n\n const chipData = {\n value: suggestion.value,\n displayText: displayText || suggestion.label,\n data: suggestion.data,\n }\n\n // Chip-click dropdown (`reopenOnChipClick`): replace the clicked chip in\n // place instead of resolving typed trigger text at the caret. Disabled\n // is re-checked here (not just at open time) in case the composer\n // became disabled while the popover was still open.\n const editing = editingChip.current\n if (editing) {\n const editor = editorRef.current\n if (editor && !disabled) {\n // Re-verify the click-time index still holds the same chip — the\n // model may have shifted (external value update, undo/redo) while\n // the dropdown was open. If it moved, recover ONLY when exactly one\n // chip in the document now matches trigger+value: with duplicates,\n // guessing risks silently editing the wrong instance, which is\n // worse than the no-op this falls back to.\n const atIndex = segments[editing.segIndex]\n const stillThere =\n atIndex?.type === 'chip' &&\n atIndex.trigger === editing.chip.trigger &&\n atIndex.value === editing.chip.value\n const segIdx = stillThere\n ? editing.segIndex\n : (() => {\n const matches: number[] = []\n segments.forEach((seg, i) => {\n if (\n seg.type === 'chip' &&\n seg.trigger === editing.chip.trigger &&\n seg.value === editing.chip.value\n ) {\n matches.push(i)\n }\n })\n return matches.length === 1 ? matches[0] : -1\n })()\n const oldChip = segIdx !== -1 ? segments[segIdx] : undefined\n\n if (oldChip?.type === 'chip') {\n const newChip: ChipSegment = {\n type: 'chip',\n trigger: activeTrigger.config.char,\n ...chipData,\n }\n let newSegments = segments.map((seg, i) => (i === segIdx ? newChip : seg))\n\n // Guarantee a real landing spot after the replaced chip, mirroring\n // resolveChip's trailing-space convention (prompt-area-engine.ts):\n // if the new chip is now the last segment (or directly followed by\n // another chip), the caret would land at a bare element boundary\n // with no text node, which some engines fail to render/snap a\n // visible caret at.\n const nextSeg = newSegments[segIdx + 1]\n const insertedSpace = !nextSeg || nextSeg.type !== 'text' || nextSeg.text.length === 0\n if (insertedSpace) {\n newSegments = [\n ...newSegments.slice(0, segIdx + 1),\n { type: 'text', text: ' ' },\n ...newSegments.slice(segIdx + 1),\n ]\n }\n\n events.pushUndo(segments)\n onChange(newSegments)\n renderSegmentsToDOM(newSegments)\n\n // Same value + display text + data: treat as a no-op confirmation\n // rather than a destructive delete+add — onChipDelete is\n // documented as firing on backspace/forward-delete, not on\n // re-confirming the already-selected suggestion.\n const unchanged =\n oldChip.value === newChip.value &&\n oldChip.displayText === newChip.displayText &&\n safeJsonStringify(oldChip.data) === safeJsonStringify(newChip.data)\n if (!unchanged) {\n onChipDelete?.(oldChip)\n onChipAdd?.(newChip)\n }\n\n // +1 when a space was inserted, matching resolveChip's own\n // \"+1 accounts for the trailing space after the chip\" placement —\n // landing exactly at the chip's end would put the caret at the\n // same bare element boundary the inserted space exists to avoid.\n const caretOffset =\n segmentsToPlainText(newSegments.slice(0, segIdx + 1)).length + (insertedSpace ? 1 : 0)\n setCursorAtOffset(editor, caretOffset)\n }\n }\n\n dismissTrigger()\n setTimeout(() => {\n editorRef.current?.focus()\n }, 0)\n return\n }\n\n const result = resolveChip(segments, activeTrigger, chipData)\n\n onChange(result.segments)\n renderSegmentsToDOM(result.segments)\n\n onChipAdd?.({\n type: 'chip',\n trigger: activeTrigger.config.char,\n ...chipData,\n })\n\n // Position cursor after the chip + trailing space\n const editor = editorRef.current\n if (editor) {\n setCursorAtOffset(editor, result.cursorOffset)\n }\n\n dismissTrigger()\n\n // Refocus editor after popover interaction\n setTimeout(() => {\n editorRef.current?.focus()\n }, 0)\n },\n [\n activeTrigger,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n dismissTrigger,\n onChipAdd,\n onChipDelete,\n events,\n disabled,\n ],\n )\n\n const selectSuggestion = selectSuggestionInternal\n\n // Chip-click dropdown: once the empty-query suggestions arrive, preselect\n // the chip's current value so the list opens \"on\" the existing choice.\n useEffect(() => {\n const editing = editingChip.current\n if (!editing || !activeTrigger?.config.reopenOnChipClick) return\n const idx = suggestions.findIndex((s) => s.value === editing.chip.value)\n if (idx > 0) setSelectedSuggestionIndex(idx)\n }, [suggestions, activeTrigger])\n\n // -----------------------------------------------------------------------\n // Handle key events\n // -----------------------------------------------------------------------\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n const applyEditResult = (\n editor: HTMLDivElement,\n result: { segments: Segment[]; cursorOffset: number },\n ) => {\n // Ordered-list numbers are a projection of position: rebuild them on\n // every structural edit and remap the caret across any digit-run width\n // changes. No-op (same reference) when there are no ordered lists.\n let { segments, cursorOffset } = result\n if (markdownEnabled) {\n const renumbered = renumberOrderedListSegments(segments)\n segments = renumbered.segments\n cursorOffset = remapOffset(cursorOffset, renumbered.edits)\n }\n lastRenderedValue.current = segments\n onChange(segments)\n renderSegmentsToDOM(segments)\n setCursorAtOffset(editor, cursorOffset)\n }\n\n const tryListContinuation = (editor: HTMLDivElement): boolean => {\n if (!markdownEnabled) return false\n const segments = readSegmentsFromDOM()\n const cursorPos = getCursorOffset(editor)\n if (cursorPos === null) return false\n const plainText = segmentsToPlainText(segments)\n if (!getListContext(plainText, cursorPos)) return false\n const result = insertListContinuation(segments, cursorPos)\n if (result) applyEditResult(editor, result)\n return true\n }\n\n // 1. Flush pending undo debounce so Cmd+Z has the latest checkpoint\n if ((e.metaKey || e.ctrlKey) && e.key === 'z' && undoBaseState.current) {\n if (undoTimer.current) {\n clearTimeout(undoTimer.current)\n undoTimer.current = null\n }\n events.pushUndo(undoBaseState.current)\n undoBaseState.current = null\n }\n\n // 1a. Undo/redo intercept\n if (events.handleKeyDownForUndoRedo(e)) return\n\n // 1.5 Markdown formatting shortcuts (Cmd+B bold, Cmd+I italic)\n if (\n markdownEnabled &&\n (e.metaKey || e.ctrlKey) &&\n !e.shiftKey &&\n (e.key === 'b' || e.key === 'i')\n ) {\n e.preventDefault()\n const editor = editorRef.current\n if (!editor) return\n\n const offsets = getSelectionOffsets(editor)\n if (!offsets || offsets.start === offsets.end) return\n\n const marker = e.key === 'b' ? '**' : '*'\n const currentSegments = readSegmentsFromDOM()\n events.pushUndo(currentSegments)\n\n const result = toggleMarkdownWrap(currentSegments, offsets.start, offsets.end, marker)\n if (!result) return\n\n lastRenderedValue.current = result.segments\n onChange(result.segments)\n renderSegmentsToDOM(result.segments)\n setSelectionAtOffsets(editor, result.selectionStart, result.selectionEnd)\n return\n }\n\n // 1.75 Launch triggers: a trigger with mode 'launch' fires onActivate on\n // keydown and suppresses the char so it never enters the editor — for\n // opening an external surface (dialog, palette). The DOM read is gated on\n // the typed key actually matching a launch char, so it stays off the hot\n // path. insertChip still inserts a chip at the cursor if the consumer\n // wants one after the external selection.\n if (\n !e.metaKey &&\n !e.ctrlKey &&\n !e.altKey &&\n !e.nativeEvent.isComposing &&\n e.key.length === 1\n ) {\n const launcher = triggers.find((t) => t.mode === 'launch' && t.char === e.key)\n const editor = editorRef.current\n if (launcher?.onActivate && editor) {\n const cursorPos = getCursorOffset(editor)\n if (cursorPos !== null) {\n const segments = readSegmentsFromDOM()\n const plainText = segmentsToPlainText(segments)\n if (isValidTriggerPosition(plainText, cursorPos, launcher.position)) {\n e.preventDefault()\n launcher.onActivate({\n text: plainText,\n cursorPosition: cursorPos,\n insertChip: buildInsertChip(\n replaceTextRange(segments, cursorPos, cursorPos, launcher.char),\n { config: launcher, startOffset: cursorPos, query: '' },\n ),\n })\n return\n }\n }\n }\n }\n\n // 2. Trigger dropdown navigation. Gated on the dropdown actually being\n // ON SCREEN, which matches TriggerPopover's own render condition\n // (non-empty suggestions, OR loading/error/emptyMessage) rather than\n // just `suggestions.length > 0` — otherwise a popover left open in a\n // loading/empty state (e.g. right after a chip-click reopen, before its\n // empty-query search resolves) lets Enter fall through to onSubmit and\n // Escape fall through to onEscape while still visibly on screen.\n const dropdownVisible =\n activeTrigger &&\n activeTrigger.config.mode === 'dropdown' &&\n (suggestions.length > 0 ||\n suggestionsLoading ||\n suggestionsError !== null ||\n !!activeTrigger.config.emptyMessage)\n if (dropdownVisible) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n if (suggestions.length > 0) {\n setSelectedSuggestionIndex((prev) => Math.min(prev + 1, suggestions.length - 1))\n }\n return\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n if (suggestions.length > 0) {\n setSelectedSuggestionIndex((prev) => Math.max(prev - 1, 0))\n }\n return\n }\n if (e.key === 'Enter' || e.key === 'Tab') {\n e.preventDefault()\n const selected = suggestions[selectedSuggestionIndex]\n if (selected) {\n selectSuggestionInternal(selected)\n }\n return\n }\n if (e.key === 'Escape') {\n e.preventDefault()\n dismissTrigger()\n return\n }\n }\n\n // 2.5. Auto-resolve on Space when trigger has resolveOnSpace\n if (e.key === ' ' && activeTrigger && activeTrigger.config.resolveOnSpace) {\n const query = activeTrigger.query.trim()\n if (query.length > 0) {\n e.preventDefault()\n autoResolveActiveTrigger(activeTrigger)\n return\n }\n }\n\n // 2.6. Tab/Shift+Tab for list indentation (only when trigger dropdown is NOT open)\n if (markdownEnabled && e.key === 'Tab' && !activeTrigger) {\n const editor = editorRef.current\n if (editor) {\n const segments = readSegmentsFromDOM()\n const plainText = segmentsToPlainText(segments)\n const cursorPos = getCursorOffset(editor)\n if (cursorPos !== null) {\n const ctx = getListContext(plainText, cursorPos)\n if (ctx) {\n e.preventDefault()\n const result = e.shiftKey\n ? outdentListItem(segments, cursorPos)\n : indentListItem(segments, cursorPos)\n if (result) applyEditResult(editor, result)\n return\n }\n }\n }\n }\n\n // Insert a newline at the model level (avoids the browser's broken\n // contentEditable behaviour near <a> elements).\n const insertPlainNewline = (editor: HTMLDivElement): void => {\n const offsets = getSelectionOffsets(editor)\n if (!offsets) return\n const currentSegments = readSegmentsFromDOM()\n events.pushUndo(currentSegments)\n const newSegments = replaceTextRange(currentSegments, offsets.start, offsets.end, '\\n')\n applyEditResult(editor, { segments: newSegments, cursorOffset: offsets.start + 1 })\n }\n\n // 2.8 Shift+Enter always inserts a newline (after a list-continuation check).\n if (e.key === 'Enter' && e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault()\n const editor = editorRef.current\n if (editor && !tryListContinuation(editor)) insertPlainNewline(editor)\n return\n }\n\n // 3. Enter without Shift (skipping IME): continue a list, else submit when\n // `submitOnEnter` is set, else insert a newline.\n if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {\n const editor = editorRef.current\n if (editor && tryListContinuation(editor)) {\n e.preventDefault()\n return\n }\n if (submitOnEnter) {\n e.preventDefault()\n onSubmit?.(readSegmentsFromDOM())\n return\n }\n e.preventDefault()\n if (editor) insertPlainNewline(editor)\n return\n }\n\n // 4. Escape\n if (e.key === 'Escape' && onEscape) {\n onEscape()\n return\n }\n\n // 4.5 Non-collapsed selection delete (Backspace/Delete across <a> boundaries)\n if ((e.key === 'Backspace' || e.key === 'Delete') && !e.nativeEvent.isComposing) {\n const editor = editorRef.current\n if (editor) {\n const offsets = getSelectionOffsets(editor)\n if (offsets && offsets.start !== offsets.end) {\n e.preventDefault()\n const currentSegments = readSegmentsFromDOM()\n events.pushUndo(currentSegments)\n const newSegments = replaceTextRange(currentSegments, offsets.start, offsets.end, '')\n applyEditResult(editor, { segments: newSegments, cursorOffset: offsets.start })\n runTriggerDetection()\n return\n }\n }\n }\n\n // 5. Backspace: check list prefix removal, then chip deletion\n if (e.key === 'Backspace') {\n const editor = editorRef.current\n if (editor) {\n const segments = readSegmentsFromDOM()\n const cursorPos = getCursorOffset(editor)\n if (markdownEnabled && cursorPos !== null) {\n const result = removeListPrefix(segments, cursorPos)\n if (result) {\n e.preventDefault()\n applyEditResult(editor, result)\n runTriggerDetection()\n return\n }\n }\n }\n if (handleChipBackspace()) {\n e.preventDefault()\n runTriggerDetection()\n return\n }\n }\n\n // 6. Delete (forward): delete chip as whole unit\n if (e.key === 'Delete' && handleChipForwardDelete()) {\n e.preventDefault()\n runTriggerDetection()\n return\n }\n },\n [\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n onSubmit,\n submitOnEnter,\n onEscape,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n markdownEnabled,\n dismissTrigger,\n handleChipBackspace,\n handleChipForwardDelete,\n autoResolveActiveTrigger,\n runTriggerDetection,\n selectSuggestionInternal,\n events,\n triggers,\n buildInsertChip,\n ],\n )\n\n // -----------------------------------------------------------------------\n // Imperative handle (memoized to avoid identity changes)\n // -----------------------------------------------------------------------\n\n const handle: PromptAreaHandle = useMemo(\n () => ({\n focus: () => editorRef.current?.focus(),\n blur: () => editorRef.current?.blur(),\n insertChip: (chip) => {\n const segments = readSegmentsFromDOM()\n const newChip: ChipSegment = { type: 'chip', ...chip }\n const newSegments: Segment[] = [...segments, newChip, { type: 'text', text: ' ' }]\n onChange(newSegments)\n renderSegmentsToDOM(newSegments)\n onChipAdd?.(newChip)\n },\n getPlainText: () => segmentsToPlainText(readSegmentsFromDOM()),\n clear: () => {\n onChange([])\n const editor = editorRef.current\n if (editor) {\n while (editor.firstChild) editor.removeChild(editor.firstChild)\n }\n events.resetUndoHistory()\n if (undoTimer.current) {\n clearTimeout(undoTimer.current)\n undoTimer.current = null\n }\n undoBaseState.current = null\n },\n setText: (text) => {\n events.pushUndo(readSegmentsFromDOM())\n const segments = plainTextToSegments(text)\n onChange(segments)\n renderSegmentsToDOM(segments)\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, text.length)\n },\n appendText: (text) => {\n const segments = readSegmentsFromDOM()\n events.pushUndo(segments)\n // Merge into the trailing text segment so the onChange value doesn't\n // carry two adjacent un-merged text segments.\n const last = segments[segments.length - 1]\n const next: Segment[] =\n last?.type === 'text'\n ? [...segments.slice(0, -1), { type: 'text', text: last.text + text }]\n : [...segments, { type: 'text', text }]\n onChange(next)\n renderSegmentsToDOM(next)\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, segmentsToPlainText(next).length)\n },\n getCursorPosition: () => {\n const editor = editorRef.current\n return editor ? getCursorOffset(editor) : null\n },\n setCursorPosition: (offset) => {\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, offset)\n },\n setCursorToEnd: () => {\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, segmentsToPlainText(readSegmentsFromDOM()).length)\n },\n getSelection: () => {\n const editor = editorRef.current\n return editor ? getSelectionOffsets(editor) : null\n },\n setSelection: (start, end) => {\n const editor = editorRef.current\n if (editor) setSelectionAtOffsets(editor, start, end)\n },\n }),\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, onChipAdd, events],\n )\n\n // -----------------------------------------------------------------------\n // Compose event handlers\n // -----------------------------------------------------------------------\n\n const eventHandlers = useMemo(\n () => ({\n onPaste: events.handlePaste,\n onCopy: events.handleCopy,\n onCut: events.handleCut,\n onDrop: events.handleDrop,\n onDragOver: events.handleDragOver,\n onCompositionStart: events.handleCompositionStart,\n onCompositionEnd: events.handleCompositionEnd,\n onBlur: events.handleBlur,\n }),\n [\n events.handlePaste,\n events.handleCopy,\n events.handleCut,\n events.handleDrop,\n events.handleDragOver,\n events.handleCompositionStart,\n events.handleCompositionEnd,\n events.handleBlur,\n ],\n )\n\n return {\n editorRef,\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n handleInput,\n handleKeyDown,\n handleClick,\n handleMouseDown,\n selectSuggestion,\n dismissTrigger,\n handle,\n triggerRect,\n eventHandlers,\n }\n}\n","'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport type { TriggerSuggestion } from './types.ts'\n\ntype TriggerPopoverProps = {\n suggestions: TriggerSuggestion[]\n loading: boolean\n error?: string | null\n emptyMessage?: string\n selectedIndex: number\n onSelect: (suggestion: TriggerSuggestion) => void\n onDismiss: () => void\n triggerRect: DOMRect | null\n triggerChar: string\n}\n\n// Single source of truth for the popover height: drives both the flip-above\n// calculation and the maxHeight style.\nconst POPOVER_MAX_HEIGHT = 240\n\n/**\n * Floating popover that displays trigger suggestions.\n * Positioned relative to the trigger character location in the editor.\n */\nexport function TriggerPopover({\n suggestions,\n loading,\n error,\n emptyMessage,\n selectedIndex,\n onSelect,\n onDismiss,\n triggerRect,\n triggerChar,\n}: TriggerPopoverProps) {\n const popoverRef = useRef<HTMLDivElement>(null)\n const selectedRef = useRef<HTMLButtonElement>(null)\n\n // Scroll selected item into view\n useEffect(() => {\n selectedRef.current?.scrollIntoView({ block: 'nearest' })\n }, [selectedIndex])\n\n // Click outside to dismiss\n useEffect(() => {\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target\n if (popoverRef.current && target instanceof Node && !popoverRef.current.contains(target)) {\n onDismiss()\n }\n }\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [onDismiss])\n\n if (!triggerRect) return null\n if (suggestions.length === 0 && !loading && !error && !emptyMessage) return null\n\n // Position the popover relative to the trigger character, clamped to the\n // viewport. Flip above the trigger when there isn't enough room below, so the\n // suggestion list stays on-screen near the bottom edge.\n const popoverMaxWidth = Math.min(320, window.innerWidth - 16)\n const left = Math.min(triggerRect.left, window.innerWidth - popoverMaxWidth - 8)\n const spaceBelow = window.innerHeight - triggerRect.bottom\n const positionAbove = spaceBelow < POPOVER_MAX_HEIGHT && triggerRect.top > spaceBelow\n const style: React.CSSProperties = {\n position: 'fixed',\n left: `${Math.max(8, left)}px`,\n zIndex: 50,\n maxWidth: `${popoverMaxWidth}px`,\n maxHeight: `${POPOVER_MAX_HEIGHT}px`,\n ...(positionAbove\n ? { bottom: `${window.innerHeight - triggerRect.top + 4}px` }\n : { top: `${triggerRect.bottom + 4}px` }),\n }\n\n return (\n <div\n ref={popoverRef}\n className={cn(\n 'min-w-[200px] overflow-y-auto',\n 'bg-surface rounded-xl border p-2 shadow-md',\n 'animate-in fade-in-0 zoom-in-95',\n )}\n style={style}\n role=\"listbox\"\n aria-label={`${triggerChar} suggestions`}>\n {loading ? (\n <div\n role=\"option\"\n aria-selected={false}\n className=\"text-muted-foreground px-3 py-2 text-sm\">\n Loading suggestions...\n </div>\n ) : error ? (\n <div role=\"option\" aria-selected={false} className=\"text-destructive px-3 py-2 text-sm\">\n {error}\n </div>\n ) : suggestions.length === 0 && emptyMessage ? (\n <div\n role=\"option\"\n aria-selected={false}\n className=\"text-muted-foreground px-3 py-2 text-sm\">\n {emptyMessage}\n </div>\n ) : (\n suggestions.map((suggestion, index) => (\n <button\n key={suggestion.value}\n ref={index === selectedIndex ? selectedRef : undefined}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={cn(\n 'text-foreground flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm',\n 'hover:bg-surface-hover cursor-pointer transition-colors',\n index === selectedIndex && 'bg-surface-hover',\n )}\n onMouseDown={(e) => {\n e.preventDefault() // Prevent blur on the editor\n onSelect(suggestion)\n }}>\n {suggestion.icon && <span className=\"mt-0.5 shrink-0\">{suggestion.icon}</span>}\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate font-medium\">{suggestion.label}</div>\n {suggestion.description && (\n <div className=\"text-muted-foreground truncate text-xs\">\n {suggestion.description}\n </div>\n )}\n </div>\n </button>\n ))\n )}\n </div>\n )\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\ntype AnimatedPlaceholderProps = {\n texts: string[]\n interval?: number\n}\n\n/**\n * Cross-fading placeholder that rotates through `texts`.\n *\n * Each text is keyed so React remounts it on change, replaying the\n * `tw-animate-css` enter animation (slide down + fade in). No animation\n * library is required.\n */\nexport function AnimatedPlaceholder({ texts, interval = 3000 }: AnimatedPlaceholderProps) {\n const [index, setIndex] = useState(0)\n\n useEffect(() => {\n if (texts.length <= 1) return\n\n const id = setInterval(() => {\n setIndex((prev) => (prev + 1) % texts.length)\n }, interval)\n\n return () => clearInterval(id)\n }, [texts.length, interval])\n\n return (\n <div\n className=\"pointer-events-none absolute top-0 left-0 overflow-hidden select-none\"\n style={{ color: 'var(--prompt-area-placeholder, var(--muted-foreground))' }}\n aria-hidden=\"true\">\n <div\n key={index}\n className=\"animate-in fade-in-0 slide-in-from-top-4 duration-300 ease-in-out\">\n {texts[index]}\n </div>\n </div>\n )\n}\n","import { cn } from '../../lib/utils.ts'\n\ntype RemoveButtonProps = {\n onClick: () => void\n label: string\n className?: string\n}\n\nexport function RemoveButton({ onClick, label, className }: RemoveButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onClick()\n }}\n className={cn(\n 'absolute top-0.5 right-0.5 grid h-3.5 w-3.5 cursor-pointer place-items-center',\n 'rounded-full bg-black/60 text-white hover:bg-black/80 dark:bg-white/60 dark:text-black dark:hover:bg-white/80',\n 'transition-colors',\n className,\n )}\n aria-label={label}>\n <svg\n width=\"8\"\n height=\"8\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\">\n <line x1=\"2.75\" y1=\"2.75\" x2=\"7.25\" y2=\"7.25\" />\n <line x1=\"7.25\" y1=\"2.75\" x2=\"2.75\" y2=\"7.25\" />\n </svg>\n </button>\n )\n}\n","'use client'\n\nimport { cn } from '../../lib/utils.ts'\nimport { RemoveButton } from './remove-button.tsx'\nimport type { PromptAreaImage } from './types.ts'\n\ntype ImageStripProps = {\n images: PromptAreaImage[]\n onRemove?: (image: PromptAreaImage) => void\n onClick?: (image: PromptAreaImage) => void\n className?: string\n}\n\nexport function ImageStrip({ images, onRemove, onClick, className }: ImageStripProps) {\n if (images.length === 0) return null\n\n return (\n <div className={cn('flex flex-wrap gap-2', className)} role=\"list\" aria-label=\"Attached images\">\n {images.map((image) => (\n <div\n key={image.id}\n role=\"listitem\"\n className={cn(\n 'border-border relative h-16 w-16 flex-shrink-0 overflow-hidden rounded-md border',\n onClick && 'cursor-pointer',\n )}\n onClick={() => onClick?.(image)}>\n {/* eslint-disable-next-line @next/next/no-img-element -- registry component used outside Next.js */}\n <img\n src={image.url}\n alt={image.alt ?? 'Attached image'}\n className=\"h-full w-full object-cover\"\n />\n {image.loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/40\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent\" />\n </div>\n )}\n {onRemove && (\n <RemoveButton\n onClick={() => onRemove(image)}\n label={`Remove ${image.alt ?? 'image'}`}\n />\n )}\n </div>\n ))}\n </div>\n )\n}\n","'use client'\n\nimport { useEffect, useRef, useState } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport { RemoveButton } from './remove-button.tsx'\nimport type { PromptAreaFile } from './types.ts'\n\ntype IconProps = { className?: string }\n\n/** Shared SVG wrapper matching the lucide icon defaults (no dependency). */\nfunction Svg({ className, children }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={className}>\n {children}\n </svg>\n )\n}\n\nconst FileBody = (\n <>\n <path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\" />\n <path d=\"M14 2v5a1 1 0 0 0 1 1h5\" />\n </>\n)\n\nconst File = ({ className }: IconProps) => <Svg className={className}>{FileBody}</Svg>\nconst FileText = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M10 9H8\" />\n <path d=\"M16 13H8\" />\n <path d=\"M16 17H8\" />\n </Svg>\n)\nconst FileSpreadsheet = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M8 13h2\" />\n <path d=\"M14 13h2\" />\n <path d=\"M8 17h2\" />\n <path d=\"M14 17h2\" />\n </Svg>\n)\nconst FileCode = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M10 12.5 8 15l2 2.5\" />\n <path d=\"m14 12.5 2 2.5-2 2.5\" />\n </Svg>\n)\nconst ImageIcon = ({ className }: IconProps) => (\n <Svg className={className}>\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n <circle cx=\"9\" cy=\"9\" r=\"2\" />\n <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />\n </Svg>\n)\n\ntype FileStripProps = {\n files: PromptAreaFile[]\n onRemove?: (file: PromptAreaFile) => void\n onClick?: (file: PromptAreaFile) => void\n className?: string\n}\n\n/** Threshold above which collapse activates automatically. */\nconst COLLAPSE_THRESHOLD = 3\n\n/** Pick a lucide icon key based on MIME type. */\nfunction getFileIconKey(type?: string): 'pdf' | 'spreadsheet' | 'code' | 'image' | 'default' {\n if (!type) return 'default'\n if (type === 'application/pdf') return 'pdf'\n if (type.includes('spreadsheet') || type === 'text/csv') return 'spreadsheet'\n if (\n type.startsWith('text/') ||\n type.includes('javascript') ||\n type.includes('json') ||\n type.includes('xml')\n )\n return 'code'\n if (type.startsWith('image/')) return 'image'\n return 'default'\n}\n\nconst FILE_ICONS = {\n pdf: FileText,\n spreadsheet: FileSpreadsheet,\n code: FileCode,\n image: ImageIcon,\n default: File,\n} as const\n\n/** Format bytes into a human-readable string. */\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n}\n\n/** Extract a short extension label from a filename (e.g., \"PDF\", \"CSV\"). */\nfunction getExtensionLabel(name: string): string | null {\n const dot = name.lastIndexOf('.')\n if (dot === -1 || dot === name.length - 1) return null\n return name.slice(dot + 1).toUpperCase()\n}\n\nfunction FileCard({\n file,\n compact,\n onRemove,\n onClick,\n}: {\n file: PromptAreaFile\n compact: boolean\n onRemove?: (file: PromptAreaFile) => void\n onClick?: (file: PromptAreaFile) => void\n}) {\n const ext = getExtensionLabel(file.name)\n const sizeStr = file.size != null ? formatFileSize(file.size) : null\n const meta = [ext, sizeStr].filter(Boolean).join(' · ')\n\n return (\n <div\n role=\"listitem\"\n className={cn(\n 'border-border relative flex flex-shrink-0 items-center gap-2 overflow-hidden rounded-lg border transition-colors',\n 'hover:bg-surface-hover',\n compact ? 'h-10 w-36 px-2' : 'h-14 w-48 px-3',\n onClick && 'cursor-pointer',\n )}\n onClick={() => onClick?.(file)}>\n {(() => {\n const Icon = FILE_ICONS[getFileIconKey(file.type)]\n return (\n <Icon\n className={cn('text-muted-foreground flex-shrink-0', compact ? 'h-4 w-4' : 'h-5 w-5')}\n />\n )\n })()}\n <div className=\"min-w-0 flex-1\">\n <div\n className={cn('truncate font-medium', compact ? 'text-xs' : 'text-sm')}\n title={file.name}>\n {file.name}\n </div>\n {!compact && meta && <div className=\"text-muted-foreground truncate text-xs\">{meta}</div>}\n </div>\n\n {file.loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/40\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent\" />\n </div>\n )}\n\n {onRemove && <RemoveButton onClick={() => onRemove(file)} label={`Remove ${file.name}`} />}\n </div>\n )\n}\n\nexport function FileStrip({ files, onRemove, onClick, className }: FileStripProps) {\n const [expanded, setExpanded] = useState(false)\n const popoverRef = useRef<HTMLDivElement>(null)\n const toggleRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (!expanded) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target\n if (!(target instanceof Node)) return\n if (\n popoverRef.current &&\n !popoverRef.current.contains(target) &&\n !toggleRef.current?.contains(target)\n ) {\n setExpanded(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [expanded])\n\n if (files.length === 0) return null\n\n const collapsible = files.length > COLLAPSE_THRESHOLD\n const compact = collapsible\n const hiddenCount = files.length - COLLAPSE_THRESHOLD\n const visibleFiles = files.slice(0, COLLAPSE_THRESHOLD)\n\n return (\n <div className={cn('relative', className)}>\n <div className=\"flex flex-wrap gap-2\" role=\"list\" aria-label=\"Attached files\">\n {(collapsible ? visibleFiles : files).map((file) => (\n <FileCard\n key={file.id}\n file={file}\n compact={compact}\n onRemove={onRemove}\n onClick={onClick}\n />\n ))}\n {collapsible && (\n <div role=\"listitem\">\n <button\n ref={toggleRef}\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className={cn(\n 'border-border text-muted-foreground hover:bg-surface-hover flex flex-shrink-0 cursor-pointer items-center justify-center rounded-lg border transition-colors',\n compact ? 'h-10 px-3 text-xs' : 'h-14 px-4 text-sm',\n )}>\n {expanded ? 'Show less' : `+${hiddenCount} more`}\n </button>\n </div>\n )}\n </div>\n\n {expanded && (\n <div\n ref={popoverRef}\n className={cn(\n 'bg-surface border-border absolute bottom-full left-0 z-10 mb-2 max-h-48 overflow-y-auto rounded-lg border p-2 shadow-lg',\n )}>\n <div className=\"flex flex-wrap gap-2\" role=\"list\" aria-label=\"More attached files\">\n {files.slice(COLLAPSE_THRESHOLD).map((file) => (\n <FileCard\n key={file.id}\n file={file}\n compact={compact}\n onRemove={onRemove}\n onClick={onClick}\n />\n ))}\n </div>\n </div>\n )}\n </div>\n )\n}\n","'use client'\n\nimport { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport type { PromptAreaProps, PromptAreaHandle } from './types.ts'\nimport { usePromptArea } from './use-prompt-area.ts'\nimport { BLUR_DELAY_MS } from './use-prompt-area-events.ts'\nimport { TriggerPopover } from './trigger-popover.tsx'\nimport { AnimatedPlaceholder } from './animated-placeholder.tsx'\nimport { ImageStrip } from './image-strip.tsx'\nimport { FileStrip } from './file-strip.tsx'\n\n/**\n * PromptArea - A lightweight rich text input with trigger support.\n *\n * Uses contentEditable to support inline chips (immutable pills) for\n * mentions, commands, and other triggered tokens. Each trigger character\n * can be configured to show a dropdown or fire a callback.\n *\n * @example\n * ```tsx\n * const [segments, setSegments] = useState<Segment[]>([])\n *\n * <PromptArea\n * value={segments}\n * onChange={setSegments}\n * triggers={[\n * { char: '@', position: 'any', mode: 'dropdown', onSearch: searchUsers },\n * { char: '/', position: 'start', mode: 'dropdown', onSearch: searchCommands },\n * { char: '#', position: 'any', mode: 'dropdown', onSearch: searchTags },\n * ]}\n * placeholder=\"Type a message...\"\n * onSubmit={handleSubmit}\n * autoGrow\n * />\n * ```\n */\nexport function PromptArea({\n value,\n onChange,\n triggers,\n placeholder,\n className,\n disabled = false,\n markdown,\n normalizeBullets,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onLinkClick,\n onPaste,\n onUndo,\n onRedo,\n minHeight = 80,\n maxHeight,\n autoFocus = false,\n autoGrow = false,\n 'aria-label': ariaLabel,\n 'data-test-id': dataTestId,\n images = [],\n imagePosition = 'above',\n onImagePaste,\n onImageRemove,\n onImageClick,\n files = [],\n filePosition = 'above',\n onFileRemove,\n onFileClick,\n onKeyDown,\n onBlur,\n onRawPaste,\n submitOnEnter,\n spellCheck,\n maxLength,\n 'aria-describedby': ariaDescribedBy,\n ref,\n}: PromptAreaProps & { ref?: React.Ref<PromptAreaHandle> }) {\n const {\n editorRef,\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n handleInput,\n handleKeyDown,\n handleClick,\n handleMouseDown,\n selectSuggestion,\n dismissTrigger,\n handle,\n triggerRect,\n eventHandlers,\n } = usePromptArea({\n value,\n onChange,\n triggers,\n disabled,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onLinkClick,\n onPaste,\n onRawPaste,\n onUndo,\n onRedo,\n onImagePaste,\n markdown,\n normalizeBullets,\n submitOnEnter,\n maxLength,\n })\n\n // Expose imperative handle via ref\n useImperativeHandle(ref, () => handle, [handle])\n\n // Auto-focus on mount\n useEffect(() => {\n if (autoFocus) {\n editorRef.current?.focus()\n }\n }, [autoFocus, editorRef])\n\n // -----------------------------------------------------------------------\n // Auto-grow: expand on focus/input, shrink on blur\n // -----------------------------------------------------------------------\n\n const [isFocused, setIsFocused] = useState(false)\n const [editorHeight, setEditorHeight] = useState<number | undefined>(undefined)\n\n const syncHeight = useCallback(() => {\n const el = editorRef.current\n if (!el) return\n // Temporarily set height to auto so scrollHeight reflects true content height\n el.style.height = 'auto'\n const contentHeight = el.scrollHeight\n el.style.height = `${contentHeight}px`\n setEditorHeight(contentHeight)\n }, [editorRef])\n\n const handleFocus = useCallback(() => {\n if (!autoGrow) return\n setIsFocused(true)\n syncHeight()\n }, [autoGrow, syncHeight])\n\n const handleBlurWithShrink = useCallback(() => {\n eventHandlers.onBlur()\n if (!autoGrow) return\n setTimeout(() => {\n const editor = editorRef.current\n if (!editor) return\n // Only shrink if focus truly left the component\n const activeEl = document.activeElement\n if (activeEl && editor.parentElement?.contains(activeEl)) return\n setIsFocused(false)\n setEditorHeight(undefined)\n }, BLUR_DELAY_MS)\n }, [eventHandlers, autoGrow, editorRef])\n\n const handleInputWithGrow = useCallback(() => {\n handleInput()\n if (autoGrow && isFocused) {\n syncHeight()\n }\n }, [handleInput, autoGrow, isFocused, syncHeight])\n\n // Re-measure on value changes (chip insertion, undo/redo, programmatic updates)\n useEffect(() => {\n if (autoGrow && isFocused) {\n requestAnimationFrame(() => syncHeight())\n }\n }, [value, autoGrow, isFocused, syncHeight])\n\n // -----------------------------------------------------------------------\n // Overflow indicator: detect when collapsed content is clipped\n // -----------------------------------------------------------------------\n\n const [hasOverflow, setHasOverflow] = useState(false)\n const overflowTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n if (!autoGrow) return\n\n const checkOverflow = () => {\n if (isFocused) {\n setHasOverflow(false)\n return\n }\n const el = editorRef.current\n if (!el) return\n setHasOverflow(el.scrollHeight > el.clientHeight)\n }\n\n // Delay the check so the CSS height transition (150ms) finishes first;\n // on initial mount there is no transition so the check still runs quickly.\n const delay = isFocused ? 0 : 160\n overflowTimerRef.current = setTimeout(checkOverflow, delay)\n return () => {\n if (overflowTimerRef.current !== null) {\n clearTimeout(overflowTimerRef.current)\n }\n }\n }, [autoGrow, isFocused, value, editorRef])\n\n // -----------------------------------------------------------------------\n // Compute editor style\n // -----------------------------------------------------------------------\n\n const editorStyle = useMemo((): React.CSSProperties => {\n if (!autoGrow) {\n const style: React.CSSProperties = { minHeight: `${minHeight}px` }\n if (maxHeight) {\n style.maxHeight = `${maxHeight}px`\n style.overflowY = 'auto'\n }\n return style\n }\n return {\n height: isFocused && editorHeight ? `${editorHeight}px` : `${minHeight}px`,\n minHeight: `${minHeight}px`,\n // Respect an explicit maxHeight; otherwise fall back to a viewport-relative\n // cap so the editor never grows past the screen.\n maxHeight: maxHeight ? `${maxHeight}px` : '70dvh',\n overflowY: isFocused ? 'auto' : 'hidden',\n // `min-height` is eased so consumers that animate it (e.g. the compact\n // prompt area's collapse/expand) morph smoothly instead of snapping.\n transition: 'height 150ms ease-out, min-height 240ms cubic-bezier(0.33, 1, 0.68, 1)',\n }\n }, [autoGrow, minHeight, maxHeight, isFocused, editorHeight])\n\n // Run a consumer's onKeyDown first; if it calls preventDefault, skip all of\n // PromptArea's built-in key handling (submit, trigger nav, etc.).\n const handleKeyDownCombined = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(e)\n if (e.defaultPrevented) return\n handleKeyDown(e)\n },\n [onKeyDown, handleKeyDown],\n )\n\n // Forward blur to the consumer (with relatedTarget) alongside the internal\n // trigger-dismiss / auto-grow-shrink handling.\n const handleBlurCombined = useCallback(\n (e: React.FocusEvent<HTMLDivElement>) => {\n onBlur?.(e)\n // handleBlurWithShrink already calls eventHandlers.onBlur() and only\n // shrinks when autoGrow is on, so it covers both modes.\n handleBlurWithShrink()\n },\n [onBlur, handleBlurWithShrink],\n )\n\n const isEmpty =\n value.length === 0 || (value.length === 1 && value[0].type === 'text' && value[0].text === '')\n\n const imageStrip =\n images.length > 0 ? (\n <ImageStrip\n images={images}\n onRemove={onImageRemove}\n onClick={onImageClick}\n className={imagePosition === 'above' ? 'pb-2' : 'pt-2'}\n />\n ) : null\n\n const fileStrip =\n files.length > 0 ? (\n <FileStrip\n files={files}\n onRemove={onFileRemove}\n onClick={onFileClick}\n className={filePosition === 'above' ? 'pb-2' : 'pt-2'}\n />\n ) : null\n\n // Typography (font-size/line-height) lives on the container, not the editor, so\n // it cascades to the editor AND the placeholder overlays — and a consumer can\n // override all three at once via `className` (e.g. `text-base leading-6`).\n return (\n <div className={cn('prompt-area-container relative text-sm leading-relaxed', className)}>\n {imagePosition === 'above' && imageStrip}\n {filePosition === 'above' && fileStrip}\n\n {/* Editor + placeholder wrapper */}\n <div className=\"relative\">\n <div\n ref={editorRef}\n contentEditable={!disabled}\n suppressContentEditableWarning\n role=\"textbox\"\n aria-label={ariaLabel ?? 'Text input'}\n aria-multiline=\"true\"\n aria-disabled={disabled || undefined}\n aria-describedby={ariaDescribedBy}\n data-test-id={dataTestId}\n spellCheck={spellCheck}\n className={cn(\n 'prompt-area-editor',\n 'w-full min-w-0 break-words whitespace-pre-wrap outline-none',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n style={editorStyle}\n onFocus={handleFocus}\n onInput={autoGrow ? handleInputWithGrow : handleInput}\n onKeyDown={handleKeyDownCombined}\n onMouseDown={handleMouseDown}\n onClick={handleClick}\n onPaste={eventHandlers.onPaste}\n onCopy={eventHandlers.onCopy}\n onCut={eventHandlers.onCut}\n onDrop={eventHandlers.onDrop}\n onDragOver={eventHandlers.onDragOver}\n onCompositionStart={eventHandlers.onCompositionStart}\n onCompositionEnd={eventHandlers.onCompositionEnd}\n onBlur={handleBlurCombined}\n />\n\n {/* Overflow gradient indicator – visible when auto-grow is collapsed and content is clipped */}\n {autoGrow && hasOverflow && !isFocused && (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-auto absolute right-0 bottom-0 left-0 cursor-pointer\"\n style={{ height: '32px' }}\n onClick={() => editorRef.current?.focus()}>\n <div\n className=\"h-full w-full\"\n style={{\n background:\n 'linear-gradient(to bottom, transparent, color-mix(in srgb, var(--prompt-area-surface, var(--background)) 80%, transparent), var(--prompt-area-surface, var(--background)))',\n }}\n />\n </div>\n )}\n\n {/* Placeholder overlay */}\n {isEmpty &&\n placeholder &&\n (Array.isArray(placeholder) ? (\n <AnimatedPlaceholder texts={placeholder} />\n ) : (\n <div\n className=\"pointer-events-none absolute top-0 left-0 select-none\"\n style={{ color: 'var(--prompt-area-placeholder, var(--muted-foreground))' }}\n aria-hidden=\"true\">\n {placeholder}\n </div>\n ))}\n </div>\n\n {filePosition === 'below' && fileStrip}\n {imagePosition === 'below' && imageStrip}\n\n {/* Trigger suggestion popover */}\n {activeTrigger && activeTrigger.config.mode === 'dropdown' && (\n <TriggerPopover\n suggestions={suggestions}\n loading={suggestionsLoading}\n error={suggestionsError}\n emptyMessage={activeTrigger.config.emptyMessage}\n selectedIndex={selectedSuggestionIndex}\n onSelect={selectSuggestion}\n onDismiss={dismissTrigger}\n triggerRect={triggerRect}\n triggerChar={activeTrigger.config.char}\n />\n )}\n </div>\n )\n}\n","/**\n * Convenience hook that wires up all the boilerplate state for a PromptArea.\n *\n * Instead of manually managing `useState<Segment[]>`, `useRef<PromptAreaHandle>`,\n * and computing derived values, call `usePromptAreaState()` once and spread\n * `bind` into your `<PromptArea>`.\n *\n * @example\n * ```tsx\n * function ChatInput() {\n * const { bind, plainText, isEmpty, chips, clear, focus } = usePromptAreaState()\n *\n * return (\n * <PromptArea\n * {...bind}\n * onSubmit={() => {\n * sendMessage(plainText)\n * clear()\n * }}\n * />\n * )\n * }\n * ```\n */\n\n'use client'\n\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport type { Segment, ChipSegment, PromptAreaHandle } from './types.ts'\nimport { segmentsToPlainText } from './prompt-area-engine.ts'\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport type UsePromptAreaStateOptions = {\n /** Initial segment value. Defaults to `[]`. */\n initialValue?: Segment[]\n}\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport type PromptAreaBind = {\n /** Ref to attach to PromptArea — gives access to imperative methods. */\n ref: React.RefObject<PromptAreaHandle | null>\n /** Current segment array — pass as `value` prop. */\n value: Segment[]\n /** Setter — pass as `onChange` prop. */\n onChange: (segments: Segment[]) => void\n}\n\nexport type PromptAreaState = {\n /** Props to spread directly onto `<PromptArea {...bind} />`. Contains ref, value, and onChange. */\n bind: PromptAreaBind\n /** Derived plain text representation of the current value. */\n plainText: string\n /** `true` when the value is empty or whitespace-only. */\n isEmpty: boolean\n /** `true` when the value contains at least one chip. */\n hasChips: boolean\n /** All chip segments in the current value. */\n chips: ChipSegment[]\n /** Clear all content (both state and the editor DOM). */\n clear: () => void\n /** Focus the editor. */\n focus: () => void\n /** Blur the editor. */\n blur: () => void\n /** Insert a chip at the current cursor position. */\n insertChip: (chip: Omit<ChipSegment, 'type'>) => void\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function usePromptAreaState(options: UsePromptAreaStateOptions = {}): PromptAreaState {\n const { initialValue = [] } = options\n\n const [value, setValue] = useState<Segment[]>(initialValue)\n const ref = useRef<PromptAreaHandle>(null)\n\n // Derived\n const plainText = useMemo(() => segmentsToPlainText(value), [value])\n\n const isEmpty = useMemo(() => {\n if (value.length === 0) return true\n return value.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n }, [value])\n\n const hasChips = useMemo(() => value.some((seg) => seg.type === 'chip'), [value])\n\n const chips = useMemo(\n () => value.filter((seg): seg is ChipSegment => seg.type === 'chip'),\n [value],\n )\n\n // Bind object — safe to spread onto <PromptArea>\n const bind = useMemo<PromptAreaBind>(() => ({ ref, value, onChange: setValue }), [value])\n\n // Actions that proxy to the imperative handle\n const clear = useCallback(() => {\n if (ref.current) {\n ref.current.clear()\n } else {\n setValue([])\n }\n }, [])\n\n const focus = useCallback(() => ref.current?.focus(), [])\n const blur = useCallback(() => ref.current?.blur(), [])\n\n const insertChip = useCallback(\n (chip: Omit<ChipSegment, 'type'>) => ref.current?.insertChip(chip),\n [],\n )\n\n return {\n bind,\n plainText,\n isEmpty,\n hasChips,\n chips,\n clear,\n focus,\n blur,\n insertChip,\n }\n}\n","/**\n * Pre-built trigger configuration factories for common AI chat patterns.\n *\n * Each factory returns a full `TriggerConfig` with sensible defaults.\n * Pass only what you need to override.\n *\n * @example\n * ```tsx\n * <PromptArea\n * triggers={[\n * mentionTrigger({ onSearch: searchUsers }),\n * commandTrigger({ onSearch: searchCommands }),\n * hashtagTrigger(),\n * ]}\n * />\n * ```\n */\n\nimport type { TriggerConfig, TriggerPosition } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Shared option type — everything in TriggerConfig except the keys each\n// factory sets by default.\n// ---------------------------------------------------------------------------\n\ntype TriggerPresetOptions = Omit<Partial<TriggerConfig>, 'char' | 'position' | 'mode'>\n\n// ---------------------------------------------------------------------------\n// @mention — dropdown at any position\n// ---------------------------------------------------------------------------\n\nexport type MentionTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'@'`. */\n char?: string\n}\n\n/**\n * Creates a **mention** trigger (`@`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * accessible label `\"mention\"`.\n */\nexport function mentionTrigger(opts: MentionTriggerOptions = {}): TriggerConfig {\n const { char = '@', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n accessibilityLabel: 'mention',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// /command — dropdown anywhere (opt into line-start-only with `position`)\n// ---------------------------------------------------------------------------\n\nexport type CommandTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'/'`. */\n char?: string\n /**\n * Where the command trigger is valid. Defaults to `'any'`, so commands fire\n * anywhere a `/` follows whitespace — not just at the start of a line.\n * Set to `'start'` to restrict the dropdown to the very start of the input\n * or immediately after a newline (the classic slash-command behavior).\n */\n position?: TriggerPosition\n}\n\n/**\n * Creates a **command** trigger (`/`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'inline'`,\n * accessible label `\"command\"`.\n *\n * By default commands work everywhere in the input. Pass `position: 'start'`\n * to limit them to the start of a line.\n */\nexport function commandTrigger(opts: CommandTriggerOptions = {}): TriggerConfig {\n const { char = '/', position = 'any', ...rest } = opts\n return {\n char,\n position,\n mode: 'dropdown',\n chipStyle: 'inline',\n accessibilityLabel: 'command',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// #hashtag — dropdown at any position, auto-resolve on space\n// ---------------------------------------------------------------------------\n\nexport type HashtagTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'#'`. */\n char?: string\n}\n\n/**\n * Creates a **hashtag / tag** trigger (`#`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * `resolveOnSpace: true`, accessible label `\"tag\"`.\n */\nexport function hashtagTrigger(opts: HashtagTriggerOptions = {}): TriggerConfig {\n const { char = '#', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n resolveOnSpace: true,\n accessibilityLabel: 'tag',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Generic callback trigger (e.g., for file pickers, model selectors)\n// ---------------------------------------------------------------------------\n\nexport type CallbackTriggerOptions = Omit<Partial<TriggerConfig>, 'mode'> & {\n /** The trigger character. Required. */\n char: string\n}\n\n/**\n * Creates a **callback** trigger that fires `onActivate` instead of showing\n * a dropdown. Useful for opening file pickers, model selectors, etc.\n *\n * Defaults: `position: 'start'`, `mode: 'callback'`.\n */\nexport function callbackTrigger(opts: CallbackTriggerOptions): TriggerConfig {\n const { char, ...rest } = opts\n return {\n char,\n position: 'start',\n mode: 'callback',\n ...rest,\n }\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","import { useMemo, type ReactNode } from 'react'\nimport type { SlashCommandInfo } from '@workerdeck/protocol'\nimport { ArrowUp, Square } from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { PromptArea } from '../prompt-area/prompt-area.tsx'\nimport { usePromptAreaState } from '../prompt-area/use-prompt-area-state.ts'\nimport { commandTrigger } from '../prompt-area/trigger-presets.ts'\nimport type { TriggerSuggestion } from '../prompt-area/types.ts'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface ComposerProps {\n onSend: (text: 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 /** Left side of the toolbar row (mode selects, attachments, …). */\n toolbar?: ReactNode\n className?: string\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/** Framed prompt input built on prompt-area's contentEditable: typing \"/\" — at the\n * start or after whitespace — opens a suggestion dropdown fed by `commands`, and a\n * picked command becomes an inline chip. Submit button flips to stop while a turn\n * is running (messages still queue while busy). */\nexport function Composer({\n onSend,\n onInterrupt,\n busy,\n disabled,\n placeholder = 'Message Claude…',\n commands,\n toolbar,\n className,\n}: ComposerProps) {\n const { bind, plainText, isEmpty, clear, focus } = usePromptAreaState()\n\n const triggers = useMemo(() => {\n if (!commands || commands.length === 0) return undefined\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 return [\n commandTrigger({\n onSearch: (query: string): TriggerSuggestion[] => {\n const needle = query.toLowerCase()\n const scored = unique.flatMap((c) => {\n const haystacks = [c.name, ...(c.aliases ?? [])].map((s) => s.toLowerCase())\n const score = haystacks.some((h) => h.startsWith(needle))\n ? 2\n : haystacks.some((h) => h.includes(needle))\n ? 1\n : 0\n return score === 0 ? [] : [{ c, score }]\n })\n scored.sort((a, b) => b.score - a.score)\n return scored.map(({ c }) => ({\n value: c.name,\n label: `/${c.name}${c.argumentHint ? ` ${c.argumentHint}` : ''}`,\n description: c.description,\n }))\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 }, [commands])\n\n const submit = () => {\n const trimmed = plainText.trim()\n if (!trimmed || disabled) return\n onSend(trimmed)\n clear()\n focus()\n }\n\n const canSend = !disabled && !isEmpty\n\n return (\n <div data-slot='composer' className={cn('px-3 pb-3', className)}>\n <div\n className={cn(\n 'mx-auto w-full max-w-3xl overflow-hidden rounded-lg border border-border bg-bg shadow-(--shadow-xs)',\n 'transition-colors focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30',\n disabled && 'opacity-60',\n )}>\n <PromptArea\n {...bind}\n triggers={triggers}\n onSubmit={submit}\n disabled={disabled}\n placeholder={disabled ? 'Session ended' : placeholder}\n minHeight={28}\n maxHeight={192}\n aria-label='Message Claude'\n className='px-3 pt-2.5 pb-1 text-body-sm text-text'\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'>{toolbar}</div>\n {busy ? (\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 </div>\n </div>\n <div className='mx-auto mt-1 w-full max-w-3xl text-center text-label text-fg-4'>\n Enter to send · Shift+Enter for a new line\n </div>\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/** 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 const normalized = model.replace(/\\[.*\\]$/, '')\n const concrete = models.filter((m) => !isDefaultOption(m.value))\n return (\n concrete.find((m) => m.value === normalized) ??\n concrete.find((m) => normalized.includes(m.value) || m.value.includes(normalized))\n )\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 { PermissionMode } 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 type PermissionModeMeta = {\n value: PermissionMode\n label: string\n description: string\n dangerous?: boolean\n}\n\n/** The modes surfaced across UI surfaces (session creation, in-session switcher). */\nexport const PERMISSION_MODES: PermissionModeMeta[] = [\n { value: 'default', label: 'default', description: 'ask for approval' },\n { value: 'acceptEdits', label: 'acceptEdits', description: 'auto-approve file edits' },\n { value: 'plan', label: 'plan', description: 'read-only planning' },\n { value: 'auto', label: 'auto', description: 'model decides when to ask' },\n { value: 'dontAsk', label: 'dontAsk', description: 'never ask — deny unapproved' },\n {\n value: 'bypassPermissions',\n label: 'bypassPermissions',\n description: 'no prompts (danger)',\n dangerous: true,\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 provider engine has no meaning for. Defaults to all of them;\n * pass `PROVIDER_PERMISSION_MODES` for a provider session. */\n modes?: readonly PermissionMode[]\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 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 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' && 'font-mono text-label')}>\n <SelectValue placeholder='permissions' />\n </span>\n </SelectTrigger>\n <SelectContent className='min-w-56'>\n {offered.map((m) => (\n <SelectItem key={m.value} value={m.value}>\n <SelectItemText>\n <span className={cn('font-medium', m.dangerous && 'text-danger')}>{m.label}</span>\n </SelectItemText>\n <span className={cn('text-label', m.dangerous ? 'text-danger/80' : 'text-fg-4')}>\n {m.description}\n </span>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n","import { useState } from 'react'\nimport type { PermissionRequest } from '@workerdeck/protocol'\nimport { ShieldAlert } from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface PermissionPromptProps {\n request: PermissionRequest\n onApprove: (requestId: string) => void\n onDeny: (requestId: string, message?: string) => void\n className?: string\n}\n\nexport function PermissionPrompt({ request, onApprove, onDeny, className }: PermissionPromptProps) {\n const [showInput, setShowInput] = useState(false)\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 <ShieldAlert 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 ?? `Claude wants to use ${request.toolName}`}\n </div>\n {request.description ? (\n <div className='mt-0.5 text-label text-fg-3'>{request.description}</div>\n ) : null}\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 gap-1.5'>\n <Button size='sm' onClick={() => onApprove(request.id)}>\n Allow\n </Button>\n <Button size='sm' variant='outline' onClick={() => onDeny(request.id, 'Denied by user')}>\n Deny\n </Button>\n </div>\n </div>\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","export function formatCost(usd: number | undefined): string {\n if (usd === undefined || Number.isNaN(usd)) return '—'\n if (usd === 0) return '$0.00'\n if (usd < 0.01) return '<$0.01'\n return `$${usd.toFixed(2)}`\n}\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${Math.round(ms)}ms`\n const s = ms / 1000\n if (s < 60) return `${s.toFixed(1)}s`\n const m = Math.floor(s / 60)\n return `${m}m ${Math.round(s % 60)}s`\n}\n\n/** Compact token count, Claude Code-style: 850 → \"850\", 359_000 → \"359.0k\", 1_200_000 → \"1.2M\". */\nexport function formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`\n if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}k`\n return String(Math.round(tokens))\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${bytes} B`\n}\n\n/** Countdown to an epoch-ms deadline: \"2h 18m\", \"12m\", \"<1m\"; \"now\" once passed. */\nexport function formatCountdown(untilEpochMs: number, now = Date.now()): string {\n const remaining = untilEpochMs - now\n if (remaining <= 0) return 'now'\n const minutes = Math.ceil(remaining / 60_000)\n if (minutes < 1) return '<1m'\n if (minutes < 60) return `${minutes}m`\n const days = Math.floor(minutes / (60 * 24))\n if (days >= 1) return `${days}d ${Math.floor((minutes % (60 * 24)) / 60)}h`\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`\n}\n\nexport function formatRelativeTime(epochMs: number | undefined, now = Date.now()): string {\n if (!epochMs) return '—'\n const diff = Math.max(0, now - epochMs)\n const s = Math.floor(diff / 1000)\n if (s < 60) return 'just now'\n const m = Math.floor(s / 60)\n if (m < 60) return `${m}m ago`\n const h = Math.floor(m / 60)\n if (h < 24) return `${h}h ago`\n const d = Math.floor(h / 24)\n return `${d}d ago`\n}\n\n/** Compact one-line preview of a tool input for card headers. */\nexport function toolInputPreview(input: unknown, max = 80): string {\n if (input === null || input === undefined) return ''\n if (typeof input === 'object') {\n const o = input as Record<string, unknown>\n const primary =\n o.command ?? o.file_path ?? o.path ?? o.url ?? o.pattern ?? o.query ?? o.description\n if (typeof primary === 'string') {\n return primary.length > max ? primary.slice(0, max - 1) + '…' : primary\n }\n }\n const text = JSON.stringify(input) ?? ''\n return text.length > max ? text.slice(0, max - 1) + '…' : text\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 } from 'react'\nimport type { TranscriptState } from '@workerdeck/react'\nimport type { ContextUsage, RateLimitInfo } from '@workerdeck/protocol'\nimport { WifiOff } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { ProgressRing } from '../ui/ProgressRing.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { Tip } from '../ui/Tooltip.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatCountdown, formatTokens } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface StatusBarProps {\n state: TranscriptState\n connected: boolean\n className?: string\n}\n\n/** Ticking clock for reset countdowns — rate_limit events are sparse, so tick locally. */\nfunction useNow(intervalMs = 30_000): number {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), intervalMs)\n return () => clearInterval(timer)\n }, [intervalMs])\n return now\n}\n\nconst utilizationColor = (pct: number) =>\n pct >= 95 ? 'text-danger' : pct >= 80 ? 'text-warning' : 'text-fg-3'\n\n/** The CLI reports category colors as its own theme token names ('inactive',\n * 'promptBorder', ...), not CSS colors — only pass through what CSS can render. */\nconst cssColor = (color: string): string | undefined =>\n typeof CSS !== 'undefined' && CSS.supports('color', color) ? color : undefined\n\nfunction ContextMeter({ usage }: { usage: ContextUsage }) {\n return (\n <Tip\n content={\n <div className='flex min-w-44 flex-col gap-1 py-0.5'>\n {usage.categories.map((c) => (\n <div key={c.name} className='flex items-center gap-2'>\n <span\n className='size-2 shrink-0 rounded-full bg-fg-4'\n style={cssColor(c.color) ? { backgroundColor: c.color } : undefined}\n />\n <span className='flex-1'>{c.name}</span>\n <span className='font-mono text-fg-3'>{formatTokens(c.tokens)}</span>\n </div>\n ))}\n <div className='mt-0.5 flex items-center justify-between gap-2 border-t border-border pt-1'>\n <span>Total</span>\n <span className='font-mono text-fg-3'>\n {formatTokens(usage.totalTokens)} / {formatTokens(usage.maxTokens)} (\n {usage.percentage.toFixed(0)}%)\n </span>\n </div>\n </div>\n }>\n <span\n className={cn(\n 'inline-flex cursor-default items-center gap-1 font-mono text-label',\n utilizationColor(usage.percentage),\n )}>\n Ctx {formatTokens(usage.totalTokens)}\n </span>\n </Tip>\n )\n}\n\nfunction RateLimitMeter({ label, info, now }: { label: string; info: RateLimitInfo; now: number }) {\n // The CLI omits utilization on some updates — show the window without a made-up 0%.\n const pct = info.utilization\n const resetsAtMs = info.resetsAt !== undefined ? info.resetsAt * 1000 : undefined\n return (\n <Tip\n content={\n <div className='flex min-w-36 flex-col gap-1 py-0.5'>\n <div className='flex items-center justify-between gap-2'>\n <span>{label} usage</span>\n <span className='font-mono text-fg-3'>\n {pct !== undefined ? `${pct.toFixed(1)}%` : '—'}\n </span>\n </div>\n {resetsAtMs !== undefined ? (\n <div className='flex items-center justify-between gap-2'>\n <span>Resets in</span>\n <span className='font-mono text-fg-3'>{formatCountdown(resetsAtMs, now)}</span>\n </div>\n ) : null}\n {info.isUsingOverage ? <div className='text-warning'>Using overage</div> : null}\n {info.status === 'rejected' ? <div className='text-danger'>Limit reached</div> : null}\n </div>\n }>\n <span\n className={cn(\n 'inline-flex cursor-default items-center gap-1 font-mono text-label',\n info.status === 'rejected' ? 'text-danger' : utilizationColor(pct ?? 0),\n )}>\n <ProgressRing value={pct ?? 0} />\n {label}\n {pct !== undefined ? ` ${pct.toFixed(0)}%` : ''}\n {resetsAtMs !== undefined ? (\n <span className='text-fg-4'>· {formatCountdown(resetsAtMs, now)}</span>\n ) : null}\n </span>\n </Tip>\n )\n}\n\nexport function StatusBar({ state, connected, className }: StatusBarProps) {\n const meta = STATUS_META[state.status]\n const now = useNow()\n const session = state.rateLimits?.five_hour\n const weekly = state.rateLimits?.seven_day\n return (\n <div\n data-slot='status-bar'\n className={cn(\n 'flex items-center gap-3 border-b border-border bg-surface px-3 py-2',\n className,\n )}>\n <Badge variant={meta.variant} dot={!meta.busy}>\n {meta.busy ? <Spinner className='size-3 text-current' /> : null}\n {meta.label}\n </Badge>\n {state.contextUsage ? <ContextMeter usage={state.contextUsage} /> : null}\n {session ? <RateLimitMeter label='Session' info={session} now={now} /> : null}\n {weekly ? <RateLimitMeter label='Weekly' info={weekly} now={now} /> : null}\n <span className='flex-1' />\n {!connected ? (\n <span className='inline-flex items-center gap-1 text-label text-warning'>\n <WifiOff className='size-3' /> reconnecting…\n </span>\n ) : null}\n <span className='font-mono text-label text-fg-3'>{formatCost(state.totalCostUsd)}</span>\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. */\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='smooth'\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-3xl flex-col gap-4 px-4 py-4', className)}\n {...props}>\n {children}\n </StickToBottom.Content>\n )\n}\n\nexport function ConversationScrollButton({ className }: { className?: string }) {\n const { isAtBottom, scrollToBottom } = useStickToBottomContext()\n if (isAtBottom) return null\n return (\n <Button\n variant='outline'\n size='icon'\n aria-label='Scroll to bottom'\n data-slot='conversation-scroll-button'\n className={cn(\n 'absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-(--shadow-md)',\n className,\n )}\n onClick={() => void scrollToBottom()}>\n <ArrowDown className='size-4' />\n </Button>\n )\n}\n","import type { TranscriptItem } from '@workerdeck/react'\nimport { Download, FileText } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatBytes } from '../../lib/format.ts'\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\n/** Three-dot pulse shown while the assistant hasn't produced output yet. */\nexport function Loader({ label, className }: { label?: string; className?: string }) {\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/** One chat turn row. User messages sit right in a bubble; assistant content is flat,\n * full-width (the AI-chat convention — assistant output is the page, user input is quoted). */\nexport function Message({ from, className, ...props }: MessageProps) {\n return (\n <div\n data-slot='message'\n data-from={from}\n className={cn(\n 'flex w-full flex-col gap-1',\n from === 'user' ? 'items-end' : 'items-start',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport function MessageContent({\n className,\n ...props\n}: 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 'in-data-[from=user]:max-w-[85%] in-data-[from=user]:rounded-lg in-data-[from=user]:rounded-br-sm',\n 'in-data-[from=user]:bg-accent-bg in-data-[from=user]:px-3 in-data-[from=user]:py-2',\n 'in-data-[from=user]:whitespace-pre-wrap',\n 'in-data-[from=assistant]:w-full',\n className,\n )}\n {...props}\n />\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(\n 'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',\n className,\n )}>\n {children}\n </Streamdown>\n )\n },\n (prev, next) =>\n prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className,\n)\n","import { useEffect, useRef, 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 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 { useState } from 'react'\nimport type { TranscriptItem } from '@workerdeck/react'\nimport { ChevronDown, Clock, Wrench } 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'\n\nexport type ToolCallItem = Extract<TranscriptItem, { kind: 'tool_call' }>\n\nconst RESULT_PREVIEW_CHARS = 2000\n\nexport interface ToolCallCardProps {\n item: ToolCallItem\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\nexport function ToolCallCard({ item, className }: ToolCallCardProps) {\n const [open, setOpen] = useState(false)\n const [fullResult, setFullResult] = useState(false)\n const status = item.status ?? (item.result === undefined ? 'running' : 'settled')\n const badge = STATE_BADGE[status]\n const isError = status === 'failed' || item.result?.isError === true\n\n const resultText = item.result?.text ?? ''\n const truncated = !fullResult && resultText.length > RESULT_PREVIEW_CHARS\n const shownResult = truncated ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText\n\n 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 <Wrench 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 <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 {open ? (\n <div className='flex flex-col gap-2 border-t border-border p-2.5'>\n <CodeBlock code={JSON.stringify(item.input, null, 2)} label='Parameters' />\n {item.logs?.length ? (\n <CodeBlock code={item.logs.join('\\n')} label='Logs' />\n ) : null}\n {item.result !== undefined ? (\n <div>\n <CodeBlock\n code={shownResult || '(empty result)'}\n label={isError ? 'Error' : 'Result'}\n className={cn(isError && 'border-danger/40 [&_pre]:text-danger')}\n />\n {truncated ? (\n <button\n type='button'\n className='mt-1 text-label text-fg-3 underline-offset-2 hover:underline'\n onClick={() => setFullResult(true)}>\n Show all {resultText.length.toLocaleString()} chars\n </button>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n )\n}\n","import type { TranscriptItem, TranscriptState } from '@workerdeck/react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatDuration } 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 { Reasoning } from './Reasoning.tsx'\nimport { Response } from './Response.tsx'\nimport { ToolCallCard } from './ToolCallCard.tsx'\n\nfunction TurnResultRow({ item }: { item: Extract<TranscriptItem, { kind: 'turn_result' }> }) {\n return (\n <div data-slot='turn-result' className='flex items-center gap-2 py-1'>\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 )\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}: {\n item: TranscriptItem\n fileUrl?: (path: string) => string\n}) {\n switch (item.kind) {\n case 'user':\n return (\n <Message from='user'>\n <MessageContent>{item.text}</MessageContent>\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} />\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/** 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\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 className?: string\n}\n\nexport function Transcript({ state, fileUrl, className }: TranscriptProps) {\n return (\n <Conversation className={className}>\n <ConversationContent>\n {state.items.length === 0 && state.status !== 'starting' ? (\n <div className='py-12 text-center text-body-sm text-fg-4'>No messages yet.</div>\n ) : (\n state.items.map((item) => (\n <TranscriptItemView key={`${item.kind}:${item.id}`} item={item} fileUrl={fileUrl} />\n ))\n )}\n {showLoader(state) ? (\n <Loader label={state.status === 'starting' ? 'Starting session…' : undefined} />\n ) : null}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n )\n}\n","import { useEffect, useMemo, useState, type ReactNode } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { PROVIDER_PERMISSION_MODES } from '@workerdeck/protocol'\nimport { useClaudeSession, useToolCallHost } from '@workerdeck/react'\nimport { cn } from '../../lib/utils.ts'\nimport { Composer } from './Composer.tsx'\nimport { ModelSelect } from './ModelSelect.tsx'\nimport { PermissionModeSelect } from './PermissionModeSelect.tsx'\nimport { PermissionPrompt } from './PermissionPrompt.tsx'\nimport { QuestionPrompt, parseUserQuestions } from './QuestionPrompt.tsx'\nimport { StatusBar } from './StatusBar.tsx'\nimport { Transcript } from './Transcript.tsx'\n\nexport interface SessionPanelProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n /** Optional slot rendered at the top, above the status bar. */\n header?: ReactNode\n className?: string\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 */\nexport function SessionPanel({ client, sessionId, header, className }: SessionPanelProps) {\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 { state, connected, handle, send, approve, deny, interrupt, setModel, setPermissionMode } =\n useClaudeSession(client, sessionId, { onProtocolError: setProtocolError })\n // Callers are told to remount on a session switch, but a changed prop must not leave\n // the previous session's failure on screen.\n useEffect(() => setProtocolError(undefined), [sessionId])\n // Host server-bridged tool calls (provider-engine sessions) in this tab, on the\n // SAME handle the panel attached with — the bridge asks the first attached\n // client. Free for Claude sessions: the guest loads lazily on the first call,\n // which for them never comes.\n useToolCallHost(handle)\n const busy = state.status === 'running' || state.status === 'awaiting_approval'\n const ended = state.status === 'failed' || state.status === 'closed'\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 // \"/model <id>\" switches the model directly instead of going to the CLI.\n const handleSend = (text: string) => {\n const modelCommand = /^\\/model\\s+(\\S+)$/.exec(text)\n if (modelCommand) {\n setModel(modelCommand[1])\n return\n }\n send(text)\n }\n\n return (\n <div\n data-slot='session-panel'\n className={cn('flex h-full min-h-0 flex-col overflow-hidden bg-bg', className)}>\n {header}\n <StatusBar state={state} connected={connected} />\n {protocolError ? (\n <div className='px-3 pt-2'>\n <div\n role='alert'\n className='mx-auto flex w-full max-w-3xl items-start gap-2 rounded-md border border-danger/40 bg-danger-bg px-3 py-2 text-body-sm text-danger'>\n <span className='min-w-0 flex-1 break-words'>{protocolError}</span>\n <button\n type='button'\n onClick={() => setProtocolError(undefined)}\n aria-label='Dismiss error'\n className='shrink-0 opacity-70 transition-opacity hover:opacity-100'>\n ✕\n </button>\n </div>\n </div>\n ) : null}\n <Transcript\n state={state}\n fileUrl={sessionId ? (path) => client.sessionFileUrl(sessionId, path) : undefined}\n />\n {state.pendingApprovals.length > 0 ? (\n <div className='px-3 pb-2'>\n <div className='mx-auto flex w-full max-w-3xl flex-col gap-2'>\n {state.pendingApprovals.map((request) =>\n request.toolName === 'AskUserQuestion' &&\n parseUserQuestions(request.input).length > 0 ? (\n <QuestionPrompt\n key={request.id}\n request={request}\n onAnswer={approve}\n onDismiss={deny}\n />\n ) : (\n <PermissionPrompt\n key={request.id}\n request={request}\n onApprove={approve}\n onDeny={deny}\n />\n ),\n )}\n </div>\n </div>\n ) : null}\n <Composer\n onSend={handleSend}\n onInterrupt={interrupt}\n busy={busy}\n disabled={ended || !sessionId}\n commands={commands}\n toolbar={\n <>\n {state.models?.length ? (\n <ModelSelect\n models={state.models}\n model={state.model}\n onModelChange={setModel}\n disabled={ended}\n />\n ) : null}\n {state.permissionMode ? (\n <PermissionModeSelect\n mode={state.permissionMode}\n onModeChange={setPermissionMode}\n // A provider session rejects the CLI-only modes with a\n // protocol_error — don't offer what can only fail.\n modes={state.engine === 'provider' ? PROVIDER_PERMISSION_MODES : undefined}\n disabled={ended}\n />\n ) : null}\n </>\n }\n />\n </div>\n )\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["SelectPrimitive","AlertDialogPrimitive","MenuPrimitive","DialogPrimitive","TooltipPrimitive","SonnerToaster","rangeStart","File","FileText","FileCard","cssColor","usageTint","EntryIcon"],"sources":["../src/lib/utils.ts","../src/components/ui/Button.tsx","../src/components/ui/Badge.tsx","../src/components/ui/Card.tsx","../src/components/ui/Input.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/Select.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Menu.tsx","../src/components/ui/Dialog.tsx","../src/components/ui/Tooltip.tsx","../src/components/ui/Sonner.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/prompt-area/segment-helpers.ts","../src/lib/format.ts","../src/components/agent/Composer.tsx","../src/components/agent/ContextDialog.tsx","../src/components/agent/HostFilesDialog.tsx","../src/components/agent/McpDialog.tsx","../src/components/agent/SkillsDialog.tsx","../src/components/agent/ModelSelect.tsx","../src/components/agent/PermissionModeSelect.tsx","../src/lib/tool-icon.ts","../src/components/agent/PermissionPrompt.tsx","../src/components/agent/QuestionPrompt.tsx","../src/components/agent/SessionInfoDialog.tsx","../src/components/agent/status.ts","../src/components/agent/StatusBar.tsx","../src/components/agent/Conversation.tsx","../src/components/agent/FileCard.tsx","../src/components/agent/Loader.tsx","../src/components/agent/Message.tsx","../src/components/agent/PromptTokenText.tsx","../src/components/agent/Response.tsx","../src/components/agent/Reasoning.tsx","../src/components/agent/SessionEmptyState.tsx","../src/components/agent/ToolCallCard.tsx","../src/components/agent/Transcript.tsx","../src/components/agent/UsageDialog.tsx","../src/components/agent/SessionPanel.tsx","../src/components/agent/EditorTabs.tsx","../src/components/agent/FileTree.tsx","../src/components/agent/CodeEditor.tsx","../src/components/agent/FileViewer.tsx","../src/components/agent/SessionWorkspace.tsx","../src/components/agent/SessionList.tsx"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n// Register the custom text-* font-size utilities as a font-size class group, or\n// tailwind-merge collapses a size class and a color class (e.g. `text-label\n// text-muted-foreground`) into one bucket and silently drops the size.\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-xl',\n 'display-lg',\n 'display-md',\n 'display-sm',\n 'heading-1',\n 'heading-2',\n 'heading-3',\n 'body',\n 'body-sm',\n 'label',\n 'code',\n ],\n },\n ],\n },\n },\n})\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","import { type ButtonHTMLAttributes, forwardRef } from 'react'\nimport { type VariantProps, cva } from 'class-variance-authority'\nimport { cn } from '../../lib/utils.ts'\n\nconst buttonVariants = cva(\n \"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-1 focus-visible:ring-offset-bg disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-accent-hover',\n outline: 'border-border bg-surface hover:border-border-strong hover:bg-surface-hover',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-surface-hover',\n ghost: 'text-fg-2 hover:bg-surface-hover hover:text-foreground',\n destructive: 'text-danger hover:bg-danger-bg',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n 'default': 'h-8 gap-1.5 px-3',\n 'xs': \"h-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3\",\n 'sm': 'h-7 gap-1 px-2.5 text-xs',\n 'lg': 'h-9 gap-1.5 px-3.5',\n 'icon': 'size-8',\n 'icon-sm': 'size-7',\n },\n },\n defaultVariants: { variant: 'default', size: 'default' },\n },\n)\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant = 'default', size = 'default', type = 'button', ...props }, ref) => (\n <button\n ref={ref}\n type={type}\n data-slot='button'\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n ),\n)\nButton.displayName = 'Button'\n\nexport { buttonVariants }\n","import { type HTMLAttributes } from 'react'\nimport { type VariantProps, cva } from 'class-variance-authority'\nimport { cn } from '../../lib/utils.ts'\n\nconst badgeVariants = cva(\n 'inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-label font-medium whitespace-nowrap',\n {\n variants: {\n variant: {\n neutral: 'border-border bg-surface text-fg-2',\n accent: 'border-transparent bg-accent-bg text-fg-1',\n success: 'border-transparent bg-success-bg text-success',\n warning: 'border-transparent bg-warning-bg text-warning',\n danger: 'border-transparent bg-danger-bg text-danger',\n info: 'border-transparent bg-info-bg text-info',\n },\n mono: {\n true: 'font-mono text-code',\n false: '',\n },\n },\n defaultVariants: { variant: 'neutral', mono: false },\n },\n)\n\nexport interface BadgeProps\n extends HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof badgeVariants> {\n /** Render a leading status dot in the variant's color. */\n dot?: boolean\n}\n\nexport function Badge({ className, variant = 'neutral', mono, dot, children, ...props }: BadgeProps) {\n return (\n <span data-slot='badge' className={cn(badgeVariants({ variant, mono, className }))} {...props}>\n {dot ? <span aria-hidden className='size-1.5 rounded-full bg-current' /> : null}\n {children}\n </span>\n )\n}\n\nexport { badgeVariants }\n","import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type 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 TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { Select as SelectPrimitive } from '@base-ui/react/select'\nimport { Check, ChevronsUpDown } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Select = SelectPrimitive.Root\nexport const SelectValue = SelectPrimitive.Value\nexport const SelectItemText = SelectPrimitive.ItemText\n\nexport const SelectTrigger: FunctionComponent<SelectPrimitive.Trigger.Props> = ({\n className,\n children,\n ...props\n}) => (\n <SelectPrimitive.Trigger\n data-slot='select-trigger'\n className={cn(\n 'inline-flex h-7 items-center justify-between gap-1.5 rounded-md border border-border bg-bg px-2 text-body-sm text-text',\n 'transition-colors outline-none hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'data-popup-open:border-border-strong disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}>\n {children}\n <SelectPrimitive.Icon className='text-fg-4'>\n <ChevronsUpDown className='size-3.5' />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n)\n\nexport const SelectContent: FunctionComponent<\n SelectPrimitive.Popup.Props &\n Pick<SelectPrimitive.Positioner.Props, 'align' | 'alignItemWithTrigger' | 'side' | 'sideOffset'>\n> = ({\n className,\n align = 'start',\n alignItemWithTrigger = false,\n side = 'bottom',\n sideOffset = 6,\n ...props\n}) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Positioner\n align={align}\n alignItemWithTrigger={alignItemWithTrigger}\n side={side}\n sideOffset={sideOffset}\n className='isolate z-60 outline-none'>\n <SelectPrimitive.Popup\n data-slot='select-content'\n className={cn(\n 'max-h-[min(24rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto',\n 'rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',\n className,\n )}\n {...props}\n />\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n)\n\nexport const SelectItem: FunctionComponent<SelectPrimitive.Item.Props> = ({\n className,\n children,\n ...props\n}) => (\n <SelectPrimitive.Item\n data-slot='select-item'\n className={cn(\n 'flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 text-body-sm text-text outline-none select-none',\n 'data-highlighted:bg-surface-hover',\n className,\n )}\n {...props}>\n <span className='flex min-w-0 flex-1 flex-col gap-0.5'>{children}</span>\n <SelectPrimitive.ItemIndicator className='mt-0.5 text-fg-1'>\n <Check className='size-3.5' />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n)\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { type FunctionComponent } from 'react'\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Menu = MenuPrimitive.Root\nexport const MenuTrigger = MenuPrimitive.Trigger\n\nexport const MenuContent: FunctionComponent<\n MenuPrimitive.Popup.Props &\n Pick<MenuPrimitive.Positioner.Props, 'align' | 'side' | 'sideOffset'>\n> = ({ className, align = 'end', side = 'bottom', sideOffset = 6, ...props }) => (\n <MenuPrimitive.Portal>\n <MenuPrimitive.Positioner\n align={align}\n side={side}\n sideOffset={sideOffset}\n className='isolate z-60 outline-none'>\n <MenuPrimitive.Popup\n data-slot='menu-content'\n className={cn(\n 'min-w-48 rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n)\n\nexport const MenuItem: FunctionComponent<MenuPrimitive.Item.Props & { destructive?: boolean }> = ({\n className,\n destructive,\n ...props\n}) => (\n <MenuPrimitive.Item\n data-slot='menu-item'\n className={cn(\n 'flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-body-sm outline-none select-none',\n 'data-highlighted:bg-surface-hover',\n destructive ? 'text-danger' : 'text-text',\n className,\n )}\n {...props}\n />\n)\n\nexport const MenuSeparator: FunctionComponent<MenuPrimitive.Separator.Props> = ({\n className,\n ...props\n}) => (\n <MenuPrimitive.Separator className={cn('my-1 h-px bg-border', className)} {...props} />\n)\n","import { type FunctionComponent } from 'react'\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog'\nimport { X } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Dialog = DialogPrimitive.Root\nexport const DialogTrigger = DialogPrimitive.Trigger\nexport const DialogClose = DialogPrimitive.Close\n\n/**\n * A dismissible panel, sized for reading rather than confirming — the web\n * counterpart of the iOS app's detail sheets (context, usage, session info, MCP).\n *\n * Taller than {@link AlertDialogContent} and scrollable inside, because these\n * carry lists whose length is the engine's business, not the layout's.\n */\nexport const DialogContent: FunctionComponent<\n DialogPrimitive.Popup.Props & { size?: 'sm' | 'md' | 'lg' }\n> = ({ className, children, size = 'md', ...props }) => (\n <DialogPrimitive.Portal>\n <DialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <DialogPrimitive.Popup\n data-slot='dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 flex max-h-[min(42rem,calc(100dvh-3rem))] -translate-x-1/2 -translate-y-1/2 flex-col',\n size === 'sm' && 'w-[min(24rem,calc(100vw-2rem))]',\n size === 'md' && 'w-[min(32rem,calc(100vw-2rem))]',\n size === 'lg' && 'w-[min(46rem,calc(100vw-2rem))]',\n 'rounded-lg border border-border bg-surface shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n)\n\n/** Title row with the close button, pinned above the scrolling body. */\nexport const DialogHeader: FunctionComponent<{\n title: string\n description?: string\n /** Rendered between the title and the close button. */\n actions?: React.ReactNode\n}> = ({ title, description, actions }) => (\n <div className='flex items-start gap-2 border-b border-border px-4 py-3'>\n <div className='min-w-0 flex-1'>\n <DialogPrimitive.Title className='truncate text-body-sm font-semibold text-text'>\n {title}\n </DialogPrimitive.Title>\n {description ? (\n <DialogPrimitive.Description className='mt-0.5 text-label text-fg-4'>\n {description}\n </DialogPrimitive.Description>\n ) : null}\n </div>\n {actions}\n <DialogPrimitive.Close\n aria-label='Close'\n className='-mr-1 flex size-6 shrink-0 items-center justify-center rounded-md text-fg-3 transition-colors outline-none hover:bg-surface-hover hover:text-fg-1'>\n <X className='size-3.5' />\n </DialogPrimitive.Close>\n </div>\n)\n\n/** The scrolling region under the header. */\nexport const DialogBody: FunctionComponent<React.HTMLAttributes<HTMLDivElement>> = ({\n className,\n ...props\n}) => <div className={cn('min-h-0 flex-1 overflow-y-auto p-4', className)} {...props} />\n\n/** A label/value row — the shape every one of these panels is mostly made of. */\nexport const DialogRow: FunctionComponent<{\n label: string\n children: React.ReactNode\n mono?: boolean\n}> = ({ label, children, mono }) => (\n <div className='flex items-baseline justify-between gap-4 py-1.5'>\n <span className='shrink-0 text-label text-fg-3'>{label}</span>\n <span className={cn('min-w-0 truncate text-right text-body-sm text-fg-1', mono && 'font-mono text-label')}>\n {children}\n </span>\n </div>\n)\n","import { type FunctionComponent, type ReactElement, type ReactNode } from 'react'\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip'\nimport { cn } from '../../lib/utils.ts'\n\nexport const TooltipProvider = TooltipPrimitive.Provider\n\nexport const TooltipContent: FunctionComponent<\n TooltipPrimitive.Popup.Props & Pick<TooltipPrimitive.Positioner.Props, 'side' | 'sideOffset'>\n> = ({ className, side = 'top', sideOffset = 6, ...props }) => (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Positioner side={side} sideOffset={sideOffset} className='isolate z-60'>\n <TooltipPrimitive.Popup\n data-slot='tooltip-content'\n className={cn(\n 'rounded-md border border-border bg-surface px-2 py-1 text-label text-fg-2 shadow-(--shadow-md) outline-none',\n className,\n )}\n {...props}\n />\n </TooltipPrimitive.Positioner>\n </TooltipPrimitive.Portal>\n)\n\n/**\n * Convenience wrapper: `<Tip content=\"...\"><Button/></Tip>`.\n *\n * Pass `render` when the trigger must *be* an element you already have — a tab, a\n * row — rather than something wrapped in a span. The default span is fine beside\n * a button but would break any layout that styles its own children (a flex tab\n * strip gets an extra box between the container and its items).\n */\nexport function Tip({\n content,\n render,\n side,\n children,\n}: {\n content: ReactNode\n render?: ReactElement\n side?: 'top' | 'right' | 'bottom' | 'left'\n children?: ReactNode\n}) {\n return (\n <TooltipPrimitive.Root>\n <TooltipPrimitive.Trigger render={render ?? <span className='inline-flex' />}>\n {children}\n </TooltipPrimitive.Trigger>\n <TooltipContent side={side}>{content}</TooltipContent>\n </TooltipPrimitive.Root>\n )\n}\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","/**\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}\n\nexport function CopyButton({ value, className, variant = 'ghost', size = 'icon-sm', ...props }: 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 {copied ? <Check className='size-3.5 text-success' /> : <Copy className='size-3.5' />}\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 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({ code, label, copyable = true, className }: CodeBlockProps) {\n return (\n <div\n data-slot='code-block'\n className={cn('overflow-hidden rounded-md border border-border bg-code-bg', className)}>\n {label !== undefined || copyable ? (\n <div className='flex h-8 items-center justify-between border-b border-border px-2.5'>\n <span className='font-mono text-label text-fg-3'>{label}</span>\n {copyable ? <CopyButton value={code} /> : null}\n </div>\n ) : null}\n <pre className='max-h-64 overflow-auto px-3 py-2 font-mono text-label whitespace-pre-wrap text-fg-2'>\n {code}\n </pre>\n </div>\n )\n}\n","import {\n useCallback,\n useRef,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n} from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface SplitterProps {\n /**\n * ARIA's sense of the word: a `vertical` splitter is a vertical bar between\n * two side-by-side panes, and it resizes a **width**. A `horizontal` one sits\n * between stacked panes and resizes a **height**.\n */\n orientation: 'vertical' | 'horizontal'\n /** Current size of the pane this splitter controls, in pixels. */\n value: number\n onValueChange: (value: number) => void\n min: number\n max: number\n /** Keyboard step. */\n step?: number\n /** Set when dragging the splitter *away* from the origin should shrink the\n * controlled pane — i.e. the pane is on the right or the bottom. */\n inverted?: boolean\n /** Required: \"Resize\" alone does not say which of two splitters this is. */\n 'aria-label': string\n className?: string\n}\n\n/**\n * A draggable pane divider.\n *\n * Hand-rolled rather than depended on: `@base-ui/react` ships no splitter, the\n * behaviour is a hundred lines of pointer events, and this repo's instinct at\n * this layer is to own it (the composer is vendored for the same reason).\n *\n * Pointer capture is what makes it survive a fast drag — without it the pointer\n * leaves the 5px bar within a frame and the moves go to whatever is underneath,\n * which for this layout is an iframe-free but still selection-happy code pane.\n * The drag origin is captured on pointerdown and every move is measured against\n * it, so the pane cannot drift relative to the cursor over a long drag the way\n * per-move deltas do once clamping is involved.\n *\n * Keyboard-operable and announced as a separator, because a pane you can only\n * size by dragging is a pane some people cannot size.\n */\nexport function Splitter({\n orientation,\n value,\n onValueChange,\n min,\n max,\n step = 16,\n inverted,\n 'aria-label': label,\n className,\n}: SplitterProps) {\n const drag = useRef<{ origin: number; start: number } | null>(null)\n const vertical = orientation === 'vertical'\n\n const clamp = useCallback((next: number) => Math.min(max, Math.max(min, next)), [min, max])\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n // Secondary buttons open context menus; they are not drags.\n if (event.button !== 0) return\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = { origin: vertical ? event.clientX : event.clientY, start: value }\n }\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const state = drag.current\n if (!state) return\n const delta = (vertical ? event.clientX : event.clientY) - state.origin\n onValueChange(clamp(state.start + (inverted ? -delta : delta)))\n }\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (!drag.current) return\n drag.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n }\n\n const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n // The arrows that move *along* this splitter's axis of travel; the other\n // pair is left to the page, which is what a separator should do with them.\n const grow = vertical ? 'ArrowRight' : 'ArrowDown'\n const shrink = vertical ? 'ArrowLeft' : 'ArrowUp'\n const direction = inverted ? -1 : 1\n if (event.key === grow) onValueChange(clamp(value + step * direction))\n else if (event.key === shrink) onValueChange(clamp(value - step * direction))\n else if (event.key === 'Home') onValueChange(min)\n else if (event.key === 'End') onValueChange(max)\n else return\n event.preventDefault()\n }\n\n return (\n <div\n data-slot='splitter'\n role='separator'\n tabIndex={0}\n aria-label={label}\n aria-orientation={orientation}\n aria-valuenow={Math.round(value)}\n aria-valuemin={min}\n aria-valuemax={max}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n className={cn(\n // A 1px line that reads as a border, with a larger invisible grab area\n // around it — a hairline is an honest divider and a cruel target.\n 'group relative shrink-0 touch-none bg-border transition-colors',\n 'hover:bg-border-strong focus-visible:bg-accent focus-visible:outline-none',\n vertical ? 'w-px cursor-col-resize' : 'h-px cursor-row-resize',\n className,\n )}>\n <span\n aria-hidden\n className={cn(\n 'absolute',\n vertical ? '-inset-x-[3px] inset-y-0' : '-inset-y-[3px] inset-x-0',\n )}\n />\n </div>\n )\n}\n","import { cn } from '../../lib/utils.ts'\n\nexport interface ProgressRingProps {\n /** Filled share, 0–100 (clamped). */\n value: number\n /** Outer diameter in px. */\n size?: number\n strokeWidth?: number\n className?: string\n}\n\n/** Tiny SVG progress circle; stroke color comes from `currentColor` so callers set it\n * via text color classes (e.g. warning/danger past thresholds). */\nexport function ProgressRing({ value, size = 13, strokeWidth = 2, className }: ProgressRingProps) {\n const clamped = Math.min(100, Math.max(0, value))\n const radius = (size - strokeWidth) / 2\n const circumference = 2 * Math.PI * radius\n return (\n <svg\n data-slot='progress-ring'\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n role='img'\n aria-label={`${Math.round(clamped)}%`}\n className={cn('shrink-0 -rotate-90', className)}>\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill='none'\n stroke='currentColor'\n strokeOpacity={0.2}\n strokeWidth={strokeWidth}\n />\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill='none'\n stroke='currentColor'\n strokeWidth={strokeWidth}\n strokeLinecap='round'\n strokeDasharray={circumference}\n strokeDashoffset={circumference * (1 - clamped / 100)}\n />\n </svg>\n )\n}\n","/**\n * Pure logic engine for the PromptArea component.\n * No DOM dependencies - fully testable in Node.\n */\nimport type { Segment, ChipSegment, TriggerConfig, TriggerPosition, ActiveTrigger } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Serialization\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an array of segments to a plain text string.\n * Chips are represented as `{trigger}{displayText}` (e.g., \"@Alice\").\n */\nexport function segmentsToPlainText(segments: Segment[]): string {\n return segments\n .map((seg) => {\n if (seg.type === 'text') return seg.text\n return `${seg.trigger}${seg.displayText}`\n })\n .join('')\n}\n\n/**\n * Converts plain text into a single text segment.\n * Used for initial value conversion from plain strings.\n */\nexport function plainTextToSegments(text: string): Segment[] {\n if (!text) return []\n return [{ type: 'text', text }]\n}\n\n/**\n * Truncates segments so their combined plain-text length is at most `maxLength`.\n * Whole segments are kept while they fit; a text segment that crosses the limit\n * is sliced to fit, and a chip that would cross the limit is dropped (a chip\n * can't be partially represented).\n */\nexport function truncateSegmentsToLength(segments: Segment[], maxLength: number): Segment[] {\n if (maxLength <= 0) return []\n const result: Segment[] = []\n let length = 0\n for (const seg of segments) {\n const segLength =\n seg.type === 'text' ? seg.text.length : seg.trigger.length + seg.displayText.length\n if (length + segLength <= maxLength) {\n result.push(seg)\n length += segLength\n continue\n }\n if (seg.type === 'text') {\n let remaining = maxLength - length\n if (remaining > 0) {\n // Don't split a surrogate pair: if the cut lands right after a high\n // surrogate, drop the incomplete code point instead of a lone surrogate.\n const code = seg.text.charCodeAt(remaining - 1)\n if (code >= 0xd800 && code <= 0xdbff) remaining -= 1\n if (remaining > 0) result.push({ type: 'text', text: seg.text.slice(0, remaining) })\n }\n }\n break\n }\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Whitespace / word boundaries\n// ---------------------------------------------------------------------------\n\n/**\n * Single source of truth for what counts as an inline-whitespace word boundary\n * in the editor model: a space, newline, or tab.\n *\n * Trigger detection, paste auto-resolution, and position validation all rely\n * on the *same* notion of a boundary — keeping it here prevents the three\n * call sites from silently drifting apart (e.g. one handling tabs and the\n * others not).\n */\nexport function isInlineWhitespace(char: string | undefined): boolean {\n return char === ' ' || char === '\\n' || char === '\\t'\n}\n\n/**\n * Builds a lookup from trigger character to its config. When two triggers\n * share a character the first one wins, preserving the previous `Array.find`\n * semantics while turning the per-character scan into an O(1) map read.\n */\nfunction buildTriggerCharMap(triggers: TriggerConfig[]): Map<string, TriggerConfig> {\n const map = new Map<string, TriggerConfig>()\n for (const trigger of triggers) {\n if (!map.has(trigger.char)) map.set(trigger.char, trigger)\n }\n return map\n}\n\n// ---------------------------------------------------------------------------\n// Trigger position validation\n// ---------------------------------------------------------------------------\n\n/**\n * Checks whether a trigger character at the given position in text\n * is valid according to the position rule.\n *\n * @param text - The full text content\n * @param charIndex - The index of the trigger character in the text\n * @param position - The position rule to validate against\n */\nexport function isValidTriggerPosition(\n text: string,\n charIndex: number,\n position: TriggerPosition,\n): boolean {\n if (charIndex === 0) return true\n\n const prevChar = text[charIndex - 1]\n\n if (position === 'start') {\n return prevChar === '\\n'\n }\n\n // position === 'any': valid after any whitespace\n return isInlineWhitespace(prevChar)\n}\n\n// ---------------------------------------------------------------------------\n// Trigger detection\n// ---------------------------------------------------------------------------\n\n/**\n * Scans backwards from the cursor position to detect if the user is\n * currently typing a trigger word.\n *\n * Returns the active trigger info, or null if no trigger is active.\n *\n * @param text - The full plain text content\n * @param cursorPos - The cursor position (character offset from start)\n * @param triggers - Available trigger configurations\n */\nexport function detectActiveTrigger(\n text: string,\n cursorPos: number,\n triggers: TriggerConfig[],\n): ActiveTrigger | null {\n if (!text || cursorPos === 0 || triggers.length === 0) return null\n\n const triggerByChar = buildTriggerCharMap(triggers)\n\n // Scan backwards from cursor to find the nearest trigger character.\n // Stop at whitespace (trigger word has ended) or start of text.\n for (let i = cursorPos - 1; i >= 0; i--) {\n const char = text[i]\n\n // If we hit whitespace before finding a trigger, check if this whitespace\n // is immediately followed by a trigger character\n if (isInlineWhitespace(char)) {\n // The character after this whitespace could be a trigger\n if (i + 1 < cursorPos) {\n const nextChar = text[i + 1]\n const matchingTrigger = triggerByChar.get(nextChar)\n if (matchingTrigger && isValidTriggerPosition(text, i + 1, matchingTrigger.position)) {\n return {\n config: matchingTrigger,\n startOffset: i + 1,\n query: text.slice(i + 2, cursorPos),\n }\n }\n }\n // No trigger found after this whitespace, stop searching\n return null\n }\n\n // Check if this character is a trigger character\n const matchingTrigger = triggerByChar.get(char)\n if (matchingTrigger && isValidTriggerPosition(text, i, matchingTrigger.position)) {\n return {\n config: matchingTrigger,\n startOffset: i,\n query: text.slice(i + 1, cursorPos),\n }\n }\n }\n\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Chip resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves an active trigger into a chip within the segments array.\n * Replaces the trigger text (trigger char + query) with a chip segment.\n *\n * @param segments - Current document segments\n * @param activeTrigger - The active trigger to resolve\n * @param chip - The chip data (value, displayText, optional data)\n * @returns New segments array with the chip inserted, and the new cursor position\n */\nexport function resolveChip(\n segments: Segment[],\n activeTrigger: ActiveTrigger,\n chip: { value: string; displayText: string; data?: unknown; autoResolved?: boolean },\n): { segments: Segment[]; cursorOffset: number } {\n const triggerStart = activeTrigger.startOffset\n const triggerEnd = triggerStart + 1 + activeTrigger.query.length // +1 for trigger char\n\n // Build the new segments by mapping plain text positions back to segment boundaries\n const newSegments: Segment[] = []\n let offset = 0\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipText = `${seg.trigger}${seg.displayText}`\n const chipStart = offset\n const chipEnd = offset + chipText.length\n\n // If the trigger range overlaps with this chip, something is wrong.\n // Chips should not be partially replaced.\n if (chipEnd <= triggerStart || chipStart >= triggerEnd) {\n newSegments.push(seg)\n }\n offset = chipEnd\n } else {\n const textStart = offset\n const textEnd = offset + seg.text.length\n\n if (textEnd <= triggerStart) {\n // Entirely before the trigger - keep as-is\n newSegments.push(seg)\n } else if (textStart >= triggerEnd) {\n // Entirely after the trigger - keep as-is\n newSegments.push(seg)\n } else {\n // This text segment contains (part of) the trigger range\n const beforeText = seg.text.slice(0, Math.max(0, triggerStart - textStart))\n const afterText = seg.text.slice(Math.min(seg.text.length, triggerEnd - textStart))\n\n if (beforeText) {\n newSegments.push({ type: 'text', text: beforeText })\n }\n\n const newChip: ChipSegment = {\n type: 'chip',\n trigger: activeTrigger.config.char,\n value: chip.value,\n displayText: chip.displayText,\n ...(chip.data !== undefined ? { data: chip.data } : {}),\n ...(chip.autoResolved ? { autoResolved: true } : {}),\n }\n newSegments.push(newChip)\n\n // Add trailing space after chip, then any remaining text\n if (afterText) {\n newSegments.push({ type: 'text', text: ' ' + afterText.replace(/^\\s/, '') })\n } else {\n newSegments.push({ type: 'text', text: ' ' })\n }\n }\n\n offset = textEnd\n }\n }\n\n // Merge adjacent text segments\n const merged = mergeAdjacentTextSegments(newSegments)\n\n // Cursor should be placed after the chip + trailing space.\n // Find the *last* matching chip so duplicates resolve correctly.\n let lastChipEndOffset = -1\n let runningOffset = 0\n for (const seg of merged) {\n if (seg.type === 'text') {\n runningOffset += seg.text.length\n } else {\n runningOffset += seg.trigger.length + seg.displayText.length\n if (\n seg.value === chip.value &&\n seg.displayText === chip.displayText &&\n seg.trigger === activeTrigger.config.char\n ) {\n lastChipEndOffset = runningOffset\n }\n }\n }\n // +1 accounts for the trailing space after the chip\n const cursorOffset = lastChipEndOffset === -1 ? runningOffset : lastChipEndOffset + 1\n\n return { segments: merged, cursorOffset }\n}\n\n/**\n * Replaces the active trigger's range with **plain text** instead of a chip.\n *\n * The sibling of {@link resolveChip}, for suggestions that are a typing aid\n * rather than a token: what lands in the document is ordinary editable text the\n * user is expected to finish and change, and it must not look or behave like a\n * resolved chip — no immutability, no trigger character, nothing for a consumer\n * to parse back out. The caret is left at the end of the inserted text so typing\n * continues from there.\n */\nexport function resolveText(\n segments: Segment[],\n activeTrigger: ActiveTrigger,\n text: string,\n): { segments: Segment[]; cursorOffset: number } {\n const triggerStart = activeTrigger.startOffset\n const triggerEnd = triggerStart + 1 + activeTrigger.query.length // +1 for trigger char\n\n const newSegments: Segment[] = []\n let offset = 0\n let cursorOffset = triggerStart + text.length\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipEnd = offset + `${seg.trigger}${seg.displayText}`.length\n // A trigger range can never overlap a chip — chips are atomic — so a chip\n // is either wholly before or wholly after, and is kept either way.\n if (chipEnd <= triggerStart || offset >= triggerEnd) newSegments.push(seg)\n offset = chipEnd\n continue\n }\n const textStart = offset\n const textEnd = offset + seg.text.length\n if (textEnd <= triggerStart || textStart >= triggerEnd) {\n newSegments.push(seg)\n } else {\n const before = seg.text.slice(0, Math.max(0, triggerStart - textStart))\n const after = seg.text.slice(Math.min(seg.text.length, triggerEnd - textStart))\n if (before) newSegments.push({ type: 'text', text: before })\n newSegments.push({ type: 'text', text })\n if (after) newSegments.push({ type: 'text', text: after })\n }\n offset = textEnd\n }\n\n const merged = mergeAdjacentTextSegments(newSegments)\n // Clamp: a trigger detected against a stale document could otherwise leave the\n // caret past the end, which renders as no caret at all.\n const total = segmentsToPlainText(merged).length\n if (cursorOffset > total) cursorOffset = total\n return { segments: merged, cursorOffset }\n}\n\n// ---------------------------------------------------------------------------\n// Chip removal\n// ---------------------------------------------------------------------------\n\n/**\n * Removes a chip at the given segment index and merges adjacent text segments.\n *\n * @param segments - Current document segments\n * @param index - The segment index to remove\n * @returns New segments array with the chip removed\n */\nexport function removeChipAtIndex(segments: Segment[], index: number): Segment[] {\n if (index < 0 || index >= segments.length) return segments\n if (segments[index].type !== 'chip') return segments\n\n const result = [...segments.slice(0, index), ...segments.slice(index + 1)]\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Reverts an auto-resolved chip at the given segment index back to plain text.\n * The text includes the trigger character + display text (e.g., \"#readme\").\n *\n * @param segments - Current document segments\n * @param index - The segment index to revert\n * @returns New segments with the chip replaced by text, or null if not applicable\n */\nexport function revertChipAtIndex(\n segments: Segment[],\n index: number,\n): { segments: Segment[]; revertedText: string } | null {\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n if (seg.type !== 'chip' || !seg.autoResolved) return null\n\n const revertedText = `${seg.trigger}${seg.displayText}`\n const result = [\n ...segments.slice(0, index),\n { type: 'text' as const, text: revertedText },\n ...segments.slice(index + 1),\n ]\n return { segments: mergeAdjacentTextSegments(result), revertedText }\n}\n\n// ---------------------------------------------------------------------------\n// Paste: resolve trigger patterns in segments\n// ---------------------------------------------------------------------------\n\n/**\n * Scans text segments for trigger patterns and auto-resolves them into chips.\n * Only resolves triggers that have `resolveOnSpace: true`.\n *\n * Trigger patterns must appear at word boundaries: start of text, after\n * whitespace, or after a newline. This avoids false positives like email\n * addresses (user@example.com).\n */\nexport function resolveTriggersInSegments(\n segments: Segment[],\n triggers: TriggerConfig[],\n): Segment[] {\n const autoResolveTriggers = triggers.filter((t) => t.resolveOnSpace)\n if (autoResolveTriggers.length === 0) return segments\n\n const triggerByChar = buildTriggerCharMap(autoResolveTriggers)\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n result.push(seg)\n continue\n }\n\n const parts = splitTextByTriggerPatterns(seg.text, triggerByChar)\n result.push(...parts)\n }\n\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Splits a text string into text and chip segments based on trigger patterns.\n * A trigger pattern is: (start-of-string | whitespace) + trigger_char + word_chars\n * followed by whitespace or end-of-string.\n */\nfunction splitTextByTriggerPatterns(\n text: string,\n triggerByChar: Map<string, TriggerConfig>,\n): Segment[] {\n if (!text) return []\n\n const segments: Segment[] = []\n let i = 0\n\n while (i < text.length) {\n const char = text[i]\n\n if (triggerByChar.has(char)) {\n const isAtBoundary = i === 0 || isInlineWhitespace(text[i - 1])\n\n if (isAtBoundary) {\n const trigger = triggerByChar.get(char)\n if (trigger && isValidTriggerPosition(text, i, trigger.position)) {\n let end = i + 1\n while (end < text.length && !isInlineWhitespace(text[end])) {\n end++\n }\n\n const query = text.slice(i + 1, end)\n if (query.length > 0) {\n // Treat both undefined and '' from onSelect as \"no custom label\"\n // and fall back to the query — an empty displayText would render\n // a blank chip.\n const displayText = trigger.onSelect?.({ value: query, label: query }) || query\n segments.push({\n type: 'chip',\n trigger: char,\n value: query,\n displayText,\n autoResolved: true,\n })\n i = end\n continue\n }\n }\n }\n }\n\n const start = i\n i++\n while (i < text.length && !(triggerByChar.has(text[i]) && isInlineWhitespace(text[i - 1]))) {\n i++\n }\n segments.push({ type: 'text', text: text.slice(start, i) })\n }\n\n return segments\n}\n\n// ---------------------------------------------------------------------------\n// Text range replacement\n// ---------------------------------------------------------------------------\n\n/**\n * Replaces a range of plain text within the segments array.\n * Handles segment boundaries correctly, preserving chip segments.\n *\n * @param segments - Current document segments\n * @param start - Start offset in plain text\n * @param end - End offset in plain text\n * @param replacement - The replacement text\n * @returns New segments array with the replacement applied\n */\nexport function replaceTextRange(\n segments: Segment[],\n start: number,\n end: number,\n replacement: string,\n): Segment[] {\n const newSegments: Segment[] = []\n let offset = 0\n let inserted = false\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipText = `${seg.trigger}${seg.displayText}`\n const chipStart = offset\n const chipEnd = offset + chipText.length\n\n // For insertion (start === end), insert before this chip if position matches\n if (!inserted && start === end && chipStart === start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n\n if (chipEnd <= start || chipStart >= end) {\n newSegments.push(seg)\n }\n // Chips within the range are removed\n offset = chipEnd\n } else {\n const textStart = offset\n const textEnd = offset + seg.text.length\n\n // Check if this segment contains the insertion/replacement point\n const isBefore = start === end ? textEnd < start : textEnd <= start\n const isAfter = start === end ? textStart > end : textStart >= end\n\n if (isBefore) {\n // Entirely before the range\n newSegments.push(seg)\n } else if (isAfter) {\n // Entirely after the range\n newSegments.push(seg)\n } else {\n // Overlaps with the range (or contains the insertion point)\n const beforeText = seg.text.slice(0, Math.max(0, start - textStart))\n const afterText = seg.text.slice(Math.min(seg.text.length, end - textStart))\n\n if (beforeText) {\n newSegments.push({ type: 'text', text: beforeText })\n }\n // Insert replacement only once (when we first enter the range)\n if (!inserted && textStart <= start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n if (afterText) {\n newSegments.push({ type: 'text', text: afterText })\n }\n }\n\n offset = textEnd\n }\n }\n\n // Fallback: if replacement wasn't inserted (e.g., insertion at very end)\n if (!inserted && replacement) {\n newSegments.push({ type: 'text', text: replacement })\n }\n\n return mergeAdjacentTextSegments(newSegments)\n}\n\n// ---------------------------------------------------------------------------\n// Markdown formatting shortcuts\n// ---------------------------------------------------------------------------\n\n/**\n * Toggles markdown wrap markers around a selected text range.\n * If the selection is already wrapped with the marker, unwraps it.\n * If not wrapped, wraps it.\n *\n * @param segments - Current document segments\n * @param selectionStart - Start offset in plain text\n * @param selectionEnd - End offset in plain text\n * @param marker - The markdown marker (e.g., '**' for bold, '*' for italic)\n * @returns New segments and selection offsets, or null if selection is collapsed\n */\nexport function toggleMarkdownWrap(\n segments: Segment[],\n selectionStart: number,\n selectionEnd: number,\n marker: string,\n): { segments: Segment[]; selectionStart: number; selectionEnd: number } | null {\n if (selectionStart === selectionEnd) return null\n\n const plainText = segmentsToPlainText(segments)\n const markerLen = marker.length\n\n // Check if already wrapped\n const hasOpeningMarker =\n selectionStart >= markerLen &&\n plainText.slice(selectionStart - markerLen, selectionStart) === marker\n const hasClosingMarker =\n selectionEnd + markerLen <= plainText.length &&\n plainText.slice(selectionEnd, selectionEnd + markerLen) === marker\n\n let isWrapped = hasOpeningMarker && hasClosingMarker\n\n // For single-char markers (e.g., '*'), ensure we're not matching\n // inside a multi-char marker (e.g., '**')\n if (isWrapped && markerLen === 1) {\n const charBeforeOpening =\n selectionStart > markerLen ? plainText[selectionStart - markerLen - 1] : ''\n const charAfterClosing =\n selectionEnd + markerLen < plainText.length ? plainText[selectionEnd + markerLen] : ''\n if (charBeforeOpening === marker || charAfterClosing === marker) {\n isWrapped = false\n }\n }\n\n if (isWrapped) {\n // Unwrap: remove closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd + markerLen, '')\n const afterOpening = replaceTextRange(\n afterClosing,\n selectionStart - markerLen,\n selectionStart,\n '',\n )\n return {\n segments: afterOpening,\n selectionStart: selectionStart - markerLen,\n selectionEnd: selectionEnd - markerLen,\n }\n }\n\n // Wrap: insert closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd, marker)\n const afterOpening = replaceTextRange(afterClosing, selectionStart, selectionStart, marker)\n return {\n segments: afterOpening,\n selectionStart: selectionStart + markerLen,\n selectionEnd: selectionEnd + markerLen,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Inline markdown parsing\n// ---------------------------------------------------------------------------\n\nexport type MarkdownToken =\n | { type: 'plain'; text: string }\n | { type: 'bold'; text: string }\n | { type: 'italic'; text: string }\n | { type: 'bold-italic'; text: string }\n | { type: 'url'; text: string }\n\n/**\n * Parses text for simple inline markdown: bold, italic, bold-italic, and URLs.\n * Does NOT handle block-level markdown (lists, headings, etc.).\n */\nexport function parseInlineMarkdown(text: string): MarkdownToken[] {\n if (!text) return []\n\n const tokens: MarkdownToken[] = []\n // Regex patterns for inline markdown elements:\n // 1. ***text*** or ___text___ -> bold-italic\n // 2. **text** or __text__ -> bold\n // 3. *text* or _text_ -> italic\n // 4. https://... or http://... -> URL\n const pattern = /(\\*{3}(.+?)\\*{3})|(\\*{2}(.+?)\\*{2})|(\\*(.+?)\\*)|(https?:\\/\\/[^\\s),]+)/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n while ((match = pattern.exec(text)) !== null) {\n // Add any plain text before this match\n if (match.index > lastIndex) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex, match.index) })\n }\n\n if (match[1] && match[2]) {\n // ***bold-italic***\n tokens.push({ type: 'bold-italic', text: match[2] })\n } else if (match[3] && match[4]) {\n // **bold**\n tokens.push({ type: 'bold', text: match[4] })\n } else if (match[5] && match[6]) {\n // *italic*\n tokens.push({ type: 'italic', text: match[6] })\n } else if (match[7]) {\n // URL\n tokens.push({ type: 'url', text: match[7] })\n }\n\n lastIndex = match.index + match[0].length\n }\n\n // Add any remaining plain text\n if (lastIndex < text.length) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex) })\n }\n\n return tokens\n}\n\n// ---------------------------------------------------------------------------\n// Segment comparison\n// ---------------------------------------------------------------------------\n\n/**\n * Shallow equality check for two segment arrays.\n * Compares type, text, trigger, value, displayText, and autoResolved fields.\n * Avoids JSON.stringify overhead for the common case.\n */\nexport function segmentsEqual(a: Segment[], b: Segment[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n\n for (let i = 0; i < a.length; i++) {\n const sa = a[i]\n const sb = b[i]\n if (sa.type !== sb.type) return false\n if (sa.type === 'text') {\n if (sb.type !== 'text' || sa.text !== sb.text) return false\n } else {\n if (\n sb.type !== 'chip' ||\n sa.trigger !== sb.trigger ||\n sa.value !== sb.value ||\n sa.displayText !== sb.displayText ||\n sa.autoResolved !== sb.autoResolved\n )\n return false\n }\n }\n return true\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merges adjacent text segments into single text segments.\n * Also removes empty text segments.\n */\nexport function mergeAdjacentTextSegments(segments: Segment[]): Segment[] {\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'text' && seg.text === '') continue\n\n const last = result[result.length - 1]\n if (seg.type === 'text' && last?.type === 'text') {\n // Merge with previous text segment\n result[result.length - 1] = { type: 'text', text: last.text + seg.text }\n } else {\n result.push(seg)\n }\n }\n\n return result\n}\n","/**\n * List auto-formatting logic for the PromptArea component.\n * Pure — no DOM dependencies, fully testable in Node.\n */\nimport type { Segment } from './types.ts'\nimport { replaceTextRange, segmentsToPlainText } from './prompt-area-engine.ts'\n\n/**\n * Information about a list line at a given cursor position.\n */\nexport type ListContext = {\n /** Offset in plain text where the line begins */\n lineStart: number\n /** The full prefix including indentation (e.g., \" • \") */\n prefix: string\n /** Number of indentation levels (each = 2 spaces) */\n indent: number\n /** Type of list */\n listType: 'bullet' | 'numbered'\n /** For bullet lists, the marker char actually used (`•`, `-`, or `*`) */\n marker?: string\n /** For numbered lists, the number */\n number?: number\n /** Offset in plain text where content after the prefix starts */\n contentStart: number\n}\n\n/**\n * Parsed shape of a single list line — the SINGLE source of truth for what\n * counts as a list line (both `getListContext` and the renumber engine derive\n * from this, so the bullet/number regexes live in exactly one place).\n *\n * Offsets (`numberStart`/`numberEnd`) are relative to the START of the line.\n */\ntype ParsedListLine =\n | { kind: 'bullet'; indent: number; marker: string; prefixLen: number }\n | {\n kind: 'numbered'\n indent: number\n number: number\n /** Offset of the first digit within the line. */\n numberStart: number\n /** Offset just past the last digit within the line (exclusive). */\n numberEnd: number\n prefixLen: number\n }\n\n/** Classifies a single line as a bullet/numbered list item, or null. */\nfunction parseListLine(line: string): ParsedListLine | null {\n const bulletMatch = line.match(/^(\\s*)([•\\-*]) /)\n if (bulletMatch) {\n return {\n kind: 'bullet',\n indent: Math.floor(bulletMatch[1].length / 2),\n marker: bulletMatch[2],\n prefixLen: bulletMatch[0].length,\n }\n }\n\n const numberMatch = line.match(/^(\\s*)(\\d+)\\. /)\n if (numberMatch) {\n const numberStart = numberMatch[1].length\n return {\n kind: 'numbered',\n indent: Math.floor(numberMatch[1].length / 2),\n number: parseInt(numberMatch[2], 10),\n numberStart,\n numberEnd: numberStart + numberMatch[2].length,\n prefixLen: numberMatch[0].length,\n }\n }\n\n return null\n}\n\n/**\n * Detects if the cursor is in a list line and returns context about it.\n *\n * @param text - The full plain text content\n * @param cursorPos - The cursor position (character offset from start)\n * @returns List context if the cursor is in a list line, null otherwise\n */\nexport function getListContext(text: string, cursorPos: number): ListContext | null {\n const lineStart = text.lastIndexOf('\\n', cursorPos - 1) + 1\n const lineEnd = text.indexOf('\\n', cursorPos)\n const line = text.slice(lineStart, lineEnd === -1 ? text.length : lineEnd)\n\n const parsed = parseListLine(line)\n if (!parsed) return null\n\n return {\n lineStart,\n prefix: line.slice(0, parsed.prefixLen),\n indent: parsed.indent,\n listType: parsed.kind,\n ...(parsed.kind === 'bullet' ? { marker: parsed.marker } : { number: parsed.number }),\n contentStart: lineStart + parsed.prefixLen,\n }\n}\n\n/**\n * Detects if the user just typed a list trigger pattern (e.g., \"- \" or \"* \")\n * and returns the segments with the replacement applied.\n */\nexport function autoFormatListPrefix(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const lineStart = plainText.lastIndexOf('\\n', cursorPos - 1) + 1\n const lineText = plainText.slice(lineStart, cursorPos)\n\n const match = lineText.match(/^(\\s*)[-*] $/)\n if (!match) return null\n\n const indent = match[1]\n const replacement = `${indent}• `\n const rangeStart = lineStart\n const rangeEnd = lineStart + lineText.length\n\n const newSegments = replaceTextRange(segments, rangeStart, rangeEnd, replacement)\n return {\n segments: newSegments,\n cursorOffset: lineStart + replacement.length,\n }\n}\n\n/**\n * Handles Enter key in a list line — continues the list or exits.\n */\nexport function insertListContinuation(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n const lineEnd = plainText.indexOf('\\n', cursorPos)\n const lineContent = plainText.slice(ctx.contentStart, lineEnd === -1 ? plainText.length : lineEnd)\n\n if (lineContent.trim() === '') {\n // Enter on an empty item: outdent one level if nested (Notion/Docs style),\n // otherwise remove the prefix and exit the list to plain text.\n if (ctx.indent > 0) {\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart + 2, '')\n return {\n segments: newSegments,\n cursorOffset: Math.max(ctx.lineStart, cursorPos - 2),\n }\n }\n const newSegments = replaceTextRange(\n segments,\n ctx.lineStart,\n ctx.lineStart + ctx.prefix.length,\n '',\n )\n return {\n segments: newSegments,\n cursorOffset: ctx.lineStart,\n }\n }\n\n const indent = ' '.repeat(ctx.indent)\n let nextPrefix: string\n if (ctx.listType === 'bullet') {\n nextPrefix = `${indent}${ctx.marker ?? '•'} `\n } else {\n const nextNum = (ctx.number ?? 1) + 1\n nextPrefix = `${indent}${nextNum}. `\n }\n\n const insertion = `\\n${nextPrefix}`\n const newSegments = replaceTextRange(segments, cursorPos, cursorPos, insertion)\n return {\n segments: newSegments,\n cursorOffset: cursorPos + insertion.length,\n }\n}\n\n/** Returns the indent level of the list line directly above `lineStart`, or null. */\nfunction getPrevListLineLevel(text: string, lineStart: number): number | null {\n if (lineStart === 0) return null\n const prevLineStart = text.lastIndexOf('\\n', lineStart - 2) + 1\n const parsed = parseListLine(text.slice(prevLineStart, lineStart - 1))\n return parsed ? parsed.indent : null\n}\n\n/**\n * Indents a list item by one level (adds 2 spaces before the prefix), capped at\n * one level deeper than the line above. An item can only nest under a preceding\n * sibling, so the first item of a list — or an item already one level below its\n * parent — cannot indent further (returns null). This keeps sub-items visually\n * connected to a parent instead of drifting arbitrarily deep.\n */\nexport function indentListItem(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n const prevLevel = getPrevListLineLevel(plainText, ctx.lineStart)\n const maxLevel = prevLevel === null ? 0 : prevLevel + 1\n if (ctx.indent >= maxLevel) return null\n\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart, ' ')\n return {\n segments: newSegments,\n cursorOffset: cursorPos + 2,\n }\n}\n\n/**\n * Outdents a list item by one level (removes 2 spaces from before the prefix).\n */\nexport function outdentListItem(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx || ctx.indent === 0) return null\n\n const newSegments = replaceTextRange(segments, ctx.lineStart, ctx.lineStart + 2, '')\n return {\n segments: newSegments,\n cursorOffset: Math.max(ctx.lineStart, cursorPos - 2),\n }\n}\n\n/**\n * Removes the list prefix from the current line (e.g., on Backspace).\n */\nexport function removeListPrefix(\n segments: Segment[],\n cursorPos: number,\n): { segments: Segment[]; cursorOffset: number } | null {\n const plainText = segmentsToPlainText(segments)\n const ctx = getListContext(plainText, cursorPos)\n if (!ctx) return null\n\n if (cursorPos > ctx.contentStart) return null\n\n const newSegments = replaceTextRange(\n segments,\n ctx.lineStart,\n ctx.contentStart,\n ' '.repeat(ctx.indent),\n )\n return {\n segments: newSegments,\n cursorOffset: ctx.lineStart + ctx.indent * 2,\n }\n}\n\n/** A line that opens or closes a fenced code block (```), optionally indented. */\nconst FENCE_LINE = /^\\s*```/\n\n/** Swaps the leading list marker on a single line (\"- \" ↔ \"• \"). */\nfunction swapListPrefixLine(line: string, markdownEnabled: boolean): string {\n return markdownEnabled ? line.replace(/^(\\s*)- /, '$1• ') : line.replace(/^(\\s*)• /, '$1- ')\n}\n\n/**\n * Returns the set of line indices that sit inside a *balanced* fenced code\n * block (a ```…``` pair), so their leading \"- \"/\"• \" markers are preserved\n * verbatim. An unterminated (unpaired) fence marker is NOT protective — its\n * following lines still normalize — so a stray \"```\" in prose does not silently\n * suppress bullet normalization for the rest of the text.\n */\nfunction fenceProtectedLineIndices(lines: string[]): Set<number> {\n const protectedLines = new Set<number>()\n let openIndex = -1\n lines.forEach((line, i) => {\n if (!FENCE_LINE.test(line)) return\n if (openIndex === -1) {\n openIndex = i\n } else {\n for (let k = openIndex; k <= i; k++) protectedLines.add(k)\n openIndex = -1\n }\n })\n return protectedLines\n}\n\n/**\n * Normalizes markdown list prefixes in a raw text string (single source of\n * truth for the bullet-glyph swap, shared by segment normalization and paste):\n * - When markdown is enabled, converts \"- \" at line starts to \"• \"\n * - When markdown is disabled, converts \"• \" at line starts to \"- \"\n *\n * Lines inside a balanced ```…``` block are preserved verbatim.\n */\nexport function normalizeListPrefixText(text: string, markdownEnabled: boolean): string {\n const lines = text.split('\\n')\n const protectedLines = fenceProtectedLineIndices(lines)\n return lines\n .map((line, i) => (protectedLines.has(i) ? line : swapListPrefixLine(line, markdownEnabled)))\n .join('\\n')\n}\n\n/**\n * Normalizes markdown list prefixes across text segments. See\n * {@link normalizeListPrefixText} for the per-line rule. Fence detection spans\n * the whole document (text segments flattened to a global line sequence), so a\n * code block split into per-line text segments on paste still has its \"- \" lines\n * preserved, while an unterminated fence does not suppress later bullets.\n */\nexport function normalizeListPrefixes(segments: Segment[], markdownEnabled: boolean): Segment[] {\n const globalLines: string[] = []\n segments.forEach((seg) => {\n if (seg.type === 'text') globalLines.push(...seg.text.split('\\n'))\n })\n const protectedLines = fenceProtectedLineIndices(globalLines)\n\n let globalIndex = 0\n let changed = false\n const result = segments.map((seg) => {\n if (seg.type !== 'text') return seg\n const newText = seg.text\n .split('\\n')\n .map((line) => {\n const out = protectedLines.has(globalIndex)\n ? line\n : swapListPrefixLine(line, markdownEnabled)\n globalIndex++\n return out\n })\n .join('\\n')\n if (newText === seg.text) return seg\n changed = true\n return { ...seg, text: newText }\n })\n return changed ? result : segments\n}\n\n// ---------------------------------------------------------------------------\n// Ordered-list renumbering\n//\n// The visible number is a projection of position (like BlockNote/ProseMirror/\n// Notion), recomputed on every structural edit rather than trusted as stored\n// text. A per-indent-level counter STACK models nested lists: descending starts\n// a fresh counter at 1, ascending continues the shallower level and clears\n// deeper ones. Every run restarts at 1, so `1. 1. 1.` rebuilds to `1. 2. 3.`\n// and Tab-indenting an item restarts its sublist at 1.\n// ---------------------------------------------------------------------------\n\n/**\n * A single rewritten number's digit run, in the INPUT text's coordinates.\n * `[oldStart, oldEnd)` spans only the digits (never the indentation or `. `).\n */\nexport type NumberEdit = { oldStart: number; oldEnd: number; newText: string }\n\n/**\n * Whether the text holds a genuine ordered-list run worth renumbering — a run\n * of 2+ consecutive same-level numbered lines that either starts at 1 or is\n * already a contiguous `n, n+1, …` sequence. Used to gate the paste path so a\n * copied list fragment (`3. 4. 5.` → renumber, or a broken `1. 1. 1.`) is\n * rebuilt, while incidental numeric-leading prose that `parseListLine` would\n * otherwise treat as a list — `1985. Born / 2020. Died`, `5. / 10. / 15.` — is\n * left untouched.\n */\nexport function hasOrderedListRun(text: string): boolean {\n let runLevel: number | null = null\n let runStart = 0\n let prevNumber = 0\n let runLength = 0\n let sequential = true\n\n for (const line of text.split('\\n')) {\n const parsed = parseListLine(line)\n if (parsed?.kind === 'numbered' && parsed.indent === runLevel) {\n sequential = sequential && parsed.number === prevNumber + 1\n prevNumber = parsed.number\n runLength++\n if (runLength >= 2 && (runStart === 1 || sequential)) return true\n } else if (parsed?.kind === 'numbered') {\n // Start a fresh run at this line's level.\n runLevel = parsed.indent\n runStart = parsed.number\n prevNumber = parsed.number\n runLength = 1\n sequential = true\n } else {\n runLevel = null\n runLength = 0\n }\n }\n\n return false\n}\n\n/**\n * Recomputes ordered-list numbering across the whole text. Returns the new text\n * plus the list of changed digit runs (ascending by `oldStart`) for cursor\n * remapping. When nothing changes, returns the SAME text reference and an empty\n * `edits` array — the no-op guard that keeps this off the typing hot path.\n */\nexport function renumberOrderedListLines(text: string): { text: string; edits: NumberEdit[] } {\n // Cheap pre-gate: with no ordered-list line there is nothing to renumber, so\n // skip the per-line scan and throwaway rebuild. This runs on every structural\n // edit (Enter, Tab, bold/italic wrap) and each paste, most of which never\n // touch a numbered list.\n if (!/^[ \\t]*\\d+\\. /m.test(text)) return { text, edits: [] }\n\n const counters = new Map<number, number>()\n const edits: NumberEdit[] = []\n const lines = text.split('\\n')\n let out = ''\n let lineStart = 0\n\n const clearDeeperThan = (level: number, inclusive: boolean) => {\n for (const key of counters.keys()) {\n if (inclusive ? key >= level : key > level) counters.delete(key)\n }\n }\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const parsed = parseListLine(line)\n\n if (!parsed) {\n // A blank or plain (non-list) line breaks every open list run.\n counters.clear()\n out += line\n } else if (parsed.kind === 'bullet') {\n // A bullet interrupts numbered runs at its level and deeper; a shallower\n // numbered list continues across it.\n clearDeeperThan(parsed.indent, true)\n out += line\n } else {\n const level = parsed.indent\n clearDeeperThan(level, false)\n const current = counters.get(level)\n const n = current === undefined ? 1 : current + 1\n counters.set(level, n)\n\n const newDigits = String(n)\n if (newDigits === String(parsed.number)) {\n out += line\n } else {\n edits.push({\n oldStart: lineStart + parsed.numberStart,\n oldEnd: lineStart + parsed.numberEnd,\n newText: newDigits,\n })\n out += line.slice(0, parsed.numberStart) + newDigits + line.slice(parsed.numberEnd)\n }\n }\n\n if (i < lines.length - 1) out += '\\n'\n lineStart += line.length + 1 // + 1 for the consumed '\\n'\n }\n\n return edits.length === 0 ? { text, edits } : { text: out, edits }\n}\n\n/**\n * Remaps a caret/selection offset (in the renumber INPUT's coordinates) across\n * the digit-run edits. A single scalar delta is wrong: many spans each change\n * width, so the shift depends on how many changed spans lie strictly before the\n * offset, with a clamp when the offset sits inside a resized number.\n */\nexport function remapOffset(old: number, edits: NumberEdit[]): number {\n let shift = 0\n for (const e of edits) {\n if (e.oldEnd <= old) {\n shift += e.newText.length - (e.oldEnd - e.oldStart) // fully before\n } else if (e.oldStart >= old) {\n break // this and every later span is after the offset\n } else {\n return e.oldStart + shift + Math.min(old - e.oldStart, e.newText.length) // inside → clamp\n }\n }\n return old + shift\n}\n\n/**\n * Segment-level renumber used by the edit-commit path. Applies the digit-run\n * edits to the segments (right-to-left so earlier offsets stay valid) and\n * returns the changed spans for {@link remapOffset}. Digit runs are pure text at\n * line starts, so chips are never touched.\n */\nexport function renumberOrderedListSegments(segments: Segment[]): {\n segments: Segment[]\n edits: NumberEdit[]\n} {\n const { edits } = renumberOrderedListLines(segmentsToPlainText(segments))\n if (edits.length === 0) return { segments, edits }\n\n let result = segments\n for (let i = edits.length - 1; i >= 0; i--) {\n const e = edits[i]\n result = replaceTextRange(result, e.oldStart, e.oldEnd, e.newText)\n }\n return { segments: result, edits }\n}\n","/**\n * Type-safe DOM helper functions for PromptArea.\n *\n * These replace all `as` type assertions with proper type guards,\n * following the codebase rule: \"Never use `any` or `as` assertions.\"\n */\n\nimport type { ChipSegment } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Type Guards\n// ---------------------------------------------------------------------------\n\n/**\n * Type guard: checks if a DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return node instanceof HTMLElement\n}\n\n/**\n * Type guard: checks if a DOM node is a chip element\n * (an HTMLElement with data-chip-trigger attribute).\n */\nexport function isChipElement(node: Node): node is HTMLElement {\n return node instanceof HTMLElement && node.dataset.chipTrigger !== undefined\n}\n\n/**\n * Type guard: checks if a DOM node is a BR element.\n */\nexport function isBRElement(node: Node): node is HTMLBRElement {\n return node instanceof HTMLBRElement\n}\n\n/**\n * Type guard: checks if a DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node instanceof Text\n}\n\n/**\n * Checks whether a chip element was auto-resolved (created by pressing space\n * on resolveOnSpace triggers, rather than explicit dropdown selection).\n */\nexport function getChipAutoResolved(node: Node): boolean {\n return isChipElement(node) && node.dataset.chipAutoResolved === 'true'\n}\n\n/**\n * Type guard: checks if a DOM node is a URL link element\n * (an HTMLAnchorElement with data-url attribute).\n */\nexport function isLinkElement(node: Node): node is HTMLAnchorElement {\n return node instanceof HTMLAnchorElement && node.dataset.url === 'true'\n}\n\n// ---------------------------------------------------------------------------\n// Safe JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Safely parses a JSON string, returning `unknown` instead of `any`.\n * Returns `undefined` if parsing fails.\n */\nexport function safeJsonParse(json: string): unknown {\n try {\n // JSON.parse returns `any` by default. We narrow it to `unknown`\n // which is the safest pattern — callers must validate before use.\n const parsed: unknown = JSON.parse(json)\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Safely serializes a value to JSON, returning undefined on failure.\n */\nexport function safeJsonStringify(value: unknown): string | undefined {\n try {\n return JSON.stringify(value)\n } catch {\n return undefined\n }\n}\n\n// ---------------------------------------------------------------------------\n// DOM reading helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Reads the chip trigger character from a chip element's dataset.\n * Returns undefined if the node is not a chip element.\n */\nexport function getChipTrigger(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipTrigger\n}\n\n/**\n * Reads the chip value from a chip element's dataset.\n */\nexport function getChipValue(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipValue\n}\n\n/**\n * Reads the chip display text from a chip element's dataset.\n */\nexport function getChipDisplay(node: Node): string | undefined {\n if (!isChipElement(node)) return undefined\n return node.dataset.chipDisplay ?? node.textContent ?? undefined\n}\n\n/**\n * Reads and safely parses the chip data from a chip element's dataset.\n */\nexport function getChipData(node: Node): unknown {\n if (!isChipElement(node)) return undefined\n const raw = node.dataset.chipData\n if (!raw) return undefined\n return safeJsonParse(raw)\n}\n\n/**\n * Length of a chip's plain-text representation (`trigger + displayText`).\n *\n * This is the single definition used wherever DOM offsets are mapped to the\n * plain-text model (cursor mapping, selection sizing). The fallbacks mirror\n * how chips are rendered: `chipDisplay` is always set, but we degrade to\n * `textContent` for resilience against externally-mutated nodes.\n */\nexport function chipNodeTextLength(node: HTMLElement): number {\n const trigger = node.dataset.chipTrigger ?? ''\n const display = node.dataset.chipDisplay ?? node.textContent ?? ''\n return trigger.length + display.length\n}\n\n/**\n * Reads a chip element into a `ChipSegment`, mirroring how chips are written\n * in `renderSegmentsToDOM`. Returns null when the node is not a chip or is\n * missing a required attribute (trigger, value, or display text).\n *\n * This is the single chip reader shared by the DOM->model sync, chip-click\n * delegation, and clipboard serialization, so they cannot diverge on which\n * fields are required or how optional `data` / `autoResolved` are attached.\n */\nexport function chipNodeToSegment(node: Node): ChipSegment | null {\n if (!isChipElement(node)) return null\n\n const trigger = getChipTrigger(node)\n const value = getChipValue(node)\n const displayText = getChipDisplay(node)\n if (!trigger || value === undefined || !displayText) return null\n\n const data = getChipData(node)\n const autoResolved = getChipAutoResolved(node)\n\n return {\n type: 'chip',\n trigger,\n value,\n displayText,\n ...(data !== undefined ? { data } : {}),\n ...(autoResolved ? { autoResolved: true } : {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// DOM manipulation helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Finds the index of a direct child node within a parent element.\n * Returns -1 if not found.\n */\nexport function indexOfChildNode(parent: HTMLElement, child: Node): number {\n const children = parent.childNodes\n for (let i = 0; i < children.length; i++) {\n if (children[i] === child) return i\n }\n return -1\n}\n\n/**\n * Whether a direct editor child node produces a segment when read by\n * `readSegmentsFromDOM` in use-prompt-area.ts. This is the single predicate\n * shared with `domChildIndexToSegmentIndex` below, so a DOM child index can\n * never map to a different segment index than the one the reader actually\n * produces — decoration elements (the URL `<a>` from `decorateURLsInEditor`,\n * the markdown `<span data-md>` from `decorateMarkdownInEditor`) fall through\n * to the reader's \"unknown element\" branch and DO produce a text segment, so\n * they must count here too, not just chips/text/`<br>`. A chip element only\n * counts if `chipNodeToSegment` would actually accept it — the reader skips a\n * chip missing a required attribute (trigger/value/display), so this must too.\n */\nexport function childProducesSegment(child: Node): boolean {\n if (child.nodeType === Node.TEXT_NODE) return (child.textContent ?? '') !== ''\n if (isBRElement(child)) return !child.dataset.sentinel\n if (isChipElement(child)) return chipNodeToSegment(child) !== null\n if (isHTMLElement(child)) return (child.textContent ?? '') !== ''\n return false\n}\n\n/**\n * Maps the index of a direct child node within the editor to the index of the\n * corresponding segment in the model array, by counting `childProducesSegment`\n * matches up to (but not including) `childIndex`.\n *\n * Keeping this in one place ensures chip-removal, chip-revert, and chip\n * in-place replacement all agree on the exact same mapping rules as the reader.\n */\nexport function domChildIndexToSegmentIndex(editor: HTMLElement, childIndex: number): number {\n let segIdx = 0\n for (let i = 0; i < childIndex; i++) {\n if (childProducesSegment(editor.childNodes[i])) segIdx++\n }\n return segIdx\n}\n\n/**\n * Gets the direct child of `ancestor` that contains `descendant`.\n * Walks up from descendant until we find a node whose parent is ancestor.\n * Returns null if descendant is not inside ancestor.\n */\nexport function getDirectChildContaining(ancestor: HTMLElement, descendant: Node): Node | null {\n let node: Node | null = descendant\n while (node !== null) {\n if (node.parentNode === ancestor) return node\n node = node.parentNode\n }\n return null\n}\n\n/**\n * Unwraps a block element (div, p) by replacing it with its child nodes\n * plus a trailing BR. Used for browser DOM normalization.\n */\nexport function unwrapBlockElement(parent: HTMLElement, block: HTMLElement): void {\n const fragment = document.createDocumentFragment()\n\n // Move all children to fragment\n while (block.firstChild) {\n fragment.appendChild(block.firstChild)\n }\n\n // Add a BR after the unwrapped content\n fragment.appendChild(document.createElement('br'))\n\n parent.replaceChild(fragment, block)\n}\n\n/**\n * Normalizes the editor DOM after browser mutations.\n *\n * Browsers insert various wrapper elements on Enter/paste:\n * - Chrome wraps new lines in <div>\n * - Safari may use <div><br></div>\n * - Some use <p> tags\n *\n * This function unwraps all non-chip block elements, leaving only:\n * - Text nodes\n * - <br> elements\n * - Chip <span> elements (with data-chip-trigger)\n */\nexport function normalizeEditorDOM(editor: HTMLElement): boolean {\n let changed = false\n const blockTags = new Set(['DIV', 'P', 'SECTION', 'ARTICLE', 'BLOCKQUOTE'])\n\n // Iterate backwards since we're modifying the DOM\n for (let i = editor.childNodes.length - 1; i >= 0; i--) {\n const child = editor.childNodes[i]\n\n // Skip non-element nodes, chip elements, and BR elements\n if (!(child instanceof HTMLElement)) continue\n if (child.dataset.chipTrigger !== undefined) continue\n if (child instanceof HTMLBRElement) continue\n\n const tag = child.tagName\n if (blockTags.has(tag)) {\n unwrapBlockElement(editor, child)\n changed = true\n } else if (\n tag === 'FONT' ||\n tag === 'B' ||\n tag === 'I' ||\n tag === 'U' ||\n tag === 'STRONG' ||\n tag === 'EM' ||\n tag === 'A' ||\n tag === 'SPAN'\n ) {\n // Unwrap inline formatting/decoration elements (browser-inserted or markdown decorations)\n const text = child.textContent ?? ''\n if (text) {\n editor.replaceChild(document.createTextNode(text), child)\n } else {\n editor.removeChild(child)\n }\n changed = true\n }\n }\n\n // Merge adjacent text nodes\n editor.normalize()\n\n return changed\n}\n\n// ---------------------------------------------------------------------------\n// URL decoration\n// ---------------------------------------------------------------------------\n\n/** URL pattern for detecting URLs in text content */\nconst URL_PATTERN = /https?:\\/\\/[^\\s),]+/g\n\n/**\n * Walks direct-child text nodes in the editor and wraps URL text in\n * `<a>` elements for visual styling and clickability.\n *\n * This is a DOM-only decoration — it does NOT modify the segment model.\n * The `<a>` elements are stripped by `normalizeEditorDOM` on every input cycle,\n * so they are re-applied fresh each time.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateURLsInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n // Collect text nodes first (avoid modifying while iterating)\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n URL_PATTERN.lastIndex = 0\n const matches: Array<{ url: string; index: number }> = []\n let match: RegExpExecArray | null\n\n while ((match = URL_PATTERN.exec(text)) !== null) {\n // Trim trailing punctuation that's likely not part of the URL\n let url = match[0]\n while (url.length > 0 && /[.;:!?]$/.test(url)) {\n url = url.slice(0, -1)\n }\n if (url.length > 0) {\n matches.push({ url, index: match.index })\n }\n }\n\n if (matches.length === 0) continue\n\n const parent = textNode.parentNode\n if (!parent) continue\n\n // Validate URLs upfront – only keep those with safe protocols (CWE-79)\n const safeMatches: Array<{ url: string; href: string; index: number }> = []\n for (const { url, index } of matches) {\n try {\n const parsed = new URL(url)\n if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {\n safeMatches.push({ url, href: parsed.href, index })\n }\n } catch {\n // skip malformed URLs\n }\n }\n\n if (safeMatches.length === 0) continue\n\n decorated = true\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { url, href, index } of safeMatches) {\n // Text before this URL\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n\n // Create the link element\n const anchor = document.createElement('a')\n anchor.href = href\n anchor.target = '_blank'\n anchor.rel = 'noopener noreferrer'\n anchor.dataset.url = 'true'\n anchor.className = 'text-accent hover:text-accent-hover underline cursor-pointer'\n anchor.textContent = url\n fragment.appendChild(anchor)\n\n lastIndex = index + url.length\n }\n\n // Text after the last URL\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n// ---------------------------------------------------------------------------\n// Markdown inline decoration\n// ---------------------------------------------------------------------------\n\n/** Pattern to find ***bold-italic***, **bold**, and *italic* markdown spans */\nconst MARKDOWN_INLINE_PATTERN = /(\\*{3})(.+?)\\*{3}|(\\*{2})(.+?)\\*{2}|(\\*)(.+?)\\*/g\n\n/**\n * Walks direct-child text nodes in the editor and wraps markdown-formatted\n * text (`**bold**`, `*italic*`, `***bold-italic***`) in styled `<span>` elements.\n *\n * This is a DOM-only decoration — it does NOT modify the segment model.\n * The `<span>` elements are stripped by `normalizeEditorDOM` on every input cycle,\n * so they are re-applied fresh each time.\n *\n * The `*` markers stay visible in the text; only the CSS styling changes.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateMarkdownInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n // Collect text nodes first (avoid modifying while iterating)\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n MARKDOWN_INLINE_PATTERN.lastIndex = 0\n const matches: Array<{\n fullMatch: string\n marker: string\n content: string\n index: number\n className: string\n }> = []\n let match: RegExpExecArray | null\n\n while ((match = MARKDOWN_INLINE_PATTERN.exec(text)) !== null) {\n if (match[1] && match[2]) {\n // ***bold-italic***\n matches.push({\n fullMatch: match[0],\n marker: match[1],\n content: match[2],\n index: match.index,\n className: 'font-bold italic',\n })\n } else if (match[3] && match[4]) {\n // **bold**\n matches.push({\n fullMatch: match[0],\n marker: match[3],\n content: match[4],\n index: match.index,\n className: 'font-bold',\n })\n } else if (match[5] && match[6]) {\n // *italic*\n matches.push({\n fullMatch: match[0],\n marker: match[5],\n content: match[6],\n index: match.index,\n className: 'italic',\n })\n }\n }\n\n if (matches.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { fullMatch, marker, content, index, className } of matches) {\n // Text before this match\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n\n // Parent span — textContent still returns full match (e.g. \"**world**\")\n const span = document.createElement('span')\n span.dataset.md = 'true'\n\n // Opening marker (visually hidden)\n const openMarker = document.createElement('span')\n openMarker.className = 'prompt-area-md-marker'\n openMarker.textContent = marker\n\n // Styled content\n const styledContent = document.createElement('span')\n styledContent.className = className\n styledContent.textContent = content\n\n // Closing marker (visually hidden)\n const closeMarker = document.createElement('span')\n closeMarker.className = 'prompt-area-md-marker'\n closeMarker.textContent = marker\n\n span.appendChild(openMarker)\n span.appendChild(styledContent)\n span.appendChild(closeMarker)\n fragment.appendChild(span)\n\n lastIndex = index + fullMatch.length\n }\n\n // Text after the last match\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/** Matches a `•` bullet glyph at the start of a line (optionally indented). */\nconst LIST_BULLET_PATTERN = /(^|\\n)([ \\t]*)•/g\n\n/**\n * Walks direct-child text nodes and wraps each line-leading `•` bullet glyph in\n * a `<span class=\"prompt-area-list-bullet\">` so CSS can size it up (the raw\n * U+2022 glyph renders much smaller than the surrounding text).\n *\n * Like {@link decorateMarkdownInEditor}, this is a DOM-only decoration: the span\n * is stripped by {@link normalizeEditorDOM} on every input cycle, so the `•`\n * stays a plain character in the segment model and is re-decorated each render.\n *\n * @param editor - The contentEditable root element\n * @returns Whether any decorations were applied\n */\nexport function decorateBulletsInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node) && node.textContent?.includes('•')) {\n textNodes.push(node)\n }\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n LIST_BULLET_PATTERN.lastIndex = 0\n const bulletIndices: number[] = []\n let match: RegExpExecArray | null\n while ((match = LIST_BULLET_PATTERN.exec(text)) !== null) {\n bulletIndices.push(match.index + match[1].length + match[2].length)\n }\n\n if (bulletIndices.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const index of bulletIndices) {\n if (index > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)))\n }\n const span = document.createElement('span')\n span.dataset.md = 'true'\n span.className = 'prompt-area-list-bullet'\n span.textContent = '•'\n fragment.appendChild(span)\n lastIndex = index + 1 // the bullet is a single character\n }\n\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/**\n * Matches the line-leading whitespace run of an indented list line (bullet or\n * numbered). The lookahead keeps the list prefix itself out of the capture, so\n * only the indentation is wrapped.\n */\nconst LIST_INDENT_PATTERN = /(^|\\n)([ \\t]+)(?=(?:[•\\-*] |\\d+\\. ))/g\n\n/**\n * Wraps each list line's leading indentation in an inline-block\n * `<span class=\"prompt-area-list-indent\">` sized per nesting level, so nested\n * items read with a wide, Notion-like indent instead of the raw 2-space gap.\n *\n * Like the other decorations this is display-only: the span keeps the original\n * whitespace as its textContent (so plain-text length and caret offsets are\n * unchanged) and is stripped by {@link normalizeEditorDOM} each input cycle.\n * Must run BEFORE the node-splitting passes ({@link decorateURLsInEditor},\n * {@link decorateMarkdownInEditor}, {@link decorateBulletsInEditor}) so every\n * direct-child text node is still a whole line — otherwise a mid-line split\n * fragment beginning with whitespace would let the `^` anchor false-match\n * non-line-leading whitespace.\n *\n * @returns Whether any decorations were applied\n */\nexport function decorateListIndentInEditor(editor: HTMLElement): boolean {\n let decorated = false\n\n const textNodes: Text[] = []\n for (let i = 0; i < editor.childNodes.length; i++) {\n const node = editor.childNodes[i]\n if (isTextNode(node)) textNodes.push(node)\n }\n\n for (const textNode of textNodes) {\n const text = textNode.textContent ?? ''\n LIST_INDENT_PATTERN.lastIndex = 0\n const runs: { start: number; end: number }[] = []\n let match: RegExpExecArray | null\n while ((match = LIST_INDENT_PATTERN.exec(text)) !== null) {\n const start = match.index + match[1].length\n runs.push({ start, end: start + match[2].length })\n }\n\n if (runs.length === 0) continue\n\n decorated = true\n const parent = textNode.parentNode\n if (!parent) continue\n\n const fragment = document.createDocumentFragment()\n let lastIndex = 0\n\n for (const { start, end } of runs) {\n if (start > lastIndex) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)))\n }\n const whitespace = text.slice(start, end)\n const level = Math.floor(whitespace.length / 2)\n const span = document.createElement('span')\n span.dataset.md = 'true'\n span.className = 'prompt-area-list-indent'\n span.style.width = `calc(var(--prompt-area-indent-size, 1.5em) * ${level})`\n span.textContent = whitespace\n fragment.appendChild(span)\n lastIndex = end\n }\n\n if (lastIndex < text.length) {\n fragment.appendChild(document.createTextNode(text.slice(lastIndex)))\n }\n\n parent.replaceChild(fragment, textNode)\n }\n\n return decorated\n}\n\n/**\n * Applies every display-only decoration to the editor in one pass: URL links\n * always, plus markdown emphasis, list indentation, and list bullets when\n * markdown mode is on. Each decoration is stripped by {@link normalizeEditorDOM}\n * on the next input cycle and re-applied here, so the segment model is never\n * mutated.\n *\n * List indentation runs FIRST, while each direct-child text node is still a\n * whole line: the URL and markdown passes split text nodes mid-line, and a tail\n * fragment starting with whitespace would let the indent regex's `^` anchor\n * false-match non-line-leading whitespace (e.g. `see http://x 1. y`).\n */\nexport function decorateEditor(editor: HTMLElement, markdownEnabled: boolean): void {\n // Whole-line passes run FIRST, while each direct-child text node is still a\n // full line. The URL and markdown passes split text nodes mid-line, so a tail\n // fragment beginning with \"•\" would let the bullet regex's `^` anchor\n // false-match a mid-line separator (e.g. `**bold** • middle`).\n if (markdownEnabled) {\n decorateListIndentInEditor(editor)\n decorateBulletsInEditor(editor)\n }\n decorateURLsInEditor(editor)\n if (markdownEnabled) decorateMarkdownInEditor(editor)\n}\n\n// ---------------------------------------------------------------------------\n// Selection helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the first Range from the current window selection, or null if\n * there is no selection or it has no ranges.\n */\nexport function getSelectionRange(): Range | null {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n return sel.getRangeAt(0)\n}\n","/**\n * Cursor/selection utilities for the PromptArea contentEditable.\n *\n * Invariants:\n * - All functions are synchronous. Never return a promise or cross a microtask\n * boundary after a DOM mutation — that can cause the browser to fire\n * `selectionchange` and reset the caret.\n * - Never cache a `Selection` or `Range` across calls. Ranges become detached\n * after DOM mutations. Each function calls `window.getSelection()` or\n * `getSelectionRange()` fresh.\n * - Chip nodes are treated atomically via `isChipElement` — we never descend\n * into a contentEditable=false subtree when mapping offsets.\n */\nimport {\n chipNodeTextLength,\n getDirectChildContaining,\n getSelectionRange,\n indexOfChildNode,\n isBRElement,\n isChipElement,\n isHTMLElement,\n} from './dom-helpers.ts'\n\nexport type SavedCursor = {\n nodeIndex: number\n offset: number\n}\n\nexport function saveCursorPosition(editor: HTMLElement): SavedCursor | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const node = range.startContainer\n if (node === editor) {\n return { nodeIndex: range.startOffset, offset: 0 }\n }\n\n // Walk up to find the direct child of editor using type-safe helper\n const directChild = getDirectChildContaining(editor, node)\n if (!directChild) return null\n\n const nodeIndex = indexOfChildNode(editor, directChild)\n return { nodeIndex, offset: range.startOffset }\n}\n\nexport function restoreCursorPosition(editor: HTMLElement, saved: SavedCursor): void {\n const sel = window.getSelection()\n if (!sel) return\n\n const childNodes = editor.childNodes\n if (childNodes.length === 0) return\n\n const range = document.createRange()\n\n if (saved.nodeIndex >= childNodes.length) {\n const lastChild = childNodes[childNodes.length - 1]\n if (lastChild.nodeType === Node.TEXT_NODE) {\n range.setStart(lastChild, (lastChild.textContent ?? '').length)\n } else {\n range.setStartAfter(lastChild)\n }\n } else {\n const targetNode = childNodes[saved.nodeIndex]\n if (targetNode.nodeType === Node.TEXT_NODE) {\n const maxOffset = (targetNode.textContent ?? '').length\n range.setStart(targetNode, Math.min(saved.offset, maxOffset))\n } else {\n range.setStartAfter(targetNode)\n }\n }\n\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\nexport function getCursorOffset(editor: HTMLElement): number | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const preRange = document.createRange()\n preRange.selectNodeContents(editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n\n return getTextLengthInRange(preRange)\n}\n\n/**\n * Create a collapsed Range at the given plain-text offset inside the editor.\n * Returns null if the offset can't be mapped to a DOM position.\n */\nexport function createRangeAtOffset(editor: HTMLElement, targetOffset: number): Range | null {\n const pos = findDOMPosition(editor, targetOffset)\n if (!pos) return null\n\n const range = document.createRange()\n range.setStart(pos.node, pos.offset)\n range.collapse(true)\n return range\n}\n\nexport function setCursorAtOffset(editor: HTMLElement, targetOffset: number): void {\n const sel = window.getSelection()\n if (!sel) return\n\n const pos = findDOMPosition(editor, targetOffset)\n if (pos) {\n const range = document.createRange()\n range.setStart(pos.node, pos.offset)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n return\n }\n\n // Fallback: place cursor at end\n const range = document.createRange()\n range.selectNodeContents(editor)\n range.collapse(false)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\nexport function getTextLengthInRange(range: Range): number {\n const fragment = range.cloneContents()\n let length = 0\n\n const walk = (node: Node): void => {\n if (node.nodeType === Node.TEXT_NODE) {\n length += (node.textContent ?? '').length\n } else if (isChipElement(node)) {\n length += chipNodeTextLength(node)\n } else if (isHTMLElement(node) && node.tagName === 'BR') {\n if (node.dataset.sentinel) return // skip sentinel <br>\n length += 1\n } else if (isHTMLElement(node)) {\n node.childNodes.forEach(walk)\n }\n }\n\n fragment.childNodes.forEach(walk)\n return length\n}\n\n/**\n * Returns the start and end plain-text offsets of the current selection.\n * Returns null if there's no selection or it's outside the editor.\n */\nexport function getSelectionOffsets(editor: HTMLElement): { start: number; end: number } | null {\n const range = getSelectionRange()\n if (!range) return null\n if (!editor.contains(range.startContainer)) return null\n\n const startRange = document.createRange()\n startRange.selectNodeContents(editor)\n startRange.setEnd(range.startContainer, range.startOffset)\n const start = getTextLengthInRange(startRange)\n\n if (range.collapsed) return { start, end: start }\n\n const endRange = document.createRange()\n endRange.selectNodeContents(editor)\n endRange.setEnd(range.endContainer, range.endOffset)\n const end = getTextLengthInRange(endRange)\n\n return { start, end }\n}\n\n/**\n * Sets a (potentially non-collapsed) selection at the given plain-text offsets.\n * Used to restore selection after markdown wrap/unwrap operations.\n */\nexport function setSelectionAtOffsets(\n editor: HTMLElement,\n startOffset: number,\n endOffset: number,\n): void {\n const sel = window.getSelection()\n if (!sel) return\n\n if (startOffset === endOffset) {\n setCursorAtOffset(editor, startOffset)\n return\n }\n\n const startPos = findDOMPosition(editor, startOffset)\n const endPos = findDOMPosition(editor, endOffset)\n if (!startPos || !endPos) return\n\n const range = document.createRange()\n range.setStart(startPos.node, startPos.offset)\n range.setEnd(endPos.node, endPos.offset)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\n/**\n * Maps a plain-text offset to a DOM node + offset pair.\n * Recurses into decoration elements (markdown spans, URL anchors).\n */\nexport function findDOMPosition(\n container: HTMLElement,\n targetOffset: number,\n): { node: Node; offset: number } | null {\n let remaining = targetOffset\n\n for (let i = 0; i < container.childNodes.length; i++) {\n const child = container.childNodes[i]\n\n if (child.nodeType === Node.TEXT_NODE) {\n const len = (child.textContent ?? '').length\n if (remaining <= len) {\n return { node: child, offset: remaining }\n }\n remaining -= len\n } else if (isChipElement(child)) {\n const chipLen = chipNodeTextLength(child)\n if (remaining <= chipLen) {\n // Position after the chip element\n return { node: container, offset: i + 1 }\n }\n remaining -= chipLen\n } else if (isBRElement(child)) {\n if (child.dataset.sentinel) continue // skip sentinel <br>\n if (remaining <= 1) {\n return { node: container, offset: i + 1 }\n }\n remaining -= 1\n } else if (isHTMLElement(child)) {\n // Decoration element (markdown span, URL anchor) — recurse\n const textLen = (child.textContent ?? '').length\n if (remaining <= textLen) {\n const result = findDOMPosition(child, remaining)\n if (result) return result\n }\n remaining -= textLen\n }\n }\n\n // Fallback: end of container\n return { node: container, offset: container.childNodes.length }\n}\n","/**\n * Clipboard-related DOM utilities for the PromptArea component.\n * Handles serializing selections and inserting pasted segments at the cursor.\n *\n * Not merged into dom-helpers.ts to avoid overcrowding that file with\n * clipboard I/O concerns alongside its DOM traversal and chip-accessor helpers.\n */\nimport type { Segment, ChipSegment } from './types.ts'\nimport {\n chipNodeToSegment,\n getChipDisplay,\n getChipTrigger,\n getSelectionRange,\n isChipElement,\n isHTMLElement,\n} from './dom-helpers.ts'\nimport { mergeAdjacentTextSegments } from './prompt-area-engine.ts'\nimport { getTextLengthInRange } from './cursor-helpers.ts'\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Visitor callbacks for {@link walkFragmentNodes}. Each fires for the\n * corresponding node kind encountered during a depth-first walk.\n */\ntype FragmentVisitor = {\n /** A text node — receives its text content (may be empty). */\n onText: (text: string) => void\n /** A chip element (has `data-chip-trigger`). */\n onChip: (node: HTMLElement) => void\n /** A `<br>` line break. */\n onBreak: () => void\n}\n\n/**\n * Depth-first walk of a selection fragment, classifying each node as text,\n * chip, or line break and recursing into any other element (decoration spans,\n * anchors, browser-inserted wrappers).\n *\n * Both fragment serializers share this single traversal so they cannot drift\n * on which nodes count as chips/breaks or how nested decorations are unwrapped.\n */\nfunction walkFragmentNodes(fragment: DocumentFragment, visitor: FragmentVisitor): void {\n const walk = (node: Node): void => {\n if (node.nodeType === Node.TEXT_NODE) {\n visitor.onText(node.textContent ?? '')\n } else if (isChipElement(node)) {\n visitor.onChip(node)\n } else if (isHTMLElement(node) && node.tagName === 'BR') {\n visitor.onBreak()\n } else {\n node.childNodes.forEach(walk)\n }\n }\n\n fragment.childNodes.forEach(walk)\n}\n\n/**\n * Serializes a DocumentFragment (from selection) to plain text,\n * converting chip elements to their `trigger + displayText` form.\n */\nexport function serializeFragmentToPlainText(fragment: DocumentFragment): string {\n let text = ''\n\n walkFragmentNodes(fragment, {\n onText: (value) => {\n text += value\n },\n onChip: (node) => {\n text += (getChipTrigger(node) ?? '') + (getChipDisplay(node) ?? '')\n },\n onBreak: () => {\n text += '\\n'\n },\n })\n\n return text\n}\n\n/**\n * Serializes a DocumentFragment to an array of Segment objects,\n * preserving chip data for internal copy/paste.\n */\nexport function serializeFragmentToSegments(fragment: DocumentFragment): Segment[] {\n const segments: Segment[] = []\n\n walkFragmentNodes(fragment, {\n onText: (value) => {\n if (value) segments.push({ type: 'text', text: value })\n },\n onChip: (node) => {\n const chip = chipNodeToSegment(node)\n if (chip) segments.push(chip)\n },\n onBreak: () => {\n segments.push({ type: 'text', text: '\\n' })\n },\n })\n\n return segments\n}\n\n/**\n * Parses segment JSON from the clipboard. Returns null if invalid.\n */\nexport function parseSegmentsFromClipboard(json: string): Segment[] | null {\n try {\n const parsed: unknown = JSON.parse(json)\n if (!Array.isArray(parsed)) return null\n\n const segments: Segment[] = []\n for (const item of parsed) {\n if (!isRecord(item)) return null\n\n if (item.type === 'text' && typeof item.text === 'string') {\n segments.push({ type: 'text', text: item.text })\n } else if (\n item.type === 'chip' &&\n typeof item.trigger === 'string' &&\n typeof item.value === 'string' &&\n typeof item.displayText === 'string'\n ) {\n const chip: ChipSegment = {\n type: 'chip',\n trigger: item.trigger,\n value: item.value,\n displayText: item.displayText,\n ...(item.data !== undefined ? { data: item.data } : {}),\n ...(item.autoResolved ? { autoResolved: true } : {}),\n }\n segments.push(chip)\n } else {\n return null\n }\n }\n\n return segments\n } catch {\n return null\n }\n}\n\n/**\n * Inserts pasted segments at the current cursor position within existing segments.\n * Splits any text segment that straddles the cursor so the pasted content lands\n * exactly at the cursor and nothing before or after is lost.\n */\nexport function insertSegmentsAtCursor(\n currentSegments: Segment[],\n pastedSegments: Segment[],\n editor: HTMLElement,\n): Segment[] {\n const range = getSelectionRange()\n if (!range) return [...currentSegments, ...pastedSegments]\n\n const preRange = document.createRange()\n preRange.selectNodeContents(editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n const cursorOffset = getTextLengthInRange(preRange)\n\n const result: Segment[] = []\n let offset = 0\n let inserted = false\n\n const insertOnce = (): void => {\n if (!inserted) {\n result.push(...pastedSegments)\n inserted = true\n }\n }\n\n for (const seg of currentSegments) {\n if (seg.type === 'chip') {\n const chipLen = seg.trigger.length + seg.displayText.length\n if (offset >= cursorOffset) insertOnce()\n result.push(seg)\n offset += chipLen\n continue\n }\n\n const segEnd = offset + seg.text.length\n if (segEnd <= cursorOffset) {\n // Entirely before the cursor\n result.push(seg)\n } else if (offset >= cursorOffset) {\n // Entirely after the cursor\n insertOnce()\n result.push(seg)\n } else {\n // Cursor falls inside this text segment — split it.\n const splitAt = cursorOffset - offset\n const before = seg.text.slice(0, splitAt)\n const after = seg.text.slice(splitAt)\n if (before) result.push({ type: 'text', text: before })\n insertOnce()\n if (after) result.push({ type: 'text', text: after })\n }\n offset = segEnd\n }\n\n insertOnce()\n return mergeAdjacentTextSegments(result)\n}\n","/**\n * Hand-rolled, dependency-free HTML -> Markdown converter.\n *\n * Used by the paste handler: when the editor is in markdown mode and the\n * clipboard carries rich `text/html` (web pages, Notion, Google Docs, GitHub,\n * Slack, etc.), we convert it to markdown SOURCE text so the paste keeps its\n * formatting. The resulting string flows through the same insertion path as a\n * plain-text paste, and the editor's inline decorators render `*`/`**`/`***`\n * and bare URLs automatically.\n *\n * Design constraints (see .size-limit.json): no runtime deps. Parsing uses the\n * ambient `DOMParser`, walking is a small recursive switch. Type-safe: no\n * `any`, DOM narrowed via the guards in `dom-helpers.ts`.\n */\nimport { isHTMLElement, isTextNode } from './dom-helpers.ts'\n\n// ---------------------------------------------------------------------------\n// Inline style / emphasis detection\n// ---------------------------------------------------------------------------\n\n/** Reads a single declaration value from an inline `style` attribute string. */\nfunction getStyleValue(style: string, prop: string): string {\n const match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style)\n return match ? match[1].trim().toLowerCase() : ''\n}\n\n/** Whether a CSS font-weight value is bold (`bold`, `bolder`, or >= 600). */\nfunction isBoldWeight(value: string): boolean {\n if (value === 'bold' || value === 'bolder') return true\n const numeric = Number.parseInt(value, 10)\n return !Number.isNaN(numeric) && numeric >= 600\n}\n\n/**\n * Computes the markdown emphasis markers for an element from BOTH its tag and\n * its inline style. Google Docs emits `<span style=\"font-weight:700\">` rather\n * than `<b>`, and wraps everything in `<b style=\"font-weight:normal\">`, so an\n * explicit `font-weight`/`font-style` always wins over the tag name.\n */\nfunction inlineEmphasis(node: HTMLElement): { prefix: string; suffix: string } {\n const tag = node.tagName\n const style = node.getAttribute('style') ?? ''\n\n const weight = getStyleValue(style, 'font-weight')\n const bold = weight ? isBoldWeight(weight) : tag === 'B' || tag === 'STRONG'\n\n const fontStyle = getStyleValue(style, 'font-style')\n const italic = fontStyle\n ? fontStyle === 'italic' || fontStyle === 'oblique'\n : tag === 'I' || tag === 'EM'\n\n const decoration = getStyleValue(style, 'text-decoration')\n const strike =\n tag === 'S' || tag === 'DEL' || tag === 'STRIKE' || decoration.includes('line-through')\n\n const prefix = (strike ? '~~' : '') + (bold ? '**' : '') + (italic ? '*' : '')\n const suffix = (italic ? '*' : '') + (bold ? '**' : '') + (strike ? '~~' : '')\n return { prefix, suffix }\n}\n\n// ---------------------------------------------------------------------------\n// Text handling\n// ---------------------------------------------------------------------------\n\n/** Collapses HTML whitespace runs (incl. `&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): continue a list, else submit when\n // `submitOnEnter` is set, else insert a newline.\n if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {\n const editor = editorRef.current\n if (editor && tryListContinuation(editor)) {\n e.preventDefault()\n return\n }\n if (submitOnEnter) {\n e.preventDefault()\n onSubmit?.(readSegmentsFromDOM())\n return\n }\n e.preventDefault()\n if (editor) insertPlainNewline(editor)\n return\n }\n\n // 4. Escape\n if (e.key === 'Escape' && onEscape) {\n onEscape()\n return\n }\n\n // 4.5 Non-collapsed selection delete (Backspace/Delete across <a> boundaries)\n if ((e.key === 'Backspace' || e.key === 'Delete') && !e.nativeEvent.isComposing) {\n const editor = editorRef.current\n if (editor) {\n const offsets = getSelectionOffsets(editor)\n if (offsets && offsets.start !== offsets.end) {\n e.preventDefault()\n const currentSegments = readSegmentsFromDOM()\n events.pushUndo(currentSegments)\n const newSegments = replaceTextRange(currentSegments, offsets.start, offsets.end, '')\n applyEditResult(editor, { segments: newSegments, cursorOffset: offsets.start })\n runTriggerDetection()\n return\n }\n }\n }\n\n // 5. Backspace: check list prefix removal, then chip deletion\n if (e.key === 'Backspace') {\n const editor = editorRef.current\n if (editor) {\n const segments = readSegmentsFromDOM()\n const cursorPos = getCursorOffset(editor)\n if (markdownEnabled && cursorPos !== null) {\n const result = removeListPrefix(segments, cursorPos)\n if (result) {\n e.preventDefault()\n applyEditResult(editor, result)\n runTriggerDetection()\n return\n }\n }\n }\n if (handleChipBackspace()) {\n e.preventDefault()\n runTriggerDetection()\n return\n }\n }\n\n // 6. Delete (forward): delete chip as whole unit\n if (e.key === 'Delete' && handleChipForwardDelete()) {\n e.preventDefault()\n runTriggerDetection()\n return\n }\n },\n [\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n onSubmit,\n submitOnEnter,\n onEscape,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n markdownEnabled,\n dismissTrigger,\n handleChipBackspace,\n handleChipForwardDelete,\n autoResolveActiveTrigger,\n runTriggerDetection,\n selectSuggestionInternal,\n events,\n triggers,\n buildInsertChip,\n ],\n )\n\n // -----------------------------------------------------------------------\n // Imperative handle (memoized to avoid identity changes)\n // -----------------------------------------------------------------------\n\n const handle: PromptAreaHandle = useMemo(\n () => ({\n focus: () => editorRef.current?.focus(),\n blur: () => editorRef.current?.blur(),\n insertChip: (chip) => {\n const segments = readSegmentsFromDOM()\n const newChip: ChipSegment = { type: 'chip', ...chip }\n const newSegments: Segment[] = [...segments, newChip, { type: 'text', text: ' ' }]\n onChange(newSegments)\n renderSegmentsToDOM(newSegments)\n onChipAdd?.(newChip)\n },\n getPlainText: () => segmentsToPlainText(readSegmentsFromDOM()),\n clear: () => {\n onChange([])\n const editor = editorRef.current\n if (editor) {\n while (editor.firstChild) editor.removeChild(editor.firstChild)\n }\n events.resetUndoHistory()\n if (undoTimer.current) {\n clearTimeout(undoTimer.current)\n undoTimer.current = null\n }\n undoBaseState.current = null\n },\n setText: (text) => {\n events.pushUndo(readSegmentsFromDOM())\n const segments = plainTextToSegments(text)\n onChange(segments)\n renderSegmentsToDOM(segments)\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, text.length)\n },\n appendText: (text) => {\n const segments = readSegmentsFromDOM()\n events.pushUndo(segments)\n // Merge into the trailing text segment so the onChange value doesn't\n // carry two adjacent un-merged text segments.\n const last = segments[segments.length - 1]\n const next: Segment[] =\n last?.type === 'text'\n ? [...segments.slice(0, -1), { type: 'text', text: last.text + text }]\n : [...segments, { type: 'text', text }]\n onChange(next)\n renderSegmentsToDOM(next)\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, segmentsToPlainText(next).length)\n },\n getCursorPosition: () => {\n const editor = editorRef.current\n return editor ? getCursorOffset(editor) : null\n },\n setCursorPosition: (offset) => {\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, offset)\n },\n setCursorToEnd: () => {\n const editor = editorRef.current\n if (editor) setCursorAtOffset(editor, segmentsToPlainText(readSegmentsFromDOM()).length)\n },\n getSelection: () => {\n const editor = editorRef.current\n return editor ? getSelectionOffsets(editor) : null\n },\n setSelection: (start, end) => {\n const editor = editorRef.current\n if (editor) setSelectionAtOffsets(editor, start, end)\n },\n }),\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, onChipAdd, events],\n )\n\n // -----------------------------------------------------------------------\n // Compose event handlers\n // -----------------------------------------------------------------------\n\n const eventHandlers = useMemo(\n () => ({\n onPaste: events.handlePaste,\n onCopy: events.handleCopy,\n onCut: events.handleCut,\n onDrop: events.handleDrop,\n onDragOver: events.handleDragOver,\n onCompositionStart: events.handleCompositionStart,\n onCompositionEnd: events.handleCompositionEnd,\n onBlur: events.handleBlur,\n }),\n [\n events.handlePaste,\n events.handleCopy,\n events.handleCut,\n events.handleDrop,\n events.handleDragOver,\n events.handleCompositionStart,\n events.handleCompositionEnd,\n events.handleBlur,\n ],\n )\n\n return {\n editorRef,\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n handleInput,\n handleKeyDown,\n handleClick,\n handleMouseDown,\n selectSuggestion,\n dismissTrigger,\n handle,\n triggerRect,\n eventHandlers,\n }\n}\n","'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport type { TriggerSuggestion } from './types.ts'\n\ntype TriggerPopoverProps = {\n suggestions: TriggerSuggestion[]\n loading: boolean\n error?: string | null\n emptyMessage?: string\n selectedIndex: number\n onSelect: (suggestion: TriggerSuggestion) => void\n onDismiss: () => void\n triggerRect: DOMRect | null\n triggerChar: string\n}\n\n// Single source of truth for the popover height: drives both the flip-above\n// calculation and the maxHeight style.\nconst POPOVER_MAX_HEIGHT = 240\n\n/**\n * Floating popover that displays trigger suggestions.\n * Positioned relative to the trigger character location in the editor.\n */\nexport function TriggerPopover({\n suggestions,\n loading,\n error,\n emptyMessage,\n selectedIndex,\n onSelect,\n onDismiss,\n triggerRect,\n triggerChar,\n}: TriggerPopoverProps) {\n const popoverRef = useRef<HTMLDivElement>(null)\n const selectedRef = useRef<HTMLButtonElement>(null)\n\n // Scroll selected item into view\n useEffect(() => {\n selectedRef.current?.scrollIntoView({ block: 'nearest' })\n }, [selectedIndex])\n\n // Click outside to dismiss\n useEffect(() => {\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target\n if (popoverRef.current && target instanceof Node && !popoverRef.current.contains(target)) {\n onDismiss()\n }\n }\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [onDismiss])\n\n if (!triggerRect) return null\n if (suggestions.length === 0 && !loading && !error && !emptyMessage) return null\n\n // Position the popover relative to the trigger character, clamped to the\n // viewport. Flip above the trigger when there isn't enough room below, so the\n // suggestion list stays on-screen near the bottom edge.\n const popoverMaxWidth = Math.min(320, window.innerWidth - 16)\n const left = Math.min(triggerRect.left, window.innerWidth - popoverMaxWidth - 8)\n const spaceBelow = window.innerHeight - triggerRect.bottom\n const positionAbove = spaceBelow < POPOVER_MAX_HEIGHT && triggerRect.top > spaceBelow\n const style: React.CSSProperties = {\n position: 'fixed',\n left: `${Math.max(8, left)}px`,\n zIndex: 50,\n maxWidth: `${popoverMaxWidth}px`,\n maxHeight: `${POPOVER_MAX_HEIGHT}px`,\n ...(positionAbove\n ? { bottom: `${window.innerHeight - triggerRect.top + 4}px` }\n : { top: `${triggerRect.bottom + 4}px` }),\n }\n\n return (\n <div\n ref={popoverRef}\n className={cn(\n 'min-w-[200px] overflow-y-auto',\n 'bg-surface rounded-xl border p-2 shadow-md',\n 'animate-in fade-in-0 zoom-in-95',\n )}\n style={style}\n role=\"listbox\"\n aria-label={`${triggerChar} suggestions`}>\n {loading ? (\n <div\n role=\"option\"\n aria-selected={false}\n className=\"text-muted-foreground px-3 py-2 text-sm\">\n Loading suggestions...\n </div>\n ) : error ? (\n <div role=\"option\" aria-selected={false} className=\"text-destructive px-3 py-2 text-sm\">\n {error}\n </div>\n ) : suggestions.length === 0 && emptyMessage ? (\n <div\n role=\"option\"\n aria-selected={false}\n className=\"text-muted-foreground px-3 py-2 text-sm\">\n {emptyMessage}\n </div>\n ) : (\n suggestions.map((suggestion, index) => (\n <button\n key={suggestion.value}\n ref={index === selectedIndex ? selectedRef : undefined}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={cn(\n 'text-foreground flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm',\n 'hover:bg-surface-hover cursor-pointer transition-colors',\n index === selectedIndex && 'bg-surface-hover',\n )}\n onMouseDown={(e) => {\n e.preventDefault() // Prevent blur on the editor\n onSelect(suggestion)\n }}>\n {suggestion.icon && <span className=\"mt-0.5 shrink-0\">{suggestion.icon}</span>}\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate font-medium\">{suggestion.label}</div>\n {suggestion.description && (\n <div className=\"text-muted-foreground truncate text-xs\">\n {suggestion.description}\n </div>\n )}\n </div>\n </button>\n ))\n )}\n </div>\n )\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\ntype AnimatedPlaceholderProps = {\n texts: string[]\n interval?: number\n}\n\n/**\n * Cross-fading placeholder that rotates through `texts`.\n *\n * Each text is keyed so React remounts it on change, replaying the\n * `tw-animate-css` enter animation (slide down + fade in). No animation\n * library is required.\n */\nexport function AnimatedPlaceholder({ texts, interval = 3000 }: AnimatedPlaceholderProps) {\n const [index, setIndex] = useState(0)\n\n useEffect(() => {\n if (texts.length <= 1) return\n\n const id = setInterval(() => {\n setIndex((prev) => (prev + 1) % texts.length)\n }, interval)\n\n return () => clearInterval(id)\n }, [texts.length, interval])\n\n return (\n <div\n className=\"pointer-events-none absolute top-0 left-0 overflow-hidden select-none\"\n style={{ color: 'var(--prompt-area-placeholder, var(--muted-foreground))' }}\n aria-hidden=\"true\">\n <div\n key={index}\n className=\"animate-in fade-in-0 slide-in-from-top-4 duration-300 ease-in-out\">\n {texts[index]}\n </div>\n </div>\n )\n}\n","import { cn } from '../../lib/utils.ts'\n\ntype RemoveButtonProps = {\n onClick: () => void\n label: string\n className?: string\n}\n\nexport function RemoveButton({ onClick, label, className }: RemoveButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onClick()\n }}\n className={cn(\n 'absolute top-0.5 right-0.5 grid h-3.5 w-3.5 cursor-pointer place-items-center',\n 'rounded-full bg-black/60 text-white hover:bg-black/80 dark:bg-white/60 dark:text-black dark:hover:bg-white/80',\n 'transition-colors',\n className,\n )}\n aria-label={label}>\n <svg\n width=\"8\"\n height=\"8\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\">\n <line x1=\"2.75\" y1=\"2.75\" x2=\"7.25\" y2=\"7.25\" />\n <line x1=\"7.25\" y1=\"2.75\" x2=\"2.75\" y2=\"7.25\" />\n </svg>\n </button>\n )\n}\n","'use client'\n\nimport { cn } from '../../lib/utils.ts'\nimport { RemoveButton } from './remove-button.tsx'\nimport type { PromptAreaImage } from './types.ts'\n\ntype ImageStripProps = {\n images: PromptAreaImage[]\n onRemove?: (image: PromptAreaImage) => void\n onClick?: (image: PromptAreaImage) => void\n className?: string\n}\n\nexport function ImageStrip({ images, onRemove, onClick, className }: ImageStripProps) {\n if (images.length === 0) return null\n\n return (\n <div className={cn('flex flex-wrap gap-2', className)} role=\"list\" aria-label=\"Attached images\">\n {images.map((image) => (\n <div\n key={image.id}\n role=\"listitem\"\n className={cn(\n 'border-border relative h-16 w-16 flex-shrink-0 overflow-hidden rounded-md border',\n onClick && 'cursor-pointer',\n )}\n onClick={() => onClick?.(image)}>\n {/* eslint-disable-next-line @next/next/no-img-element -- registry component used outside Next.js */}\n <img\n src={image.url}\n alt={image.alt ?? 'Attached image'}\n className=\"h-full w-full object-cover\"\n />\n {image.loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/40\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent\" />\n </div>\n )}\n {onRemove && (\n <RemoveButton\n onClick={() => onRemove(image)}\n label={`Remove ${image.alt ?? 'image'}`}\n />\n )}\n </div>\n ))}\n </div>\n )\n}\n","'use client'\n\nimport { useEffect, useRef, useState } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport { RemoveButton } from './remove-button.tsx'\nimport type { PromptAreaFile } from './types.ts'\n\ntype IconProps = { className?: string }\n\n/** Shared SVG wrapper matching the lucide icon defaults (no dependency). */\nfunction Svg({ className, children }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={className}>\n {children}\n </svg>\n )\n}\n\nconst FileBody = (\n <>\n <path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\" />\n <path d=\"M14 2v5a1 1 0 0 0 1 1h5\" />\n </>\n)\n\nconst File = ({ className }: IconProps) => <Svg className={className}>{FileBody}</Svg>\nconst FileText = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M10 9H8\" />\n <path d=\"M16 13H8\" />\n <path d=\"M16 17H8\" />\n </Svg>\n)\nconst FileSpreadsheet = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M8 13h2\" />\n <path d=\"M14 13h2\" />\n <path d=\"M8 17h2\" />\n <path d=\"M14 17h2\" />\n </Svg>\n)\nconst FileCode = ({ className }: IconProps) => (\n <Svg className={className}>\n {FileBody}\n <path d=\"M10 12.5 8 15l2 2.5\" />\n <path d=\"m14 12.5 2 2.5-2 2.5\" />\n </Svg>\n)\nconst ImageIcon = ({ className }: IconProps) => (\n <Svg className={className}>\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n <circle cx=\"9\" cy=\"9\" r=\"2\" />\n <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />\n </Svg>\n)\n\ntype FileStripProps = {\n files: PromptAreaFile[]\n onRemove?: (file: PromptAreaFile) => void\n onClick?: (file: PromptAreaFile) => void\n className?: string\n}\n\n/** Threshold above which collapse activates automatically. */\nconst COLLAPSE_THRESHOLD = 3\n\n/** Pick a lucide icon key based on MIME type. */\nfunction getFileIconKey(type?: string): 'pdf' | 'spreadsheet' | 'code' | 'image' | 'default' {\n if (!type) return 'default'\n if (type === 'application/pdf') return 'pdf'\n if (type.includes('spreadsheet') || type === 'text/csv') return 'spreadsheet'\n if (\n type.startsWith('text/') ||\n type.includes('javascript') ||\n type.includes('json') ||\n type.includes('xml')\n )\n return 'code'\n if (type.startsWith('image/')) return 'image'\n return 'default'\n}\n\nconst FILE_ICONS = {\n pdf: FileText,\n spreadsheet: FileSpreadsheet,\n code: FileCode,\n image: ImageIcon,\n default: File,\n} as const\n\n/** Format bytes into a human-readable string. */\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n}\n\n/** Extract a short extension label from a filename (e.g., \"PDF\", \"CSV\"). */\nfunction getExtensionLabel(name: string): string | null {\n const dot = name.lastIndexOf('.')\n if (dot === -1 || dot === name.length - 1) return null\n return name.slice(dot + 1).toUpperCase()\n}\n\nfunction FileCard({\n file,\n compact,\n onRemove,\n onClick,\n}: {\n file: PromptAreaFile\n compact: boolean\n onRemove?: (file: PromptAreaFile) => void\n onClick?: (file: PromptAreaFile) => void\n}) {\n const ext = getExtensionLabel(file.name)\n const sizeStr = file.size != null ? formatFileSize(file.size) : null\n const meta = [ext, sizeStr].filter(Boolean).join(' · ')\n\n return (\n <div\n role=\"listitem\"\n className={cn(\n 'border-border relative flex flex-shrink-0 items-center gap-2 overflow-hidden rounded-lg border transition-colors',\n 'hover:bg-surface-hover',\n compact ? 'h-10 w-36 px-2' : 'h-14 w-48 px-3',\n onClick && 'cursor-pointer',\n )}\n onClick={() => onClick?.(file)}>\n {(() => {\n const Icon = FILE_ICONS[getFileIconKey(file.type)]\n return (\n <Icon\n className={cn('text-muted-foreground flex-shrink-0', compact ? 'h-4 w-4' : 'h-5 w-5')}\n />\n )\n })()}\n <div className=\"min-w-0 flex-1\">\n <div\n className={cn('truncate font-medium', compact ? 'text-xs' : 'text-sm')}\n title={file.name}>\n {file.name}\n </div>\n {!compact && meta && <div className=\"text-muted-foreground truncate text-xs\">{meta}</div>}\n </div>\n\n {file.loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/40\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent\" />\n </div>\n )}\n\n {onRemove && <RemoveButton onClick={() => onRemove(file)} label={`Remove ${file.name}`} />}\n </div>\n )\n}\n\nexport function FileStrip({ files, onRemove, onClick, className }: FileStripProps) {\n const [expanded, setExpanded] = useState(false)\n const popoverRef = useRef<HTMLDivElement>(null)\n const toggleRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (!expanded) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target\n if (!(target instanceof Node)) return\n if (\n popoverRef.current &&\n !popoverRef.current.contains(target) &&\n !toggleRef.current?.contains(target)\n ) {\n setExpanded(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [expanded])\n\n if (files.length === 0) return null\n\n const collapsible = files.length > COLLAPSE_THRESHOLD\n const compact = collapsible\n const hiddenCount = files.length - COLLAPSE_THRESHOLD\n const visibleFiles = files.slice(0, COLLAPSE_THRESHOLD)\n\n return (\n <div className={cn('relative', className)}>\n <div className=\"flex flex-wrap gap-2\" role=\"list\" aria-label=\"Attached files\">\n {(collapsible ? visibleFiles : files).map((file) => (\n <FileCard\n key={file.id}\n file={file}\n compact={compact}\n onRemove={onRemove}\n onClick={onClick}\n />\n ))}\n {collapsible && (\n <div role=\"listitem\">\n <button\n ref={toggleRef}\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className={cn(\n 'border-border text-muted-foreground hover:bg-surface-hover flex flex-shrink-0 cursor-pointer items-center justify-center rounded-lg border transition-colors',\n compact ? 'h-10 px-3 text-xs' : 'h-14 px-4 text-sm',\n )}>\n {expanded ? 'Show less' : `+${hiddenCount} more`}\n </button>\n </div>\n )}\n </div>\n\n {expanded && (\n <div\n ref={popoverRef}\n className={cn(\n 'bg-surface border-border absolute bottom-full left-0 z-10 mb-2 max-h-48 overflow-y-auto rounded-lg border p-2 shadow-lg',\n )}>\n <div className=\"flex flex-wrap gap-2\" role=\"list\" aria-label=\"More attached files\">\n {files.slice(COLLAPSE_THRESHOLD).map((file) => (\n <FileCard\n key={file.id}\n file={file}\n compact={compact}\n onRemove={onRemove}\n onClick={onClick}\n />\n ))}\n </div>\n </div>\n )}\n </div>\n )\n}\n","'use client'\n\nimport { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'\nimport { cn } from '../../lib/utils.ts'\nimport type { PromptAreaProps, PromptAreaHandle } from './types.ts'\nimport { usePromptArea } from './use-prompt-area.ts'\nimport { BLUR_DELAY_MS } from './use-prompt-area-events.ts'\nimport { TriggerPopover } from './trigger-popover.tsx'\nimport { AnimatedPlaceholder } from './animated-placeholder.tsx'\nimport { ImageStrip } from './image-strip.tsx'\nimport { FileStrip } from './file-strip.tsx'\n\n/**\n * PromptArea - A lightweight rich text input with trigger support.\n *\n * Uses contentEditable to support inline chips (immutable pills) for\n * mentions, commands, and other triggered tokens. Each trigger character\n * can be configured to show a dropdown or fire a callback.\n *\n * @example\n * ```tsx\n * const [segments, setSegments] = useState<Segment[]>([])\n *\n * <PromptArea\n * value={segments}\n * onChange={setSegments}\n * triggers={[\n * { char: '@', position: 'any', mode: 'dropdown', onSearch: searchUsers },\n * { char: '/', position: 'start', mode: 'dropdown', onSearch: searchCommands },\n * { char: '#', position: 'any', mode: 'dropdown', onSearch: searchTags },\n * ]}\n * placeholder=\"Type a message...\"\n * onSubmit={handleSubmit}\n * autoGrow\n * />\n * ```\n */\nexport function PromptArea({\n value,\n onChange,\n triggers,\n placeholder,\n className,\n disabled = false,\n markdown,\n normalizeBullets,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onLinkClick,\n onPaste,\n onUndo,\n onRedo,\n minHeight = 80,\n maxHeight,\n autoFocus = false,\n autoGrow = false,\n 'aria-label': ariaLabel,\n 'data-test-id': dataTestId,\n images = [],\n imagePosition = 'above',\n onImagePaste,\n onImageRemove,\n onImageClick,\n files = [],\n filePosition = 'above',\n onFileRemove,\n onFileClick,\n onKeyDown,\n onBlur,\n onRawPaste,\n submitOnEnter,\n spellCheck,\n maxLength,\n 'aria-describedby': ariaDescribedBy,\n ref,\n}: PromptAreaProps & { ref?: React.Ref<PromptAreaHandle> }) {\n const {\n editorRef,\n activeTrigger,\n suggestions,\n suggestionsLoading,\n suggestionsError,\n selectedSuggestionIndex,\n handleInput,\n handleKeyDown,\n handleClick,\n handleMouseDown,\n selectSuggestion,\n dismissTrigger,\n handle,\n triggerRect,\n eventHandlers,\n } = usePromptArea({\n value,\n onChange,\n triggers,\n disabled,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onLinkClick,\n onPaste,\n onRawPaste,\n onUndo,\n onRedo,\n onImagePaste,\n markdown,\n normalizeBullets,\n submitOnEnter,\n maxLength,\n })\n\n // Expose imperative handle via ref\n useImperativeHandle(ref, () => handle, [handle])\n\n // Auto-focus on mount\n useEffect(() => {\n if (autoFocus) {\n editorRef.current?.focus()\n }\n }, [autoFocus, editorRef])\n\n // -----------------------------------------------------------------------\n // Auto-grow: expand on focus/input, shrink on blur\n // -----------------------------------------------------------------------\n\n const [isFocused, setIsFocused] = useState(false)\n const [editorHeight, setEditorHeight] = useState<number | undefined>(undefined)\n\n const syncHeight = useCallback(() => {\n const el = editorRef.current\n if (!el) return\n // Temporarily set height to auto so scrollHeight reflects true content height\n el.style.height = 'auto'\n const contentHeight = el.scrollHeight\n el.style.height = `${contentHeight}px`\n setEditorHeight(contentHeight)\n }, [editorRef])\n\n const handleFocus = useCallback(() => {\n if (!autoGrow) return\n setIsFocused(true)\n syncHeight()\n }, [autoGrow, syncHeight])\n\n const handleBlurWithShrink = useCallback(() => {\n eventHandlers.onBlur()\n if (!autoGrow) return\n setTimeout(() => {\n const editor = editorRef.current\n if (!editor) return\n // Only shrink if focus truly left the component\n const activeEl = document.activeElement\n if (activeEl && editor.parentElement?.contains(activeEl)) return\n setIsFocused(false)\n setEditorHeight(undefined)\n }, BLUR_DELAY_MS)\n }, [eventHandlers, autoGrow, editorRef])\n\n const handleInputWithGrow = useCallback(() => {\n handleInput()\n if (autoGrow && isFocused) {\n syncHeight()\n }\n }, [handleInput, autoGrow, isFocused, syncHeight])\n\n // Re-measure on value changes (chip insertion, undo/redo, programmatic updates)\n useEffect(() => {\n if (autoGrow && isFocused) {\n requestAnimationFrame(() => syncHeight())\n }\n }, [value, autoGrow, isFocused, syncHeight])\n\n // -----------------------------------------------------------------------\n // Overflow indicator: detect when collapsed content is clipped\n // -----------------------------------------------------------------------\n\n const [hasOverflow, setHasOverflow] = useState(false)\n const overflowTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n if (!autoGrow) return\n\n const checkOverflow = () => {\n if (isFocused) {\n setHasOverflow(false)\n return\n }\n const el = editorRef.current\n if (!el) return\n setHasOverflow(el.scrollHeight > el.clientHeight)\n }\n\n // Delay the check so the CSS height transition (150ms) finishes first;\n // on initial mount there is no transition so the check still runs quickly.\n const delay = isFocused ? 0 : 160\n overflowTimerRef.current = setTimeout(checkOverflow, delay)\n return () => {\n if (overflowTimerRef.current !== null) {\n clearTimeout(overflowTimerRef.current)\n }\n }\n }, [autoGrow, isFocused, value, editorRef])\n\n // -----------------------------------------------------------------------\n // Compute editor style\n // -----------------------------------------------------------------------\n\n const editorStyle = useMemo((): React.CSSProperties => {\n if (!autoGrow) {\n const style: React.CSSProperties = { minHeight: `${minHeight}px` }\n if (maxHeight) {\n style.maxHeight = `${maxHeight}px`\n style.overflowY = 'auto'\n }\n return style\n }\n return {\n height: isFocused && editorHeight ? `${editorHeight}px` : `${minHeight}px`,\n minHeight: `${minHeight}px`,\n // Respect an explicit maxHeight; otherwise fall back to a viewport-relative\n // cap so the editor never grows past the screen.\n maxHeight: maxHeight ? `${maxHeight}px` : '70dvh',\n overflowY: isFocused ? 'auto' : 'hidden',\n // `min-height` is eased so consumers that animate it (e.g. the compact\n // prompt area's collapse/expand) morph smoothly instead of snapping.\n transition: 'height 150ms ease-out, min-height 240ms cubic-bezier(0.33, 1, 0.68, 1)',\n }\n }, [autoGrow, minHeight, maxHeight, isFocused, editorHeight])\n\n // Run a consumer's onKeyDown first; if it calls preventDefault, skip all of\n // PromptArea's built-in key handling (submit, trigger nav, etc.).\n const handleKeyDownCombined = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(e)\n if (e.defaultPrevented) return\n handleKeyDown(e)\n },\n [onKeyDown, handleKeyDown],\n )\n\n // Forward blur to the consumer (with relatedTarget) alongside the internal\n // trigger-dismiss / auto-grow-shrink handling.\n const handleBlurCombined = useCallback(\n (e: React.FocusEvent<HTMLDivElement>) => {\n onBlur?.(e)\n // handleBlurWithShrink already calls eventHandlers.onBlur() and only\n // shrinks when autoGrow is on, so it covers both modes.\n handleBlurWithShrink()\n },\n [onBlur, handleBlurWithShrink],\n )\n\n const isEmpty =\n value.length === 0 || (value.length === 1 && value[0].type === 'text' && value[0].text === '')\n\n const imageStrip =\n images.length > 0 ? (\n <ImageStrip\n images={images}\n onRemove={onImageRemove}\n onClick={onImageClick}\n className={imagePosition === 'above' ? 'pb-2' : 'pt-2'}\n />\n ) : null\n\n const fileStrip =\n files.length > 0 ? (\n <FileStrip\n files={files}\n onRemove={onFileRemove}\n onClick={onFileClick}\n className={filePosition === 'above' ? 'pb-2' : 'pt-2'}\n />\n ) : null\n\n // Typography (font-size/line-height) lives on the container, not the editor, so\n // it cascades to the editor AND the placeholder overlays — and a consumer can\n // override all three at once via `className` (e.g. `text-base leading-6`).\n return (\n <div className={cn('prompt-area-container relative text-sm leading-relaxed', className)}>\n {imagePosition === 'above' && imageStrip}\n {filePosition === 'above' && fileStrip}\n\n {/* Editor + placeholder wrapper */}\n <div className=\"relative\">\n <div\n ref={editorRef}\n contentEditable={!disabled}\n suppressContentEditableWarning\n role=\"textbox\"\n aria-label={ariaLabel ?? 'Text input'}\n aria-multiline=\"true\"\n aria-disabled={disabled || undefined}\n aria-describedby={ariaDescribedBy}\n data-test-id={dataTestId}\n spellCheck={spellCheck}\n className={cn(\n 'prompt-area-editor',\n 'w-full min-w-0 break-words whitespace-pre-wrap outline-none',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n style={editorStyle}\n onFocus={handleFocus}\n onInput={autoGrow ? handleInputWithGrow : handleInput}\n onKeyDown={handleKeyDownCombined}\n onMouseDown={handleMouseDown}\n onClick={handleClick}\n onPaste={eventHandlers.onPaste}\n onCopy={eventHandlers.onCopy}\n onCut={eventHandlers.onCut}\n onDrop={eventHandlers.onDrop}\n onDragOver={eventHandlers.onDragOver}\n onCompositionStart={eventHandlers.onCompositionStart}\n onCompositionEnd={eventHandlers.onCompositionEnd}\n onBlur={handleBlurCombined}\n />\n\n {/* Overflow gradient indicator – visible when auto-grow is collapsed and content is clipped */}\n {autoGrow && hasOverflow && !isFocused && (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-auto absolute right-0 bottom-0 left-0 cursor-pointer\"\n style={{ height: '32px' }}\n onClick={() => editorRef.current?.focus()}>\n <div\n className=\"h-full w-full\"\n style={{\n background:\n 'linear-gradient(to bottom, transparent, color-mix(in srgb, var(--prompt-area-surface, var(--background)) 80%, transparent), var(--prompt-area-surface, var(--background)))',\n }}\n />\n </div>\n )}\n\n {/* Placeholder overlay */}\n {isEmpty &&\n placeholder &&\n (Array.isArray(placeholder) ? (\n <AnimatedPlaceholder texts={placeholder} />\n ) : (\n <div\n className=\"pointer-events-none absolute top-0 left-0 select-none\"\n style={{ color: 'var(--prompt-area-placeholder, var(--muted-foreground))' }}\n aria-hidden=\"true\">\n {placeholder}\n </div>\n ))}\n </div>\n\n {filePosition === 'below' && fileStrip}\n {imagePosition === 'below' && imageStrip}\n\n {/* Trigger suggestion popover */}\n {activeTrigger && activeTrigger.config.mode === 'dropdown' && (\n <TriggerPopover\n suggestions={suggestions}\n loading={suggestionsLoading}\n error={suggestionsError}\n emptyMessage={activeTrigger.config.emptyMessage}\n selectedIndex={selectedSuggestionIndex}\n onSelect={selectSuggestion}\n onDismiss={dismissTrigger}\n triggerRect={triggerRect}\n triggerChar={activeTrigger.config.char}\n />\n )}\n </div>\n )\n}\n","/**\n * Convenience hook that wires up all the boilerplate state for a PromptArea.\n *\n * Instead of manually managing `useState<Segment[]>`, `useRef<PromptAreaHandle>`,\n * and computing derived values, call `usePromptAreaState()` once and spread\n * `bind` into your `<PromptArea>`.\n *\n * @example\n * ```tsx\n * function ChatInput() {\n * const { bind, plainText, isEmpty, chips, clear, focus } = usePromptAreaState()\n *\n * return (\n * <PromptArea\n * {...bind}\n * onSubmit={() => {\n * sendMessage(plainText)\n * clear()\n * }}\n * />\n * )\n * }\n * ```\n */\n\n'use client'\n\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport type { Segment, ChipSegment, PromptAreaHandle } from './types.ts'\nimport { segmentsToPlainText } from './prompt-area-engine.ts'\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport type UsePromptAreaStateOptions = {\n /** Initial segment value. Defaults to `[]`. */\n initialValue?: Segment[]\n}\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport type PromptAreaBind = {\n /** Ref to attach to PromptArea — gives access to imperative methods. */\n ref: React.RefObject<PromptAreaHandle | null>\n /** Current segment array — pass as `value` prop. */\n value: Segment[]\n /** Setter — pass as `onChange` prop. */\n onChange: (segments: Segment[]) => void\n}\n\nexport type PromptAreaState = {\n /** Props to spread directly onto `<PromptArea {...bind} />`. Contains ref, value, and onChange. */\n bind: PromptAreaBind\n /** Derived plain text representation of the current value. */\n plainText: string\n /** `true` when the value is empty or whitespace-only. */\n isEmpty: boolean\n /** `true` when the value contains at least one chip. */\n hasChips: boolean\n /** All chip segments in the current value. */\n chips: ChipSegment[]\n /** Clear all content (both state and the editor DOM). */\n clear: () => void\n /** Focus the editor. */\n focus: () => void\n /** Blur the editor. */\n blur: () => void\n /** Insert a chip at the current cursor position. */\n insertChip: (chip: Omit<ChipSegment, 'type'>) => void\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function usePromptAreaState(options: UsePromptAreaStateOptions = {}): PromptAreaState {\n const { initialValue = [] } = options\n\n const [value, setValue] = useState<Segment[]>(initialValue)\n const ref = useRef<PromptAreaHandle>(null)\n\n // Derived\n const plainText = useMemo(() => segmentsToPlainText(value), [value])\n\n const isEmpty = useMemo(() => {\n if (value.length === 0) return true\n return value.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n }, [value])\n\n const hasChips = useMemo(() => value.some((seg) => seg.type === 'chip'), [value])\n\n const chips = useMemo(\n () => value.filter((seg): seg is ChipSegment => seg.type === 'chip'),\n [value],\n )\n\n // Bind object — safe to spread onto <PromptArea>\n const bind = useMemo<PromptAreaBind>(() => ({ ref, value, onChange: setValue }), [value])\n\n // Actions that proxy to the imperative handle\n const clear = useCallback(() => {\n if (ref.current) {\n ref.current.clear()\n } else {\n setValue([])\n }\n }, [])\n\n const focus = useCallback(() => ref.current?.focus(), [])\n const blur = useCallback(() => ref.current?.blur(), [])\n\n const insertChip = useCallback(\n (chip: Omit<ChipSegment, 'type'>) => ref.current?.insertChip(chip),\n [],\n )\n\n return {\n bind,\n plainText,\n isEmpty,\n hasChips,\n chips,\n clear,\n focus,\n blur,\n insertChip,\n }\n}\n","/**\n * Pre-built trigger configuration factories for common AI chat patterns.\n *\n * Each factory returns a full `TriggerConfig` with sensible defaults.\n * Pass only what you need to override.\n *\n * @example\n * ```tsx\n * <PromptArea\n * triggers={[\n * mentionTrigger({ onSearch: searchUsers }),\n * commandTrigger({ onSearch: searchCommands }),\n * hashtagTrigger(),\n * ]}\n * />\n * ```\n */\n\nimport type { TriggerConfig, TriggerPosition } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Shared option type — everything in TriggerConfig except the keys each\n// factory sets by default.\n// ---------------------------------------------------------------------------\n\ntype TriggerPresetOptions = Omit<Partial<TriggerConfig>, 'char' | 'position' | 'mode'>\n\n// ---------------------------------------------------------------------------\n// @mention — dropdown at any position\n// ---------------------------------------------------------------------------\n\nexport type MentionTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'@'`. */\n char?: string\n}\n\n/**\n * Creates a **mention** trigger (`@`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * accessible label `\"mention\"`.\n */\nexport function mentionTrigger(opts: MentionTriggerOptions = {}): TriggerConfig {\n const { char = '@', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n accessibilityLabel: 'mention',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// /command — dropdown anywhere (opt into line-start-only with `position`)\n// ---------------------------------------------------------------------------\n\nexport type CommandTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'/'`. */\n char?: string\n /**\n * Where the command trigger is valid. Defaults to `'any'`, so commands fire\n * anywhere a `/` follows whitespace — not just at the start of a line.\n * Set to `'start'` to restrict the dropdown to the very start of the input\n * or immediately after a newline (the classic slash-command behavior).\n */\n position?: TriggerPosition\n}\n\n/**\n * Creates a **command** trigger (`/`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'inline'`,\n * accessible label `\"command\"`.\n *\n * By default commands work everywhere in the input. Pass `position: 'start'`\n * to limit them to the start of a line.\n */\nexport function commandTrigger(opts: CommandTriggerOptions = {}): TriggerConfig {\n const { char = '/', position = 'any', ...rest } = opts\n return {\n char,\n position,\n mode: 'dropdown',\n chipStyle: 'inline',\n accessibilityLabel: 'command',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// #hashtag — dropdown at any position, auto-resolve on space\n// ---------------------------------------------------------------------------\n\nexport type HashtagTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'#'`. */\n char?: string\n}\n\n/**\n * Creates a **hashtag / tag** trigger (`#`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * `resolveOnSpace: true`, accessible label `\"tag\"`.\n */\nexport function hashtagTrigger(opts: HashtagTriggerOptions = {}): TriggerConfig {\n const { char = '#', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n resolveOnSpace: true,\n accessibilityLabel: 'tag',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Generic callback trigger (e.g., for file pickers, model selectors)\n// ---------------------------------------------------------------------------\n\nexport type CallbackTriggerOptions = Omit<Partial<TriggerConfig>, 'mode'> & {\n /** The trigger character. Required. */\n char: string\n}\n\n/**\n * Creates a **callback** trigger that fires `onActivate` instead of showing\n * a dropdown. Useful for opening file pickers, model selectors, etc.\n *\n * Defaults: `position: 'start'`, `mode: 'callback'`.\n */\nexport function callbackTrigger(opts: CallbackTriggerOptions): TriggerConfig {\n const { char, ...rest } = opts\n return {\n char,\n position: 'start',\n mode: 'callback',\n ...rest,\n }\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","export function formatCost(usd: number | undefined): string {\n if (usd === undefined || Number.isNaN(usd)) return '—'\n if (usd === 0) return '$0.00'\n if (usd < 0.01) return '<$0.01'\n return `$${usd.toFixed(2)}`\n}\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${Math.round(ms)}ms`\n const s = ms / 1000\n if (s < 60) return `${s.toFixed(1)}s`\n const m = Math.floor(s / 60)\n return `${m}m ${Math.round(s % 60)}s`\n}\n\n/** Compact token count, Claude Code-style: 850 → \"850\", 359_000 → \"359.0k\", 1_200_000 → \"1.2M\". */\nexport function formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`\n if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}k`\n return String(Math.round(tokens))\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${bytes} B`\n}\n\n/** Countdown to an epoch-ms deadline: \"2h 18m\", \"12m\", \"<1m\"; \"now\" once passed. */\nexport function formatCountdown(untilEpochMs: number, now = Date.now()): string {\n const remaining = untilEpochMs - now\n if (remaining <= 0) return 'now'\n const minutes = Math.ceil(remaining / 60_000)\n if (minutes < 1) return '<1m'\n if (minutes < 60) return `${minutes}m`\n const days = Math.floor(minutes / (60 * 24))\n if (days >= 1) return `${days}d ${Math.floor((minutes % (60 * 24)) / 60)}h`\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`\n}\n\nexport function formatRelativeTime(epochMs: number | undefined, now = Date.now()): string {\n if (!epochMs) return '—'\n const diff = Math.max(0, now - epochMs)\n const s = Math.floor(diff / 1000)\n if (s < 60) return 'just now'\n const m = Math.floor(s / 60)\n if (m < 60) return `${m}m ago`\n const h = Math.floor(m / 60)\n if (h < 24) return `${h}h ago`\n const d = Math.floor(h / 24)\n return `${d}d ago`\n}\n\n/**\n * Human label for a rate-limit window key, compact: 'five_hour' → \"5h\",\n * 'seven_day_opus' → \"7d opus\". The per-model suffix is an open set — the CLI\n * adds buckets as plans gain them — so it is rewritten rather than enumerated.\n */\nexport function formatRateLimitWindow(key: string): string {\n if (key === 'five_hour') return '5h'\n if (key === 'seven_day') return '7d'\n const spaced = key.replaceAll('_', ' ')\n return key.startsWith('seven_day_') ? `7d ${spaced.slice('seven day '.length)}` : spaced\n}\n\n/** The same key spelled out, where there is room: 'five_hour' → \"5-hour\n * session\", 'seven_day_fable' → \"Weekly · Fable\". */\nexport function formatRateLimitWindowLong(key: string): string {\n if (key === 'five_hour') return '5-hour session'\n if (key === 'seven_day') return 'Weekly'\n if (key === 'seven_day_oauth_apps') return 'Weekly · apps'\n const capitalize = (s: string) => s.replace(/\\b\\w/g, (c) => c.toUpperCase())\n if (!key.startsWith('seven_day_')) return capitalize(key.replaceAll('_', ' '))\n return `Weekly · ${capitalize(key.slice('seven_day_'.length).replaceAll('_', ' '))}`\n}\n\n/**\n * How long a rate-limit window is, in seconds — the denominator behind the pace\n * marker. Derived from the key rather than reported: the CLI sends a reset time\n * and a percentage, never a duration. `undefined` for a window whose key doesn't\n * say, and the marker is then simply not drawn rather than guessed.\n */\nexport function rateLimitWindowSeconds(key: string): number | undefined {\n if (key === 'five_hour') return 5 * 3600\n if (key.startsWith('seven_day')) return 7 * 86_400\n return undefined\n}\n\n/** \"8 secs ago\" / \"3 mins ago\" — a freshness line finer-grained than\n * {@link formatRelativeTime}, because a poll that just landed should say so. */\nexport function formatAgoPrecise(epochMs: number, now = Date.now()): string {\n const seconds = Math.max(0, Math.floor((now - epochMs) / 1000))\n if (seconds < 60) return `${seconds} sec${seconds === 1 ? '' : 's'} ago`\n if (seconds < 3600) {\n const minutes = Math.floor(seconds / 60)\n return `${minutes} min${minutes === 1 ? '' : 's'} ago`\n }\n const hours = Math.floor(seconds / 3600)\n return `${hours} hour${hours === 1 ? '' : 's'} ago`\n}\n\n/** Compact one-line preview of a tool input for card headers. */\nexport function toolInputPreview(input: unknown, max = 80): string {\n if (input === null || input === undefined) return ''\n if (typeof input === 'object') {\n const o = input as Record<string, unknown>\n const primary =\n o.command ?? o.file_path ?? o.path ?? o.url ?? o.pattern ?? o.query ?? o.description\n if (typeof primary === 'string') {\n return primary.length > max ? primary.slice(0, max - 1) + '…' : primary\n }\n }\n const text = JSON.stringify(input) ?? ''\n return text.length > max ? text.slice(0, max - 1) + '…' : text\n}\n","import { useImperativeHandle, useMemo, useRef, useState, type ReactNode, type Ref } from 'react'\nimport type { SkillInfo, SlashCommandInfo } from '@workerdeck/protocol'\nimport type { StagedAttachment, UseAttachmentsResult } from '@workerdeck/react'\nimport {\n ArrowUp,\n FileText,\n Paperclip,\n RotateCw,\n Sparkles,\n Square,\n TriangleAlert,\n X,\n} from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { PromptArea } from '../prompt-area/prompt-area.tsx'\nimport { usePromptAreaState } from '../prompt-area/use-prompt-area-state.ts'\nimport { commandTrigger, mentionTrigger } from '../prompt-area/trigger-presets.ts'\nimport type { TriggerSuggestion } from '../prompt-area/types.ts'\nimport { cn } from '../../lib/utils.ts'\nimport { formatBytes } from '../../lib/format.ts'\n\n/** Files matching an `@` query, for the composer's file trigger. Structural so\n * the ui package doesn't have to reach for the protocol's `HostFileMatch`. */\nexport type ComposerFileMatch = { path: string; relative: string }\n\n/** Imperative surface for panels that draft a message the user then finishes —\n * the skills dialog's \"Use this skill\". Nothing here sends. */\nexport type ComposerHandle = {\n /** Append plain text at the caret's end and focus, separating it from\n * whatever is already there. */\n insertText: (text: string) => void\n}\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 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 className,\n ref,\n}: ComposerProps) {\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 }),\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 return (\n <div data-slot='composer' className={cn('px-3 pb-3', className)}>\n <div\n onDragOver={(e) => {\n if (attachments && !attachments.disabled) {\n e.preventDefault()\n setDragging(true)\n }\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n if (!attachments || attachments.disabled) return\n e.preventDefault()\n setDragging(false)\n pick(e.dataTransfer.files)\n }}\n className={cn(\n 'mx-auto w-full max-w-3xl overflow-hidden rounded-lg border border-border bg-bg shadow-(--shadow-xs)',\n 'transition-colors focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30',\n dragging && 'border-ring ring-2 ring-ring/30',\n disabled && 'opacity-60',\n )}>\n {/* Above the field, like the picture you are talking about should be. */}\n {staged.length > 0 && attachments ? (\n <AttachmentStrip attachments={attachments} />\n ) : null}\n <PromptArea\n {...bind}\n triggers={triggers}\n onSubmit={submit}\n disabled={disabled}\n placeholder={disabled ? 'Session ended' : placeholder}\n minHeight={28}\n maxHeight={192}\n aria-label='Message the agent'\n className='px-3 pt-2.5 pb-1 text-body-sm text-text'\n onImagePaste={(file) => attachments?.add([file])}\n />\n <div className='flex items-center justify-between gap-2 px-2 pb-2'>\n <div className='flex min-w-0 items-center gap-1'>\n {/* An attach affordance the engine has no meaning for is not a\n choice — the capability record decides whether it exists. */}\n {attachments && !attachments.disabled ? (\n <>\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 <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 {toolbar}\n </div>\n {busy && !canSend ? (\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 </div>\n </div>\n {attachments?.error ? (\n <div className='mx-auto mt-1 flex w-full max-w-3xl items-center gap-2 text-label text-danger'>\n <TriangleAlert className='size-3 shrink-0' />\n <span className='min-w-0 flex-1'>{attachments.error}</span>\n <button\n type='button'\n onClick={attachments.dismissError}\n aria-label='Dismiss'\n className='shrink-0 opacity-70 hover:opacity-100'>\n <X className='size-3' />\n </button>\n </div>\n ) : (\n <div className='mx-auto mt-1 w-full max-w-3xl text-center text-label text-fg-4'>\n Enter to send · Shift+Enter for a new line\n </div>\n )}\n </div>\n )\n}\n\n/** Staged files as a scrolling row of chips above the field. The thumbnail is\n * the local blob, so nothing here waits on the network; the upload's state rides\n * on top of it and the ✕ takes it back off. */\nfunction AttachmentStrip({ attachments }: { attachments: UseAttachmentsResult }) {\n return (\n <div className='flex gap-2 overflow-x-auto border-b border-border px-2 py-2'>\n {attachments.items.map((item) => (\n <AttachmentChip\n key={item.key}\n item={item}\n onRetry={() => attachments.retry(item.key)}\n onRemove={() => attachments.remove(item.key)}\n />\n ))}\n </div>\n )\n}\n\nfunction AttachmentChip({\n item,\n onRetry,\n onRemove,\n}: {\n item: StagedAttachment\n onRetry: () => void\n onRemove: () => void\n}) {\n const failed = item.status === 'failed'\n return (\n <div\n className='group relative shrink-0'\n title={failed ? `${item.name} — ${item.error}` : `${item.name} · ${formatBytes(item.bytes)}`}>\n <div\n className={cn(\n 'flex size-14 items-center justify-center overflow-hidden rounded-md border border-border bg-surface',\n failed && 'border-danger/50',\n )}>\n {item.previewUrl ? (\n <img src={item.previewUrl} alt={item.name} className='size-full object-cover' />\n ) : (\n <div className='flex flex-col items-center gap-0.5 text-fg-3'>\n <FileText className='size-4' />\n <span className='max-w-12 truncate text-[9px] font-semibold uppercase'>\n {extensionOf(item.name)}\n </span>\n </div>\n )}\n </div>\n {item.status === 'uploading' ? (\n <div className='absolute inset-0 flex items-center justify-center rounded-md bg-black/35'>\n <Spinner className='size-4 text-white' />\n </div>\n ) : null}\n {failed ? (\n <button\n type='button'\n onClick={onRetry}\n aria-label={`Retry ${item.name}`}\n className='absolute inset-0 flex items-center justify-center rounded-md bg-black/45 text-warning'>\n <RotateCw className='size-4' />\n </button>\n ) : null}\n <button\n type='button'\n onClick={onRemove}\n aria-label={`Remove ${item.name}`}\n className='absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full border border-border bg-surface text-fg-3 shadow-(--shadow-xs) hover:text-fg-1'>\n <X className='size-2.5' />\n </button>\n </div>\n )\n}\n\nconst extensionOf = (name: string) => {\n const dot = name.lastIndexOf('.')\n return dot > 0 ? name.slice(dot + 1).toUpperCase() : 'FILE'\n}\n","import type { ContextUsage } from '@workerdeck/protocol'\nimport { Dialog, DialogBody, DialogContent, DialogHeader } from '../ui/Dialog.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatTokens } from '../../lib/format.ts'\n\nexport interface ContextDialogProps {\n usage?: ContextUsage\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/** The CLI reports category colors as its own theme token names ('inactive',\n * 'promptBorder', ...), not CSS colors — only pass through what CSS can render. */\nconst cssColor = (color: string): string | undefined =>\n typeof CSS !== 'undefined' && CSS.supports('color', color) ? color : undefined\n\nconst usageTint = (pct: number) => (pct >= 90 ? 'bg-danger' : pct >= 70 ? 'bg-warning' : 'bg-accent')\n\n/**\n * What is in the model's context window right now, category by category.\n *\n * One of the three panels the status bar opens. Context, usage and session info\n * are different questions asked at different moments, so they are different\n * screens rather than one \"details\" list you scroll past two answers to reach.\n */\nexport function ContextDialog({ usage, open, onOpenChange }: ContextDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader title='Context' description={usage?.model} />\n <DialogBody>\n {!usage ? (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n No reading yet — the context window is measured after a turn completes.\n </p>\n ) : (\n <>\n <div className='flex items-baseline justify-between gap-4'>\n <span className='text-label text-fg-3'>Used</span>\n <span className='font-mono text-body-sm text-fg-1'>\n {formatTokens(usage.totalTokens)} / {formatTokens(usage.maxTokens)} ·{' '}\n {usage.percentage.toFixed(0)}%\n </span>\n </div>\n <div className='mt-2 h-2 overflow-hidden rounded-full bg-border'>\n <div\n className={cn('h-full rounded-full', usageTint(usage.percentage))}\n style={{ width: `${Math.min(100, Math.max(2, usage.percentage))}%` }}\n />\n </div>\n {/* Codex reports occupancy with no breakdown, so an engine can send\n a real reading and an empty `categories`. A \"Breakdown\" heading\n over nothing reads as a failed load; the row above already says\n everything that is known. */}\n {usage.categories.length > 0 ? (\n <div className='mt-5'>\n <h3 className='text-label font-medium text-fg-3'>Breakdown</h3>\n <div className='mt-2 flex flex-col gap-3'>\n {usage.categories.map((category) => {\n const share = (category.tokens / Math.max(usage.maxTokens, 1)) * 100\n const color = cssColor(category.color)\n return (\n <div key={category.name}>\n <div className='flex items-baseline justify-between gap-3'>\n <span className='flex min-w-0 items-center gap-2'>\n <span\n className='size-2 shrink-0 rounded-full bg-fg-4'\n style={color ? { backgroundColor: color } : undefined}\n />\n <span className='truncate text-body-sm text-fg-1'>\n {category.name}\n </span>\n </span>\n <span className='shrink-0 font-mono text-label text-fg-3'>\n {formatTokens(category.tokens)}\n </span>\n </div>\n <div className='mt-1 h-1 overflow-hidden rounded-full bg-border'>\n <div\n className='h-full rounded-full bg-fg-4'\n style={{\n width: `${Math.min(100, share)}%`,\n backgroundColor: color,\n }}\n />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n ) : null}\n </>\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n","import { useCallback, useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { HostDirEntry, HostFileMatch } from '@workerdeck/protocol'\nimport { ChevronLeft, File, Folder, Link2, Search } from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { CodeBlock } from '../ui/CodeBlock.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader } from '../ui/Dialog.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { formatBytes } from '../../lib/format.ts'\n\nexport interface HostFilesDialogProps {\n client: WorkerDeckClient\n /** The session's working directory — the browser is rooted here. */\n cwd: string | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * Browse the project the session is working in.\n *\n * Deliberately rooted at the session's cwd rather than at the server's\n * `hostFiles.roots`: the roots are the *security* boundary (the server enforces\n * them on every request), but what someone wants while watching an agent is this\n * project's tree. Read-only — writing is a separate server opt-in and not\n * something a session viewer should be doing behind the agent's back.\n */\nexport function HostFilesDialog({ client, cwd, open, onOpenChange }: HostFilesDialogProps) {\n const [path, setPath] = useState<string | undefined>(cwd)\n const [entries, setEntries] = useState<HostDirEntry[]>([])\n const [truncated, setTruncated] = useState(false)\n const [query, setQuery] = useState('')\n const [matches, setMatches] = useState<HostFileMatch[] | undefined>()\n const [file, setFile] = useState<{ path: string; content: string; bytes: number } | undefined>()\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | undefined>()\n\n const list = useCallback(\n async (target: string) => {\n setLoading(true)\n setError(undefined)\n try {\n const response = await client.listHostDir(target)\n setPath(response.path)\n setEntries(response.entries)\n setTruncated(response.truncated ?? false)\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not read that directory')\n } finally {\n setLoading(false)\n }\n },\n [client],\n )\n\n // Opening resets the browser to the session's directory. Navigation from here\n // is explicit (`list`) rather than an effect on `path`, so walking into a\n // folder is one request and not two.\n useEffect(() => {\n if (!open) return\n setFile(undefined)\n setQuery('')\n setMatches(undefined)\n setError(undefined)\n if (cwd) void list(cwd)\n }, [open, cwd, list])\n\n // Debounced, and only while there is something to search for; an empty box is\n // \"show me the directory again\", not \"search for everything\".\n useEffect(() => {\n if (!open || !cwd) return\n const q = query.trim()\n if (!q) {\n setMatches(undefined)\n return\n }\n const timer = setTimeout(() => {\n client\n .findHostFiles(cwd, q, 40)\n .then((response) => setMatches(response.matches))\n .catch(() => setMatches([]))\n }, 150)\n return () => clearTimeout(timer)\n }, [client, cwd, query, open])\n\n const openFile = async (target: string) => {\n setLoading(true)\n setError(undefined)\n try {\n const response = await client.readHostFile(target)\n setFile({\n path: response.path,\n bytes: response.bytes,\n content:\n response.encoding === 'utf8'\n ? response.content\n : '(binary file — not shown)',\n })\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not read that file')\n } finally {\n setLoading(false)\n }\n }\n\n const parent = path && cwd && path !== cwd ? path.slice(0, path.lastIndexOf('/')) || '/' : undefined\n const shown = matches ?? entries\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent size='lg'>\n <DialogHeader\n title={file ? file.path.split('/').pop()! : 'Files'}\n description={file ? file.path : path}\n actions={\n file ? (\n <Button variant='ghost' size='xs' onClick={() => setFile(undefined)}>\n <ChevronLeft className='size-3.5' />\n Back\n </Button>\n ) : parent ? (\n <Button variant='ghost' size='xs' onClick={() => void list(parent)}>\n <ChevronLeft className='size-3.5' />\n Up\n </Button>\n ) : null\n }\n />\n <DialogBody className='flex flex-col gap-3'>\n {error ? (\n <div className='rounded-md bg-danger-bg px-3 py-2 text-body-sm text-danger'>{error}</div>\n ) : null}\n\n {file ? (\n <>\n <p className='text-label text-fg-4'>{formatBytes(file.bytes)}</p>\n <CodeBlock code={file.content} label={file.path.split('/').pop()} />\n </>\n ) : (\n <>\n <div className='relative'>\n <Search className='absolute top-1/2 left-2 size-3.5 -translate-y-1/2 text-fg-4' />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder='Search this project…'\n className='pl-7'\n spellCheck={false}\n />\n </div>\n {loading && shown.length === 0 ? (\n <div className='py-6 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n ) : shown.length === 0 ? (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n {matches ? 'No matching files.' : 'This directory is empty.'}\n </p>\n ) : (\n <ul className='flex flex-col'>\n {matches\n ? matches.map((match) => (\n <li key={match.path}>\n <button\n type='button'\n onClick={() => void openFile(match.path)}\n className='flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-surface-hover'>\n <File className='size-3.5 shrink-0 text-fg-4' />\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {match.relative}\n </span>\n </button>\n </li>\n ))\n : entries.map((entry) => (\n <li key={entry.path}>\n <button\n type='button'\n onClick={() =>\n entry.type === 'dir' ? void list(entry.path) : void openFile(entry.path)\n }\n className='flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-surface-hover'>\n <EntryIcon type={entry.type} />\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {entry.name}\n {entry.type === 'dir' ? '/' : ''}\n </span>\n {entry.bytes !== undefined ? (\n <span className='shrink-0 text-label text-fg-4'>\n {formatBytes(entry.bytes)}\n </span>\n ) : null}\n </button>\n </li>\n ))}\n </ul>\n )}\n {truncated && !matches ? (\n <p className='text-label text-fg-4'>\n More entries than the server will return — use the search box.\n </p>\n ) : null}\n </>\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\n/** A symlink is reported as itself and never silently resolved — following it is\n * the next request's problem, and that request is refused if it escapes the roots. */\nfunction EntryIcon({ type }: { type: HostDirEntry['type'] }) {\n if (type === 'dir') return <Folder className='size-3.5 shrink-0 text-accent' />\n if (type === 'symlink') return <Link2 className='size-3.5 shrink-0 text-fg-4' />\n return <File className='size-3.5 shrink-0 text-fg-4' />\n}\n","import { useCallback, useEffect, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type {\n McpServerActionRequest,\n McpServerStatusInfo,\n McpServerToolInfo,\n} from '@workerdeck/protocol'\nimport { ChevronLeft, ChevronRight, Power, PowerOff, RotateCw } from 'lucide-react'\nimport { Badge, type BadgeProps } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader, DialogRow } from '../ui/Dialog.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface McpDialogProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n /**\n * Whether this engine can reconnect/enable/disable a server\n * (`EngineCapabilities.mcpServerActions`). False renders the panel read-only:\n * codex reports rich status but exposes no per-server action, and buttons\n * that 501 are worse than buttons that aren't there.\n */\n canManageServers?: boolean\n}\n\n/** The engine's status vocabulary is open — anything unrecognised renders\n * neutrally rather than being forced into one of these. */\nconst STATUS_VARIANT: Record<string, NonNullable<BadgeProps['variant']>> = {\n connected: 'success',\n failed: 'danger',\n 'needs-auth': 'warning',\n pending: 'info',\n disabled: 'neutral',\n}\n\n/**\n * The session's MCP servers, at the CLI's own `/mcp` depth: servers → one server\n * → its tools → one tool, with Reconnect / Enable / Disable where they apply.\n *\n * Two things vary by engine rather than being fixed here. **The actions** exist\n * only where the engine has them (`canManageServers`): codex reports rich status\n * but has no per-server reconnect or toggle, so its panel is read-only. And\n * **tool parameters** appear only where the engine reports a schema — codex\n * returns each tool's full JSON Schema, the Agent SDK returns none at all, so\n * the tool view either renders it or says why it can't, rather than leaving a\n * silent gap or claiming the absence is universal.\n */\nexport function McpDialog({\n client,\n sessionId,\n open,\n onOpenChange,\n canManageServers = true,\n}: McpDialogProps) {\n const [servers, setServers] = useState<McpServerStatusInfo[] | undefined>()\n const [error, setError] = useState<string | undefined>()\n const [loading, setLoading] = useState(false)\n const [busyServer, setBusyServer] = useState<string | undefined>()\n // The drill-down, by name rather than by object — an action replaces the whole\n // list, and a held reference would go stale on the first Reconnect.\n const [selectedServer, setSelectedServer] = useState<string | undefined>()\n const [selectedTool, setSelectedTool] = useState<string | undefined>()\n\n const load = useCallback(async () => {\n if (!sessionId) return\n setLoading(true)\n setError(undefined)\n try {\n setServers(await client.listMcpServers(sessionId))\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not read MCP status')\n } finally {\n setLoading(false)\n }\n }, [client, sessionId])\n\n useEffect(() => {\n if (!open) return\n setSelectedServer(undefined)\n setSelectedTool(undefined)\n void load()\n }, [open, load])\n\n const act = async (name: string, action: McpServerActionRequest['action']) => {\n if (!sessionId) return\n setBusyServer(name)\n setError(undefined)\n try {\n setServers(await client.mcpServerAction(sessionId, name, action))\n } catch (e) {\n setError(e instanceof Error ? e.message : `Could not ${action} ${name}`)\n } finally {\n setBusyServer(undefined)\n }\n }\n\n const server = servers?.find((s) => s.name === selectedServer)\n const tool = server?.tools?.find((t) => t.name === selectedTool)\n const title = tool?.name ?? server?.name ?? 'MCP servers'\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader\n title={title}\n description={\n tool ? `${server?.name} tool` : server ? server.serverInfo?.name : undefined\n }\n actions={\n selectedServer ? (\n <Button\n variant='ghost'\n size='xs'\n onClick={() => (selectedTool ? setSelectedTool(undefined) : setSelectedServer(undefined))}>\n <ChevronLeft className='size-3.5' />\n Back\n </Button>\n ) : (\n <Button variant='ghost' size='xs' onClick={() => void load()} disabled={loading}>\n {loading ? <Spinner className='size-3 text-current' /> : <RotateCw className='size-3' />}\n Refresh\n </Button>\n )\n }\n />\n <DialogBody>\n {error ? (\n <div className='mb-3 rounded-md bg-danger-bg px-3 py-2 text-body-sm text-danger'>\n {error}\n </div>\n ) : null}\n {tool ? (\n <ToolView tool={tool} />\n ) : server ? (\n <ServerView\n server={server}\n busy={busyServer === server.name}\n canManage={canManageServers}\n onAct={(action) => void act(server.name, action)}\n onSelectTool={setSelectedTool}\n />\n ) : error && !servers ? (\n // The strip above already says what went wrong. Falling through to\n // the list here would print \"No MCP servers configured\" underneath\n // it — a claim about the operator's config that a failed request\n // gives us no standing to make.\n null\n ) : (\n <ServerList\n servers={servers}\n loading={loading}\n onSelect={setSelectedServer}\n />\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction ServerList({\n servers,\n loading,\n onSelect,\n}: {\n servers: McpServerStatusInfo[] | undefined\n loading: boolean\n onSelect: (name: string) => void\n}) {\n if (loading && !servers) {\n return (\n <div className='py-6 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n )\n }\n if (!servers?.length) {\n return (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n No MCP servers configured for this session.\n </p>\n )\n }\n // Grouped by where they were configured, like the CLI's own screen.\n const scopes = [...new Set(servers.map((s) => s.scope ?? 'other'))]\n return (\n <div className='flex flex-col gap-4'>\n {scopes.map((scope) => (\n <div key={scope}>\n <h3 className='text-label font-medium text-fg-3 capitalize'>{scope}</h3>\n <ul className='mt-1 flex flex-col'>\n {servers\n .filter((s) => (s.scope ?? 'other') === scope)\n .map((s) => (\n <li key={s.name}>\n <button\n type='button'\n onClick={() => onSelect(s.name)}\n className='flex w-full items-center gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-surface-hover'>\n <span className='min-w-0 flex-1 truncate text-body-sm text-fg-1'>{s.name}</span>\n {s.tools?.length ? (\n <span className='shrink-0 text-label text-fg-4'>{s.tools.length} tools</span>\n ) : null}\n <Badge variant={STATUS_VARIANT[s.status] ?? 'neutral'} dot className='shrink-0'>\n {s.status}\n </Badge>\n <ChevronRight className='size-3.5 shrink-0 text-fg-4' />\n </button>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n )\n}\n\nfunction ServerView({\n server,\n busy,\n canManage,\n onAct,\n onSelectTool,\n}: {\n server: McpServerStatusInfo\n busy: boolean\n canManage: boolean\n onAct: (action: McpServerActionRequest['action']) => void\n onSelectTool: (name: string) => void\n}) {\n const disabled = server.status === 'disabled'\n return (\n <div className='flex flex-col gap-4'>\n <div>\n <DialogRow label='Status'>\n <Badge variant={STATUS_VARIANT[server.status] ?? 'neutral'} dot>\n {server.status}\n </Badge>\n </DialogRow>\n {server.transport ? <DialogRow label='Transport'>{server.transport}</DialogRow> : null}\n {server.scope ? <DialogRow label='Scope'>{server.scope}</DialogRow> : null}\n {server.serverInfo ? (\n <DialogRow label='Server'>\n {server.serverInfo.name} {server.serverInfo.version}\n </DialogRow>\n ) : null}\n {server.command ? (\n <DialogRow label='Command' mono>\n {[server.command, ...(server.args ?? [])].join(' ')}\n </DialogRow>\n ) : null}\n {server.url ? (\n <DialogRow label='URL' mono>\n {server.url}\n </DialogRow>\n ) : null}\n </div>\n\n {server.error ? (\n <div className='rounded-md bg-danger-bg px-3 py-2 text-body-sm break-words text-danger'>\n {server.error}\n </div>\n ) : null}\n\n {/* Absent, not disabled, when the engine has no per-server action: a\n greyed-out Reconnect invites the question \"why can't I?\" on every\n visit, where nothing at all reads as \"this engine works differently\". */}\n {canManage ? (\n <div className='flex gap-2'>\n <Button variant='outline' size='sm' disabled={busy} onClick={() => onAct('reconnect')}>\n {busy ? <Spinner className='size-3 text-current' /> : <RotateCw className='size-3' />}\n Reconnect\n </Button>\n <Button\n variant='outline'\n size='sm'\n disabled={busy}\n onClick={() => onAct(disabled ? 'enable' : 'disable')}>\n {disabled ? <Power className='size-3' /> : <PowerOff className='size-3' />}\n {disabled ? 'Enable' : 'Disable'}\n </Button>\n </div>\n ) : null}\n\n <div>\n <h3 className='text-label font-medium text-fg-3'>Tools</h3>\n {!server.tools?.length ? (\n <p className='py-2 text-body-sm text-fg-4'>\n {server.status === 'connected'\n ? 'This server exposes no tools.'\n : 'Tools are listed once the server connects.'}\n </p>\n ) : (\n <ul className='mt-1 flex flex-col'>\n {server.tools.map((t) => (\n <li key={t.name}>\n <button\n type='button'\n onClick={() => onSelectTool(t.name)}\n className='flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-surface-hover'>\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {t.name}\n </span>\n <ChevronRight className='size-3.5 shrink-0 text-fg-4' />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n )\n}\n\nfunction ToolView({ tool }: { tool: McpServerToolInfo }) {\n const annotations = tool.annotations\n return (\n <div className='flex flex-col gap-3'>\n {tool.description ? (\n <p className='text-body-sm whitespace-pre-wrap text-fg-2'>{tool.description}</p>\n ) : (\n <p className='text-body-sm text-fg-4'>This tool carries no description.</p>\n )}\n {annotations ? (\n <div className='flex flex-wrap gap-1.5'>\n {annotations.readOnly ? <Badge variant='success'>read-only</Badge> : null}\n {annotations.destructive ? <Badge variant='danger'>destructive</Badge> : null}\n {annotations.openWorld ? <Badge variant='warning'>open world</Badge> : null}\n </div>\n ) : null}\n {/* Engine-dependent, and said as such: codex returns each tool's full\n JSON Schema, the Agent SDK returns none at all. So this is a real\n section where one exists and an explanation where it doesn't — never a\n silent gap, and never a claim that no engine has them. */}\n {tool.inputSchema !== undefined ? (\n <div>\n <h3 className='text-label font-medium text-fg-3'>Parameters</h3>\n <pre className='mt-1 max-h-64 overflow-auto rounded-md bg-surface px-3 py-2 text-label text-fg-2'>\n {safeSchema(tool.inputSchema)}\n </pre>\n </div>\n ) : (\n <p className={cn('text-label text-fg-4')}>\n Parameters aren’t available: this engine names and describes each tool but reports no\n input schema.\n </p>\n )}\n </div>\n )\n}\n\n/** The schema is an opaque JSON document from another process — pretty-print it,\n * and never let an unserializable value take the dialog down with it. */\nfunction safeSchema(schema: unknown): string {\n try {\n return JSON.stringify(schema, null, 2)\n } catch {\n return String(schema)\n }\n}\n","import { useState } from 'react'\nimport type { SkillInfo } from '@workerdeck/protocol'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader, DialogRow } from '../ui/Dialog.tsx'\n\nexport interface SkillsDialogProps {\n skills: SkillInfo[] | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Insert a skill's opening message into the composer, if the host offers\n * that. Omit and the dialog is read-only. */\n onUse?: (skill: SkillInfo) => void\n}\n\n/** Where the skill came from. The engine's set is open, so an unrecognised\n * scope renders as itself rather than being forced into a bucket. */\nconst SCOPE_LABEL: Record<string, string> = {\n user: 'Personal',\n repo: 'This project',\n system: 'System',\n admin: 'Managed',\n}\n\n/**\n * What this session's engine can do beyond its own tools: the skills it found,\n * grouped by where they came from, with one drilled-down view each.\n *\n * The framing matters more here than in most panels. A skill is **not** a\n * command — the model decides to use one by reading its description, and there\n * is no wire syntax that invokes it. So this screen is a *discovery* surface,\n * and the one action it offers (\"Use this skill\") is honest about being a\n * drafting aid: it types a message for the operator to edit and send.\n *\n * Fed from the session's `skills` event rather than a REST route, because that\n * is the channel the engine refreshes on its own when a skill changes on disk.\n */\nexport function SkillsDialog({ skills, open, onOpenChange, onUse }: SkillsDialogProps) {\n const [selected, setSelected] = useState<string | undefined>()\n const skill = skills?.find((s) => s.name === selected)\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) setSelected(undefined)\n onOpenChange(next)\n }}>\n <DialogContent>\n <DialogHeader\n title={skill ? (skill.displayName ?? skill.name) : 'Skills'}\n description={\n skill\n ? skill.name !== (skill.displayName ?? skill.name)\n ? skill.name\n : undefined\n : 'Capabilities the agent can choose to use. Not commands — the model picks them from their descriptions.'\n }\n actions={\n skill ? (\n <Button variant='ghost' size='xs' onClick={() => setSelected(undefined)}>\n <ChevronLeft className='size-3.5' />\n Back\n </Button>\n ) : undefined\n }\n />\n <DialogBody>\n {skill ? (\n <SkillView skill={skill} onUse={onUse} onUsed={() => onOpenChange(false)} />\n ) : (\n <SkillList skills={skills} onSelect={setSelected} />\n )}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction SkillList({\n skills,\n onSelect,\n}: {\n skills: SkillInfo[] | undefined\n onSelect: (name: string) => void\n}) {\n if (!skills) {\n // Not \"none\" — codex can only list skills over a live child, which it does\n // not spawn until the session has something to do. Saying so beats an empty\n // list that reads as \"you have no skills\".\n return (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n Skills are listed once the session connects — send a message first.\n </p>\n )\n }\n if (skills.length === 0) {\n return <p className='py-6 text-center text-body-sm text-fg-4'>This session found no skills.</p>\n }\n const scopes = [...new Set(skills.map((s) => s.scope ?? 'other'))]\n return (\n <div className='flex flex-col gap-4'>\n {scopes.map((scope) => (\n <div key={scope}>\n <h3 className='text-label font-medium text-fg-3'>{SCOPE_LABEL[scope] ?? scope}</h3>\n <ul className='mt-1 flex flex-col'>\n {skills\n .filter((s) => (s.scope ?? 'other') === scope)\n .map((s) => (\n <li key={s.name}>\n <button\n type='button'\n onClick={() => onSelect(s.name)}\n className='flex w-full items-start gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-surface-hover'>\n <span className='min-w-0 flex-1'>\n <span className='block truncate text-body-sm text-fg-1'>\n {s.displayName ?? s.name}\n </span>\n {s.shortDescription ?? s.description ? (\n <span className='block truncate text-label text-fg-4'>\n {s.shortDescription ?? s.description}\n </span>\n ) : null}\n </span>\n {/* Listed but switched off: a different answer from absent,\n and the one an operator hunting for a missing skill needs. */}\n {!s.enabled ? (\n <Badge variant='neutral' className='mt-0.5 shrink-0'>\n off\n </Badge>\n ) : null}\n <ChevronRight className='mt-0.5 size-3.5 shrink-0 text-fg-4' />\n </button>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n )\n}\n\nfunction SkillView({\n skill,\n onUse,\n onUsed,\n}: {\n skill: SkillInfo\n onUse?: (skill: SkillInfo) => void\n onUsed: () => void\n}) {\n return (\n <div className='flex flex-col gap-4'>\n <div>\n {skill.scope ? (\n <DialogRow label='Scope'>{SCOPE_LABEL[skill.scope] ?? skill.scope}</DialogRow>\n ) : null}\n <DialogRow label='Status'>\n <Badge variant={skill.enabled ? 'success' : 'neutral'} dot>\n {skill.enabled ? 'enabled' : 'disabled'}\n </Badge>\n </DialogRow>\n </div>\n {skill.description ? (\n <div>\n <h3 className='text-label font-medium text-fg-3'>Description</h3>\n {/* Shown verbatim because this is the text the MODEL selects on —\n paraphrasing it here would describe a different skill than the one\n the agent is reading about. */}\n <p className='mt-1 text-body-sm whitespace-pre-wrap text-fg-2'>{skill.description}</p>\n </div>\n ) : (\n <p className='text-body-sm text-fg-4'>This skill carries no description.</p>\n )}\n {onUse && skill.enabled ? (\n <div>\n <Button\n variant='outline'\n size='sm'\n onClick={() => {\n onUse(skill)\n onUsed()\n }}>\n Use this skill\n </Button>\n <p className='mt-1.5 text-label text-fg-4'>\n Writes an opening message into the composer for you to edit — it isn’t sent, and there\n is no command that runs a skill directly.\n </p>\n </div>\n ) : null}\n </div>\n )\n}\n","import type { ModelOption } from '@workerdeck/protocol'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectItemText,\n SelectTrigger,\n SelectValue,\n} from '../ui/Select.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface ModelSelectProps {\n /** Models the session can switch to (TranscriptState.models). */\n models: ModelOption[]\n /** The session's current model id (TranscriptState.model), possibly decorated\n * (e.g. \"claude-fable-5[1m]\") — matched leniently against the options.\n * `undefined` selects the list's default row, if it has one. */\n model?: string\n /** `undefined` = back to the CLI's default model. */\n onModelChange: (model?: string) => void\n /** 'toolbar' (default) is the composer's compact borderless trigger;\n * 'form' is a standard field-sized Select for create/settings forms. */\n variant?: 'toolbar' | 'form'\n disabled?: boolean\n className?: string\n}\n\n/** The CLI's supportedModels list leads with a \"Default (recommended)\" row whose value\n * is a sentinel, not a model id — selecting it means \"clear the override\". */\nconst isDefaultOption = (value: string) => value === 'default'\n\n/** Everything before a '[1m]'-style context-window suffix. */\nconst dropVariant = (id: string) => id.replace(/\\[.*\\]$/, '')\n\n/** 'claude-opus-4-8' → \"opus\", 'sonnet' → \"sonnet\". The vendor prefix and the\n * version tail are dropped; what is left is the name a person would say. */\nfunction family(id: string): string {\n const parts = id.toLowerCase().split('-')\n if (parts[0] === 'claude') parts.shift()\n return parts[0] ?? ''\n}\n\n/**\n * Whether a row is the one naming `model`.\n *\n * Three passes, narrowest first, because the rows and the id a session *reports*\n * are written differently: the rows are aliases ('opus[1m]', 'sonnet',\n * 'claude-fable-5[1m]') and a session reports a resolved wire id\n * ('claude-opus-5[1m]'). `resolvedModel` is the server's own answer to this and\n * wins when present; the family fallback covers a server that doesn't send it,\n * which is the difference between the chip reading \"Opus 5\" and reading\n * `claude-opus-5[1m]`. Kept identical to the iOS client's `ModelOption.matches`.\n */\nfunction optionMatches(option: ModelOption, model: string): boolean {\n if (model === option.value || model === option.resolvedModel) return true\n const stripped = dropVariant(model)\n // A row that declares what it resolves to is *authoritative*, including when\n // it disagrees: two rows of the same family (\"Opus 5\" and \"Opus 4.8\") differ\n // only here, so falling through to the family would match both.\n if (option.resolvedModel) return stripped === dropVariant(option.resolvedModel)\n const token = family(stripped)\n return token !== '' && token === family(dropVariant(option.value))\n}\n\n/** Find the option matching a (possibly decorated/aliased) session model id. */\nfunction matchModel(models: ModelOption[], model?: string): ModelOption | undefined {\n if (!model) return undefined\n return models.filter((m) => !isDefaultOption(m.value)).find((m) => optionMatches(m, model))\n}\n\n/** Compact model switcher for the composer toolbar; fed by the `capabilities` event.\n * Rows render CLI-style: bold display name with the model's description beneath. */\nexport function ModelSelect({\n models,\n model,\n onModelChange,\n variant = 'toolbar',\n disabled,\n className,\n}: ModelSelectProps) {\n const selected =\n matchModel(models, model) ?? (model ? undefined : models.find((m) => isDefaultOption(m.value)))\n return (\n <Select\n items={models.map((m) => ({ value: m.value, label: m.displayName }))}\n value={selected?.value ?? null}\n onValueChange={(value) => {\n if (typeof value !== 'string' || value === selected?.value) return\n onModelChange(isDefaultOption(value) ? undefined : value)\n }}\n disabled={disabled}>\n <SelectTrigger\n aria-label='Model'\n className={cn(\n variant === 'toolbar' &&\n 'h-6 max-w-56 border-transparent bg-transparent text-fg-3 hover:bg-surface-hover',\n className,\n )}>\n <span className={cn('truncate', variant === 'toolbar' && 'font-mono text-label')}>\n <SelectValue placeholder={model ?? 'model'} />\n </span>\n </SelectTrigger>\n <SelectContent className='min-w-72'>\n {models.map((m) => (\n <SelectItem key={m.value} value={m.value}>\n <SelectItemText>\n <span className='font-medium'>{m.displayName}</span>\n </SelectItemText>\n {m.description ? <span className='text-label text-fg-4'>{m.description}</span> : null}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n","import type { LucideIcon } from 'lucide-react'\nimport type { PermissionMode } from '@workerdeck/protocol'\nimport { ClipboardList, Code, Hand, ShieldCheck, TriangleAlert, Zap } from 'lucide-react'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectItemText,\n SelectTrigger,\n SelectValue,\n} from '../ui/Select.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport type PermissionModeMeta = {\n value: PermissionMode\n /** The name Claude Code itself uses. */\n label: string\n /** The chip form, for bars where the label shares a line with three other\n * things and \"Bypass permissions\" would eat half of it. */\n shortLabel: string\n /** What the mode actually does — the CLI's own one-liners. */\n description: string\n icon: LucideIcon\n dangerous?: boolean\n}\n\n/**\n * The modes surfaced across UI surfaces (session creation, in-session switcher),\n * ordered by how much of the approval gate they give away.\n *\n * Notably `default` is **\"Manual\"**: the wire value is `default`, but calling it\n * that in the UI conflates a real mode (ask me every time) with \"whatever the\n * server picked\", which is the one confusion a mode chip exists to avoid. The\n * naming, the icons and the summaries are shared with the iOS app on purpose —\n * the two surfaces should read as the same list.\n */\nexport const PERMISSION_MODES: PermissionModeMeta[] = [\n {\n value: 'default',\n label: 'Manual',\n shortLabel: 'Manual',\n description: 'Always ask before making changes',\n icon: Hand,\n },\n {\n value: 'acceptEdits',\n label: 'Accept edits',\n shortLabel: 'Edits',\n description: 'Automatically accept all file edits',\n icon: Code,\n },\n {\n value: 'plan',\n label: 'Plan',\n shortLabel: 'Plan',\n description: 'Create a plan before making changes',\n icon: ClipboardList,\n },\n {\n value: 'auto',\n label: 'Auto',\n shortLabel: 'Auto',\n description: 'Claude handles permission decisions',\n icon: Zap,\n },\n {\n value: 'dontAsk',\n label: \"Don't ask\",\n shortLabel: \"Don't ask\",\n // The CLI's own definition, and the opposite of bypass: it never prompts,\n // and anything not already permitted is denied rather than allowed.\n description: 'Never ask — deny anything not pre-approved',\n icon: ShieldCheck,\n },\n {\n value: 'bypassPermissions',\n label: 'Bypass permissions',\n shortLabel: 'Bypass',\n description: 'Skip every approval — the agent is unsupervised',\n icon: TriangleAlert,\n dangerous: true,\n },\n]\n\nexport const permissionModeMeta = (mode: PermissionMode): PermissionModeMeta | undefined =>\n PERMISSION_MODES.find((m) => m.value === mode)\n\nexport interface PermissionModeSelectProps {\n /** The session's current mode (TranscriptState.permissionMode). */\n mode?: PermissionMode\n onModeChange: (mode: PermissionMode) => void\n /** Restrict what is offered — most of {@link PERMISSION_MODES} is Claude Code\n * vocabulary the other engines have no meaning for. Defaults to all of them;\n * pass the session's `capabilities.permissionModes`. */\n modes?: readonly PermissionMode[]\n /**\n * Whether this session may be switched into `bypassPermissions` at all. The\n * CLI refuses unless the process was spawned for it, so a session that didn't\n * ask up front can never gain it. The row is shown disabled rather than hidden\n * — \"you can't have this here\" is a more useful answer than a row that\n * silently isn't there. `undefined` (an older server) offers it.\n */\n canBypass?: boolean\n /** 'toolbar' (default) is the composer's compact borderless trigger;\n * 'form' is a standard field-sized Select for create/settings forms. */\n variant?: 'toolbar' | 'form'\n disabled?: boolean\n className?: string\n}\n\n/** Permission-mode switcher: compact in the composer toolbar, field-sized in forms. */\nexport function PermissionModeSelect({\n mode,\n onModeChange,\n modes,\n canBypass,\n variant = 'toolbar',\n disabled,\n className,\n}: PermissionModeSelectProps) {\n const dangerous = mode === 'bypassPermissions'\n const offered = modes ? PERMISSION_MODES.filter((m) => modes.includes(m.value)) : PERMISSION_MODES\n const unavailable = (meta: PermissionModeMeta) =>\n meta.value === 'bypassPermissions' && canBypass === false\n return (\n <Select\n items={offered.map((m) => ({ value: m.value, label: m.label }))}\n value={mode ?? null}\n onValueChange={(value) => {\n if (typeof value === 'string' && value !== mode) onModeChange(value as PermissionMode)\n }}\n disabled={disabled}>\n <SelectTrigger\n aria-label='Permission mode'\n className={cn(\n variant === 'toolbar' &&\n 'h-6 max-w-44 border-transparent bg-transparent hover:bg-surface-hover',\n dangerous ? 'text-danger' : variant === 'toolbar' ? 'text-fg-3' : undefined,\n className,\n )}>\n <span className={cn('truncate', variant === 'toolbar' && 'text-label')}>\n <SelectValue placeholder='permissions' />\n </span>\n </SelectTrigger>\n <SelectContent className='min-w-64'>\n {offered.map((m) => {\n const blocked = unavailable(m)\n return (\n <SelectItem key={m.value} value={m.value} disabled={blocked}>\n <SelectItemText>\n <span className='flex items-center gap-2'>\n <m.icon\n className={cn('size-3.5 shrink-0', m.dangerous ? 'text-danger' : 'text-fg-3')}\n />\n <span className={cn('font-medium', m.dangerous && 'text-danger')}>{m.label}</span>\n </span>\n </SelectItemText>\n <span\n className={cn(\n 'pl-5.5 text-label',\n blocked ? 'text-fg-4' : m.dangerous ? 'text-danger/80' : 'text-fg-4',\n )}>\n {blocked ? 'Only available to a session started in this mode' : m.description}\n </span>\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n )\n}\n","import {\n ArrowDownCircle,\n CheckSquare,\n FileDiff,\n FileText,\n FolderSearch,\n Globe,\n Image,\n type LucideIcon,\n MessageCircleQuestion,\n PencilLine,\n Puzzle,\n Search,\n Sparkles,\n SquarePen,\n Terminal,\n UsersRound,\n Wrench,\n} from 'lucide-react'\n\n/**\n * An icon per tool, so a transcript can be skimmed by shape rather than read.\n *\n * The same mapping the iOS app makes, in lucide's vocabulary rather than SF\n * Symbols — the two clients should be recognisably showing the same thing. An\n * unknown tool falls back to a wrench, and an MCP tool (`mcp__server__name`) to\n * the puzzle piece the MCP screens use, because \"which server is this from\" is\n * the useful thing to see at a glance.\n */\nexport function toolIcon(toolName: string): LucideIcon {\n switch (toolName) {\n case 'Bash':\n case 'BashOutput':\n case 'KillShell':\n return Terminal\n case 'Read':\n return FileText\n case 'Write':\n return SquarePen\n case 'Edit':\n case 'MultiEdit':\n case 'NotebookEdit':\n return PencilLine\n case 'Glob':\n return FolderSearch\n case 'Grep':\n return Search\n case 'WebFetch':\n return ArrowDownCircle\n case 'WebSearch':\n return Globe\n case 'Task':\n case 'Agent':\n return UsersRound\n case 'TodoWrite':\n return CheckSquare\n case 'Skill':\n return Sparkles\n case 'AskUserQuestion':\n return MessageCircleQuestion\n // The codex engine's own tool names (see its runner's item mapping).\n case 'CodexCommand':\n return Terminal\n case 'CodexFileChange':\n return FileDiff\n case 'CodexWebSearch':\n return Globe\n case 'CodexImageGeneration':\n case 'CodexImageView':\n return Image\n default:\n return toolName.startsWith('mcp__') ? Puzzle : Wrench\n }\n}\n","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 { 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, useState } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport type { SessionFileInfo } from '@workerdeck/protocol'\nimport type { TranscriptState } from '@workerdeck/react'\nimport { Download } from 'lucide-react'\nimport { CopyButton } from '../ui/CopyButton.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader, DialogRow } from '../ui/Dialog.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { formatBytes, formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { permissionModeMeta } from './PermissionModeSelect.tsx'\n\nexport interface SessionInfoDialogProps {\n state: TranscriptState\n client: WorkerDeckClient\n sessionId: string | undefined\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * What this session *is*: engine, profile, model, mode, where it runs, which\n * credentials it found, and the files it has handed over.\n *\n * The identity half of the session's facts. Context and usage have their own\n * panels — they change every turn and are consulted mid-run, while everything\n * here is fixed at creation and looked up once.\n */\nexport function SessionInfoDialog({\n state,\n client,\n sessionId,\n open,\n onOpenChange,\n}: SessionInfoDialogProps) {\n const session = state.session\n const mode = state.permissionMode ? permissionModeMeta(state.permissionMode) : undefined\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader title='Session info' description={session?.title} />\n <DialogBody>\n <div className='flex flex-col divide-y divide-border'>\n <div className='pb-2'>\n <DialogRow label='Engine'>{state.engine ?? 'claude'}</DialogRow>\n {session?.profile ? (\n <DialogRow label='Profile'>{session.profile}</DialogRow>\n ) : null}\n {state.model ? (\n <DialogRow label='Model' mono>\n {state.model}\n </DialogRow>\n ) : null}\n {mode ? <DialogRow label='Permission mode'>{mode.label}</DialogRow> : null}\n {session?.apiKeySource ? (\n <DialogRow label='Credentials'>{session.apiKeySource}</DialogRow>\n ) : null}\n </div>\n <div className='py-2'>\n {state.cwd ? <CopyRow label='Working directory' value={state.cwd} /> : null}\n {state.sdkSessionId ? (\n <CopyRow label='Engine session id' value={state.sdkSessionId} />\n ) : null}\n {session ? <CopyRow label='Gateway session id' value={session.id} /> : null}\n </div>\n <div className='py-2'>\n {session?.createdAt ? (\n <DialogRow label='Started'>{formatRelativeTime(session.createdAt)}</DialogRow>\n ) : null}\n {session?.numTurns !== undefined ? (\n <DialogRow label='Turns'>{session.numTurns}</DialogRow>\n ) : null}\n <DialogRow label='Cost' mono>\n {formatCost(state.totalCostUsd)}\n </DialogRow>\n </div>\n {/* Only for an engine that has a scratch VFS — for the rest the route\n 404s, and a \"Files\" heading over nothing reads as a failed load. */}\n {state.capabilities.vfs ? (\n <SessionFiles client={client} sessionId={sessionId} open={open} />\n ) : null}\n </div>\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\n/** Long ids are for pasting elsewhere, so give them a copy target. */\nfunction CopyRow({ label, value }: { label: string; value: string }) {\n return (\n <div className='flex items-start justify-between gap-2 py-1.5'>\n <div className='min-w-0 flex-1'>\n <div className='text-label text-fg-3'>{label}</div>\n <div className='mt-0.5 font-mono text-label break-all text-fg-1'>{value}</div>\n </div>\n <CopyButton value={value} aria-label={`Copy ${label.toLowerCase()}`} />\n </div>\n )\n}\n\n/** Files the agent produced inside the session's VFS. Fetched when the panel\n * opens — this is not a live list and nothing pushes changes to it. */\nfunction SessionFiles({\n client,\n sessionId,\n open,\n}: {\n client: WorkerDeckClient\n sessionId: string | undefined\n open: boolean\n}) {\n const [files, setFiles] = useState<SessionFileInfo[] | undefined>()\n const [loading, setLoading] = useState(false)\n\n useEffect(() => {\n if (!open || !sessionId) return\n let cancelled = false\n setLoading(true)\n client\n .listSessionFiles(sessionId)\n .then((list) => {\n if (!cancelled) setFiles(list)\n })\n .catch(() => {\n if (!cancelled) setFiles([])\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [client, sessionId, open])\n\n return (\n <div className='pt-3'>\n <h3 className='text-label font-medium text-fg-3'>Files</h3>\n {loading ? (\n <div className='py-3 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n ) : !files?.length ? (\n <p className='py-2 text-body-sm text-fg-4'>Nothing delivered yet.</p>\n ) : (\n <ul className='mt-1 flex flex-col'>\n {files.map((file) => (\n <li key={file.path}>\n <a\n href={sessionId ? client.sessionFileUrl(sessionId, file.path) : undefined}\n className='flex items-center gap-2 rounded-md px-1 py-1.5 hover:bg-surface-hover'>\n <Download className='size-3.5 shrink-0 text-fg-4' />\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-1'>\n {file.path}\n </span>\n <span className='shrink-0 text-label text-fg-4'>{formatBytes(file.bytes)}</span>\n </a>\n </li>\n ))}\n </ul>\n )}\n </div>\n )\n}\n","import type { SessionStatus } from '@workerdeck/protocol'\nimport type { BadgeProps } from '../ui/Badge.tsx'\n\nexport const STATUS_META: Record<\n SessionStatus,\n { label: string; variant: NonNullable<BadgeProps['variant']>; busy: boolean }\n> = {\n starting: { label: 'Starting', variant: 'info', busy: true },\n running: { label: 'Running', variant: 'info', busy: true },\n awaiting_approval: { label: 'Needs approval', variant: 'warning', busy: true },\n idle: { label: 'Idle', variant: 'success', busy: false },\n // Waiting on a deferred execution: the run is alive but nothing is burning.\n parked: { label: 'Parked', variant: 'accent', busy: false },\n failed: { label: 'Failed', variant: 'danger', busy: false },\n closed: { label: 'Closed', variant: 'neutral', busy: false },\n}\n","import { useEffect, useState, type ReactNode } from 'react'\nimport type { ConnectionState, TranscriptState } from '@workerdeck/react'\nimport type { ContextUsage, RateLimitInfo } from '@workerdeck/protocol'\nimport { RefreshCw, WifiOff } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { ProgressRing } from '../ui/ProgressRing.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { Tip } from '../ui/Tooltip.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatCountdown, formatTokens } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface StatusBarProps {\n state: TranscriptState\n /** @deprecated Pass {@link StatusBarProps.connection}; kept so an embedder\n * still handing over a boolean keeps working. */\n connected?: boolean\n /** How the client is doing at reaching the gateway. A dropped socket wins the\n * status slot: the session status held over a dead socket is a stale reading,\n * and presenting it as a live one is the thing worth avoiding. */\n connection?: ConnectionState\n /**\n * Where the gauges lead. Each one opens the panel that answers *its* question\n * — the two meters measure different things, so sending both to one \"details\"\n * list would be a detour every time. Omit a handler and that gauge stays a\n * read-only tooltip.\n */\n onOpenStatus?: () => void\n onOpenContext?: () => void\n onOpenUsage?: () => void\n /** Trailing slot — the session-actions menu, in the panel's top-right. */\n actions?: ReactNode\n className?: string\n}\n\n/** Ticking clock for reset countdowns — rate_limit events are sparse, so tick locally. */\nfunction useNow(intervalMs = 30_000): number {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n const timer = setInterval(() => setNow(Date.now()), intervalMs)\n return () => clearInterval(timer)\n }, [intervalMs])\n return now\n}\n\nconst utilizationColor = (pct: number) =>\n pct >= 95 ? 'text-danger' : pct >= 80 ? 'text-warning' : 'text-fg-3'\n\n/** The CLI reports category colors as its own theme token names ('inactive',\n * 'promptBorder', ...), not CSS colors — only pass through what CSS can render. */\nconst cssColor = (color: string): string | undefined =>\n typeof CSS !== 'undefined' && CSS.supports('color', color) ? color : undefined\n\nfunction ContextMeter({ usage }: { usage: ContextUsage }) {\n return (\n <Tip\n content={\n <div className='flex min-w-44 flex-col gap-1 py-0.5'>\n {usage.categories.map((c) => (\n <div key={c.name} className='flex items-center gap-2'>\n <span\n className='size-2 shrink-0 rounded-full bg-fg-4'\n style={cssColor(c.color) ? { backgroundColor: c.color } : undefined}\n />\n <span className='flex-1'>{c.name}</span>\n <span className='font-mono text-fg-3'>{formatTokens(c.tokens)}</span>\n </div>\n ))}\n <div className='mt-0.5 flex items-center justify-between gap-2 border-t border-border pt-1'>\n <span>Total</span>\n <span className='font-mono text-fg-3'>\n {formatTokens(usage.totalTokens)} / {formatTokens(usage.maxTokens)} (\n {usage.percentage.toFixed(0)}%)\n </span>\n </div>\n </div>\n }>\n <span\n className={cn(\n 'inline-flex cursor-default items-center gap-1 font-mono text-label',\n utilizationColor(usage.percentage),\n )}>\n Ctx {formatTokens(usage.totalTokens)}\n </span>\n </Tip>\n )\n}\n\nfunction RateLimitMeter({ label, info, now }: { label: string; info: RateLimitInfo; now: number }) {\n // The CLI omits utilization on some updates — show the window without a made-up 0%.\n const pct = info.utilization\n const resetsAtMs = info.resetsAt !== undefined ? info.resetsAt * 1000 : undefined\n return (\n <Tip\n content={\n <div className='flex min-w-36 flex-col gap-1 py-0.5'>\n <div className='flex items-center justify-between gap-2'>\n <span>{label} usage</span>\n <span className='font-mono text-fg-3'>\n {pct !== undefined ? `${pct.toFixed(1)}%` : '—'}\n </span>\n </div>\n {resetsAtMs !== undefined ? (\n <div className='flex items-center justify-between gap-2'>\n <span>Resets in</span>\n <span className='font-mono text-fg-3'>{formatCountdown(resetsAtMs, now)}</span>\n </div>\n ) : null}\n {info.isUsingOverage ? <div className='text-warning'>Using overage</div> : null}\n {info.status === 'rejected' ? <div className='text-danger'>Limit reached</div> : null}\n </div>\n }>\n <span\n className={cn(\n 'inline-flex cursor-default items-center gap-1 font-mono text-label',\n info.status === 'rejected' ? 'text-danger' : utilizationColor(pct ?? 0),\n )}>\n <ProgressRing value={pct ?? 0} />\n {label}\n {pct !== undefined ? ` ${pct.toFixed(0)}%` : ''}\n {resetsAtMs !== undefined ? (\n <span className='text-fg-4'>· {formatCountdown(resetsAtMs, now)}</span>\n ) : null}\n </span>\n </Tip>\n )\n}\n\n/** A gauge that leads somewhere. `undefined` handler leaves it inert, so an\n * embedder that mounts no panels doesn't get buttons that do nothing. */\nfunction Slot({ onClick, hint, children }: { onClick?: () => void; hint: string; children: ReactNode }) {\n if (!onClick) return <>{children}</>\n return (\n <button\n type='button'\n onClick={onClick}\n aria-label={hint}\n className='rounded-md px-1 py-0.5 transition-colors outline-none hover:bg-surface-hover focus-visible:bg-surface-hover'>\n {children}\n </button>\n )\n}\n\nexport function StatusBar({\n state,\n connected,\n connection,\n onOpenStatus,\n onOpenContext,\n onOpenUsage,\n actions,\n className,\n}: StatusBarProps) {\n const meta = STATUS_META[state.status]\n const now = useNow()\n const session = state.rateLimits?.five_hour\n const weekly = state.rateLimits?.seven_day\n const link: ConnectionState = connection ?? (connected === false ? 'reconnecting' : 'live')\n return (\n <div\n data-slot='status-bar'\n className={cn(\n 'flex items-center gap-2 border-b border-border bg-surface px-3 py-1.5',\n className,\n )}>\n {/* One slot, two meanings: connection trouble wins it, because a session\n status shown over a dead socket is a stale reading presented as a live\n one. Tapping it opens the session's own facts — where it runs, on what,\n with which credentials — which is the question a status prompts. */}\n <Slot onClick={onOpenStatus} hint='Session info'>\n {link === 'live' ? (\n <Badge variant={meta.variant} dot={!meta.busy}>\n {meta.busy ? <Spinner className='size-3 text-current' /> : null}\n {meta.label}\n </Badge>\n ) : (\n <Badge variant={link === 'offline' ? 'danger' : 'warning'} dot={false}>\n {link === 'offline' ? (\n <WifiOff className='size-3 text-current' />\n ) : (\n <RefreshCw className='size-3 animate-spin text-current' />\n )}\n {link === 'offline' ? 'Offline' : 'Reconnecting…'}\n </Badge>\n )}\n </Slot>\n {/* Never a 0% meter for an engine that doesn't measure: an absent reading\n and a full window are not the same claim. */}\n {state.capabilities.contextUsage && state.contextUsage ? (\n <Slot onClick={onOpenContext} hint='Context breakdown'>\n <ContextMeter usage={state.contextUsage} />\n </Slot>\n ) : null}\n {session || weekly ? (\n <Slot onClick={onOpenUsage} hint='Plan usage'>\n <span className='inline-flex items-center gap-2'>\n {session ? <RateLimitMeter label='Session' info={session} now={now} /> : null}\n {weekly ? <RateLimitMeter label='Weekly' info={weekly} now={now} /> : null}\n </span>\n </Slot>\n ) : null}\n <span className='flex-1' />\n <span className='font-mono text-label text-fg-3'>{formatCost(state.totalCostUsd)}</span>\n {actions}\n </div>\n )\n}\n","import type { HTMLAttributes, ReactNode } from 'react'\nimport { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom'\nimport { ArrowDown } from 'lucide-react'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\n\nexport interface ConversationProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n children: ReactNode\n}\n\n/** Scroll container that stays pinned to the bottom while streaming, unless the user\n * scrolls up — plus a floating scroll-to-bottom button. */\nexport function Conversation({ className, children, ...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='smooth'\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-3xl flex-col gap-4 px-4 py-4', className)}\n {...props}>\n {children}\n </StickToBottom.Content>\n )\n}\n\nexport function ConversationScrollButton({ className }: { className?: string }) {\n const { isAtBottom, scrollToBottom } = useStickToBottomContext()\n if (isAtBottom) return null\n return (\n <Button\n variant='outline'\n size='icon'\n aria-label='Scroll to bottom'\n data-slot='conversation-scroll-button'\n className={cn(\n 'absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-(--shadow-md)',\n className,\n )}\n onClick={() => void scrollToBottom()}>\n <ArrowDown className='size-4' />\n </Button>\n )\n}\n","import type { TranscriptItem } from '@workerdeck/react'\nimport { Download, FileText } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatBytes } from '../../lib/format.ts'\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\n/** Three-dot pulse shown while the assistant hasn't produced output yet. */\nexport function Loader({ label, className }: { label?: string; className?: string }) {\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/** One chat turn row. User messages sit right in a bubble; assistant content is flat,\n * full-width (the AI-chat convention — assistant output is the page, user input is quoted). */\nexport function Message({ from, className, ...props }: MessageProps) {\n return (\n <div\n data-slot='message'\n data-from={from}\n className={cn(\n 'flex w-full flex-col gap-1',\n from === 'user' ? 'items-end' : 'items-start',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport function MessageContent({\n className,\n ...props\n}: 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 'in-data-[from=user]:max-w-[85%] in-data-[from=user]:rounded-lg in-data-[from=user]:rounded-br-sm',\n 'in-data-[from=user]:bg-accent-bg in-data-[from=user]:px-3 in-data-[from=user]:py-2',\n 'in-data-[from=user]:whitespace-pre-wrap',\n 'in-data-[from=assistant]:w-full',\n 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(\n 'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',\n className,\n )}>\n {children}\n </Streamdown>\n )\n },\n (prev, next) =>\n prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className,\n)\n","import { useEffect, useRef, 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 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'\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\nexport function ToolCallCard({ item, hostImage, className }: ToolCallCardProps) {\n const [open, setOpen] = useState(false)\n const [fullResult, setFullResult] = useState(false)\n const imagePath = imagePathOf(item)\n const status = item.status ?? (item.result === undefined ? 'running' : 'settled')\n const badge = STATE_BADGE[status]\n const isError = status === 'failed' || item.result?.isError === true\n const Icon = toolIcon(item.name)\n\n const resultText = item.result?.text ?? ''\n const truncated = !fullResult && resultText.length > RESULT_PREVIEW_CHARS\n const shownResult = truncated ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText\n\n 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 {/* The picture is the point of the call — shown without expanding, the\n way the tool's own output would be if the engine had sent bytes. */}\n {imagePath && hostImage ? (\n <HostImage path={imagePath} load={hostImage} />\n ) : null}\n {open ? (\n <div className='flex flex-col gap-2 border-t border-border p-2.5'>\n <CodeBlock code={JSON.stringify(item.input, null, 2)} label='Parameters' />\n {item.logs?.length ? (\n <CodeBlock code={item.logs.join('\\n')} label='Logs' />\n ) : null}\n {item.result !== undefined ? (\n <div>\n <CodeBlock\n code={shownResult || '(empty result)'}\n label={isError ? 'Error' : 'Result'}\n className={cn(isError && 'border-danger/40 [&_pre]:text-danger')}\n />\n {truncated ? (\n <button\n type='button'\n className='mt-1 text-label text-fg-3 underline-offset-2 hover:underline'\n onClick={() => setFullResult(true)}>\n Show all {resultText.length.toLocaleString()} chars\n </button>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null}\n </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 { MessageAttachment } from '@workerdeck/protocol'\nimport type { TranscriptItem, TranscriptState } from '@workerdeck/react'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatDuration } 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'\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}: {\n item: TranscriptItem\n fileUrl?: (path: string) => string\n attachmentUrl?: (attachmentId: string) => string\n hostImage?: (path: string) => Promise<string | undefined>\n}) {\n switch (item.kind) {\n case 'user':\n return (\n <Message from='user'>\n {item.attachments?.length ? (\n <SentAttachments attachments={item.attachments} attachmentUrl={attachmentUrl} />\n ) : null}\n {/* A photo can be the whole message — an empty bubble under it says nothing. */}\n {item.text ? (\n <MessageContent>\n <PromptTokenText text={item.text} />\n </MessageContent>\n ) : null}\n </Message>\n )\n case 'assistant_text':\n return (\n <Message from='assistant'>\n <MessageContent>\n <Response streaming={item.streaming}>{item.text}</Response>\n </MessageContent>\n </Message>\n )\n case 'thinking':\n return <Reasoning isStreaming={item.id === 'streaming-thinking'}>{item.text}</Reasoning>\n case 'tool_call':\n return <ToolCallCard item={item} hostImage={hostImage} />\n case 'turn_result':\n return <TurnResultRow item={item} />\n case 'notice':\n return <NoticeRow item={item} />\n case 'file_delivered':\n return <FileCard item={item} href={fileUrl?.(item.path)} />\n default:\n return null\n }\n}\n\n/** 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-end 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/** Rows produced inside a subagent (`parentToolUseId != null`) are stepped in\n * behind a rule, so a Task's own output reads as belonging to the tool call\n * above it rather than as the main thread carrying on. */\nfunction nestedClass(item: TranscriptItem): string | undefined {\n const nested = 'parentToolUseId' in item && item.parentToolUseId != null\n return nested ? 'border-l-2 border-border pl-3' : undefined\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 className?: string\n}\n\nexport function Transcript({\n state,\n fileUrl,\n attachmentUrl,\n canBrowseFiles,\n hostImage,\n className,\n}: TranscriptProps) {\n return (\n <Conversation className={className}>\n <ConversationContent>\n {state.items.length === 0 && state.status !== 'starting' ? (\n <SessionEmptyState\n cwd={state.cwd}\n hasCommands={!!state.commands?.length}\n hasSkills={!!state.skills?.some((s) => s.enabled)}\n canBrowseFiles={canBrowseFiles}\n />\n ) : (\n state.items.map((item) => (\n <div key={`${item.kind}:${item.id}`} className={nestedClass(item)}>\n <TranscriptItemView\n item={item}\n fileUrl={fileUrl}\n attachmentUrl={attachmentUrl}\n hostImage={hostImage}\n />\n </div>\n ))\n )}\n {showLoader(state) ? (\n <Loader label={state.status === 'starting' ? 'Starting session…' : undefined} />\n ) : null}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { ProfileEngine, RateLimitInfo } from '@workerdeck/protocol'\nimport { RotateCcw } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Dialog, DialogBody, DialogContent, DialogHeader } from '../ui/Dialog.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport {\n formatAgoPrecise,\n formatCost,\n formatCountdown,\n formatRateLimitWindowLong,\n rateLimitWindowSeconds,\n} from '../../lib/format.ts'\n\nexport interface UsageDialogProps {\n /** Windows in reading order — session, weekly, then per-model weeklies. */\n rateLimits: Array<{ key: string; info: RateLimitInfo }>\n /** claude.ai plan behind the windows ('max', 'pro', …), when there is one. */\n subscriptionType?: string\n engine: ProfileEngine\n totalCostUsd: number\n /** Local receipt time of the last window update. `rate_limit` events are one\n * per turn at best, so a stale reading is normal and worth saying out loud. */\n updatedAt?: number\n open: boolean\n onOpenChange: (open: boolean) => void\n}\n\n/** Ticking clock — the countdowns and the pace markers both move with it, and a\n * minute is the finest resolution either of them prints. */\nfunction useMinuteClock(open: boolean): number {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n if (!open) return\n setNow(Date.now())\n const timer = setInterval(() => setNow(Date.now()), 60_000)\n return () => clearInterval(timer)\n }, [open])\n return now\n}\n\nconst usageTint = (pct: number) => (pct >= 90 ? 'bg-danger' : pct >= 70 ? 'bg-warning' : 'bg-accent')\n\n/**\n * The plan's rate-limit windows, spelled out: how much of each is used, how that\n * compares to the pace that would spend the window exactly, and when it resets.\n *\n * The pace marker is the point. A bar alone says \"17% used\", which is only\n * alarming or reassuring once you know how far into the week you are — so every\n * window draws a tick at the elapsed share of its duration. Left of the tick is\n * under budget, right of it is ahead of it. The duration comes from the window\n * key (5h, 7d) because the CLI reports a reset time and a percentage and never a\n * duration; a window whose key doesn't say gets no marker rather than a guessed one.\n */\nexport function UsageDialog({\n rateLimits,\n subscriptionType,\n engine,\n totalCostUsd,\n updatedAt,\n open,\n onOpenChange,\n}: UsageDialogProps) {\n const now = useMinuteClock(open)\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader\n title='Usage'\n description={engine === 'claude' ? 'Claude Code' : engine}\n actions={\n // The CLI reports a tier ('max'), never the multiplier a\n // subscription page shows — so this says \"Max\" and stops there.\n subscriptionType ? (\n <Badge variant='accent' className='mt-0.5 shrink-0 capitalize'>\n {subscriptionType}\n </Badge>\n ) : null\n }\n />\n <DialogBody>\n {rateLimits.length === 0 ? (\n <p className='py-6 text-center text-body-sm text-fg-4'>\n {engine === 'claude'\n ? 'This session reports no plan windows — API-key sessions have none, and a subscription session reports them once a turn has run.'\n : `Plan windows are a claude.ai subscription thing; this session runs on the ${engine} engine.`}\n </p>\n ) : (\n <div className='flex flex-col gap-5'>\n {rateLimits.map(({ key, info }) => (\n <UsageWindow key={key} windowKey={key} info={info} now={now} />\n ))}\n </div>\n )}\n <div className='mt-5 flex items-baseline justify-between gap-4 border-t border-border pt-3'>\n <span className='text-label text-fg-3'>This session has cost</span>\n <span className='font-mono text-body-sm text-fg-1'>{formatCost(totalCostUsd)}</span>\n </div>\n {updatedAt ? (\n <p className='mt-2 text-label text-fg-4'>Updated {formatAgoPrecise(updatedAt, now)}</p>\n ) : null}\n </DialogBody>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction UsageWindow({\n windowKey,\n info,\n now,\n}: {\n windowKey: string\n info: RateLimitInfo\n now: number\n}) {\n const utilization = info.utilization ?? 0\n const resetsAtMs = info.resetsAt !== undefined ? info.resetsAt * 1000 : undefined\n // Share of the window already elapsed — where usage *would* be if it were\n // spent evenly. Needs both a duration (from the key) and a reset time.\n const duration = rateLimitWindowSeconds(windowKey)\n const remaining = resetsAtMs !== undefined ? (resetsAtMs - now) / 1000 : undefined\n const pace =\n duration !== undefined && remaining !== undefined && remaining > 0 && remaining < duration\n ? (duration - remaining) / duration\n : undefined\n return (\n <div>\n <div className='flex items-baseline justify-between gap-3'>\n <span className='truncate text-body-sm text-fg-1'>\n {formatRateLimitWindowLong(windowKey)}\n </span>\n <span\n className={cn(\n 'shrink-0 font-mono text-body-sm font-medium',\n info.status === 'rejected' ? 'text-danger' : 'text-fg-1',\n )}>\n {utilization.toFixed(0)}% used\n </span>\n </div>\n <div className='relative mt-2 h-2 rounded-full bg-border'>\n <div\n className={cn('h-full rounded-full', usageTint(utilization))}\n // A floor, so a barely-touched window still shows a mark instead of\n // reading as missing data.\n style={{ width: `${Math.min(100, Math.max(2, utilization))}%` }}\n />\n {pace !== undefined ? (\n <span\n aria-hidden\n title='Spent evenly, usage would be here by now'\n className='absolute -top-1 h-4 w-0.5 -translate-x-1/2 rounded-full bg-fg-1'\n style={{ left: `${Math.min(100, Math.max(0, pace * 100))}%` }}\n />\n ) : null}\n </div>\n <div className='mt-1.5 flex items-center gap-3 text-label text-fg-4'>\n {resetsAtMs !== undefined ? (\n <span className='inline-flex items-center gap-1'>\n <RotateCcw className='size-3' /> Resets in {formatCountdown(resetsAtMs, now)}\n </span>\n ) : null}\n {info.isUsingOverage ? <span className='text-warning'>overage</span> : null}\n {info.status === 'rejected' ? <span className='text-danger'>limit reached</span> : null}\n </div>\n </div>\n )\n}\n","import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport { PROTOCOL_VERSION } from '@workerdeck/protocol'\nimport {\n rateLimitWindows,\n useAttachments,\n useClaudeSession,\n useHostFileSearch,\n useToolCallHost,\n type ProducedFileRef,\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 { PermissionModeSelect } from './PermissionModeSelect.tsx'\nimport { PermissionPrompt } from './PermissionPrompt.tsx'\nimport { QuestionPrompt, parseUserQuestions } from './QuestionPrompt.tsx'\nimport { SessionInfoDialog } from './SessionInfoDialog.tsx'\nimport { StatusBar } from './StatusBar.tsx'\nimport { Transcript } from './Transcript.tsx'\nimport { UsageDialog } from './UsageDialog.tsx'\n\nexport interface SessionPanelProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n /**\n * Optional slot rendered at the top, above the status bar.\n *\n * Pass a **function** to take the session-actions (`⋯`) menu into your own\n * chrome: it is called with the menu element, and wherever you put it is\n * where it lives — the status bar then renders without it, so it never\n * appears twice. Pass a plain node (or nothing) and the menu stays in the\n * status bar's trailing slot.\n *\n * The seam exists because the menu can only be *built* here — it needs the\n * capability record, the host-file verdict and the panel's own dialog state —\n * but an embedder with a real header usually wants it up there with the rest\n * of the session's controls, not stranded on the status line.\n */\n header?: ReactNode | ((slots: { actions: ReactNode }) => ReactNode)\n className?: string\n}\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. */\ntype Panel = 'info' | 'context' | 'usage' | 'mcp' | 'files' | 'skills'\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({ client, sessionId, header, className }: SessionPanelProps) {\n // Rejected commands (the CLI refusing a permission-mode switch, say) render INSIDE\n // the panel rather than through `toast`. The panel does not mount a `Toaster`, and\n // an embedder that doesn't either would drop the only signal that a command failed\n // — the select would just \"not stick\". An error channel a host can lose by omission\n // is not an error channel.\n const [protocolError, setProtocolError] = useState<string | undefined>(undefined)\n const [panel, setPanel] = useState<Panel | undefined>()\n const {\n state,\n connection,\n protocolMismatch,\n models,\n effectiveModel,\n handle,\n send,\n approve,\n deny,\n interrupt,\n setModel,\n setPermissionMode,\n reconnectNow,\n } = useClaudeSession(client, sessionId, { onProtocolError: setProtocolError })\n // Callers are told to remount on a session switch, but a changed prop must not leave\n // the previous session's failure on screen.\n useEffect(() => setProtocolError(undefined), [sessionId])\n // A tab that was in the background has been sitting out the reconnect backoff;\n // coming back to it is exactly when waiting the rest of it out is wrong.\n useEffect(() => {\n const onVisible = () => {\n if (document.visibilityState === 'visible') reconnectNow()\n }\n document.addEventListener('visibilitychange', onVisible)\n return () => document.removeEventListener('visibilitychange', onVisible)\n }, [reconnectNow])\n // Host server-bridged tool calls (provider-engine sessions) in this tab, on the\n // SAME handle the panel attached with — the bridge asks the first attached\n // client. Free for Claude sessions: the guest loads lazily on the first call,\n // which for them never comes.\n useToolCallHost(handle)\n const capabilities = state.capabilities\n const busy = state.status === 'running' || state.status === 'awaiting_approval'\n const ended = state.status === 'failed' || state.status === 'closed'\n const attachments = useAttachments(client, sessionId, {\n capabilities,\n engine: state.engine,\n })\n // Rooted at the session's cwd, which arrives with the snapshot — so `@` is\n // inert for the moment before it does, and stays inert on a gateway that\n // serves no host files.\n const hostFiles = useHostFileSearch(client, state.cwd)\n const windows = useMemo(() => rateLimitWindows(state), [state])\n // Reads a picture the engine left on the host (codex's `image_gen` reports a\n // path, never bytes). Stable and memoized per path: transcript rows re-render\n // on every delta, and a fresh function would re-fetch each time.\n const hostImage = useHostImage(client, sessionId, state.producedFiles)\n const composerRef = useRef<ComposerHandle>(null)\n\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 setPanel('mcp')\n return\n }\n }\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={() => setPanel('context')}>\n <ChartPie className='size-3.5 text-fg-3' /> Context\n </MenuItem>\n ) : null}\n {capabilities.rateLimits ? (\n <MenuItem onClick={() => setPanel('usage')}>\n <Gauge className='size-3.5 text-fg-3' /> Usage\n </MenuItem>\n ) : null}\n <MenuItem onClick={() => setPanel('info')}>\n <Info className='size-3.5 text-fg-3' /> Session info\n </MenuItem>\n {capabilities.mcpStatus ? (\n <MenuItem onClick={() => setPanel('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={() => setPanel('skills')}>\n <Sparkles className='size-3.5 text-fg-3' /> Skills\n </MenuItem>\n ) : null}\n {hostFiles.available ? (\n <MenuItem onClick={() => setPanel('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 const headerTakesActions = typeof header === 'function'\n\n return (\n <div\n data-slot='session-panel'\n className={cn('flex h-full min-h-0 flex-col overflow-hidden bg-bg', className)}>\n {headerTakesActions ? header({ actions: actionsMenu }) : header}\n <StatusBar\n state={state}\n connection={connection}\n onOpenStatus={() => setPanel('info')}\n onOpenContext={() => setPanel('context')}\n onOpenUsage={() => setPanel('usage')}\n actions={headerTakesActions ? undefined : actionsMenu}\n />\n {protocolMismatch !== undefined ? (\n <Notice level='warning'>\n Server speaks protocol v{protocolMismatch}, this build renders v{PROTOCOL_VERSION}. Some\n events may not render.\n </Notice>\n ) : null}\n {protocolError ? (\n <Notice level='error' onDismiss={() => setProtocolError(undefined)}>\n {protocolError}\n </Notice>\n ) : null}\n <Transcript\n state={state}\n fileUrl={sessionId ? (path) => client.sessionFileUrl(sessionId, path) : undefined}\n attachmentUrl={sessionId ? (id) => client.attachmentUrl(sessionId, id) : undefined}\n canBrowseFiles={hostFiles.available}\n hostImage={hostImage}\n />\n {/* An engine with no approval channel never raises these, but a stale\n pending request from a replayed log would still render — the record is\n the authority on whether an approval UI means anything here. */}\n {capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? (\n <div className='px-3 pb-2'>\n <div className='mx-auto flex w-full max-w-3xl flex-col gap-2'>\n {state.pendingApprovals.map((request) =>\n request.toolName === 'AskUserQuestion' &&\n parseUserQuestions(request.input).length > 0 ? (\n <QuestionPrompt\n key={request.id}\n request={request}\n onAnswer={approve}\n onDismiss={(id) => deny(id, 'Question dismissed by user')}\n />\n ) : (\n <PermissionPrompt\n key={request.id}\n request={request}\n onApprove={approve}\n onDeny={deny}\n />\n ),\n )}\n </div>\n </div>\n ) : null}\n <Composer\n ref={composerRef}\n onSend={handleSend}\n onInterrupt={interrupt}\n busy={busy}\n disabled={ended || !sessionId}\n commands={capabilities.slashCommands ? commands : undefined}\n skills={capabilities.skillsList ? state.skills : undefined}\n attachments={attachments}\n onSearchFiles={\n hostFiles.available\n ? (query, options) => hostFiles.search(query, { ...options, limit: 8 })\n : undefined\n }\n toolbar={\n <>\n {/* Codex reports no `capabilities` event, so its models arrive from\n the profile catalog instead — without that fallback its picker\n would be permanently empty and the session unswitchable. */}\n {models.length ? (\n <ModelSelect\n models={models}\n model={effectiveModel}\n onModelChange={setModel}\n disabled={ended}\n />\n ) : null}\n {state.permissionMode ? (\n <PermissionModeSelect\n mode={state.permissionMode}\n onModeChange={setPermissionMode}\n // Only what this engine implements — the rest would come back as\n // a protocol_error.\n modes={capabilities.permissionModes}\n canBypass={state.session?.canBypassPermissions}\n disabled={ended}\n />\n ) : null}\n </>\n }\n />\n\n <SessionInfoDialog\n state={state}\n client={client}\n sessionId={sessionId}\n open={panel === 'info'}\n onOpenChange={(next) => setPanel(next ? 'info' : undefined)}\n />\n <ContextDialog\n usage={state.contextUsage}\n open={panel === 'context'}\n onOpenChange={(next) => setPanel(next ? 'context' : undefined)}\n />\n <UsageDialog\n rateLimits={windows}\n subscriptionType={state.subscriptionType}\n engine={state.engine ?? 'claude'}\n totalCostUsd={state.totalCostUsd}\n updatedAt={state.rateLimitsUpdatedAt}\n open={panel === 'usage'}\n onOpenChange={(next) => setPanel(next ? 'usage' : undefined)}\n />\n <McpDialog\n client={client}\n sessionId={sessionId}\n canManageServers={capabilities.mcpServerActions}\n open={panel === 'mcp'}\n onOpenChange={(next) => setPanel(next ? 'mcp' : undefined)}\n />\n <SkillsDialog\n skills={state.skills}\n open={panel === 'skills'}\n onOpenChange={(next) => setPanel(next ? 'skills' : undefined)}\n // Drafts into the composer; the operator sends it. There is no engine\n // call that runs a skill, so there is nothing else this button could do.\n onUse={(skill) => composerRef.current?.insertText(skillPrompt(skill))}\n />\n <HostFilesDialog\n client={client}\n cwd={state.cwd}\n open={panel === 'files'}\n onOpenChange={(next) => setPanel(next ? 'files' : undefined)}\n />\n </div>\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-3xl 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","import { useEffect, useRef, type MouseEvent as ReactMouseEvent } from 'react'\nimport { isDirty, type OpenFile } from '@workerdeck/react'\nimport { X } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Tip, TooltipProvider } from '../ui/Tooltip.tsx'\nimport { formatBytes } from '../../lib/format.ts'\n\nexport interface EditorTabsProps {\n files: OpenFile[]\n activePath?: string\n onActivate: (path: string) => void\n onClose: (path: string) => void\n className?: string\n}\n\n/**\n * The open-file tab strip.\n *\n * Hand-rolled rather than built on `@base-ui/react`'s `Tabs`: a VS Code tab\n * carries a close button, and a `<button>` inside a `<button>` is invalid HTML —\n * getting the primitive to render something else costs more than the roving\n * tabindex it would have provided. So that part is here, explicitly, along with\n * the two affordances that actually make a tab strip feel right: middle-click to\n * close, and the active tab scrolling itself into view.\n *\n * Deliberately state-free. Which files are open, which is focused and what\n * closing does are all decided by `useOpenFiles`.\n */\nexport function EditorTabs({ files, activePath, onActivate, onClose, className }: EditorTabsProps) {\n return (\n // Grouped, so moving along the strip shows each tab's path immediately\n // instead of re-serving the open delay on every tab.\n <TooltipProvider delay={500} closeDelay={0}>\n <div\n data-slot='editor-tabs'\n role='tablist'\n aria-label='Open files'\n className={cn(\n 'flex shrink-0 items-stretch overflow-x-auto border-b border-border bg-surface',\n className,\n )}>\n {files.map((file) => (\n <Tab\n key={file.path}\n file={file}\n active={file.path === activePath}\n onActivate={() => onActivate(file.path)}\n onClose={() => onClose(file.path)}\n onArrow={(direction) => {\n const index = files.findIndex((f) => f.path === file.path)\n const next = files[index + direction]\n if (next) onActivate(next.path)\n }}\n />\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction Tab({\n file,\n active,\n onActivate,\n onClose,\n onArrow,\n}: {\n file: OpenFile\n active: boolean\n onActivate: () => void\n onClose: () => void\n onArrow: (direction: 1 | -1) => void\n}) {\n const dirty = isDirty(file)\n const ref = useRef<HTMLDivElement>(null)\n // Opening a file from the tree can push the new tab off the end of the strip;\n // the point of opening it was to look at it. `nearest` scrolls the minimum, so\n // a tab already on screen stays exactly where it is.\n useEffect(() => {\n if (active) ref.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n }, [active])\n\n const onAuxClick = (event: ReactMouseEvent) => {\n // Middle click. `auxclick` rather than `mousedown` so it matches how every\n // other middle-click target on the platform behaves.\n if (event.button !== 1) return\n event.preventDefault()\n onClose()\n }\n\n return (\n // The tab *is* the trigger (`render`), and it carries the full path — which\n // is why the viewer no longer spends a whole row on a line of monospace\n // nobody reads. No `title` alongside it: the browser's native tooltip would\n // show up underneath this one.\n <Tip\n side='bottom'\n render={\n <div\n ref={ref}\n role='tab'\n tabIndex={active ? 0 : -1}\n aria-selected={active}\n onClick={onActivate}\n onAuxClick={onAuxClick}\n onKeyDown={(event) => {\n if (event.key === 'ArrowRight') onArrow(1)\n else if (event.key === 'ArrowLeft') onArrow(-1)\n else if (event.key === 'Enter' || event.key === ' ') onActivate()\n else return\n event.preventDefault()\n }}\n className={cn(\n 'group flex min-w-0 shrink-0 cursor-pointer items-center gap-1.5 border-r border-border px-3 py-1.5 transition-colors',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent focus-visible:ring-inset',\n active ? 'bg-bg text-fg-1' : 'text-fg-3 hover:bg-surface-hover hover:text-fg-2',\n )}\n />\n }\n content={\n <span className='flex flex-col gap-0.5'>\n <span className='font-mono break-all'>{file.path}</span>\n {file.bytes !== undefined ? (\n <span className='text-fg-4'>{formatBytes(file.bytes)}</span>\n ) : null}\n </span>\n }>\n <span className={cn('max-w-40 truncate font-mono text-label', dirty && 'italic')}>\n {file.name}\n </span>\n {/* Errors are the one state the strip shows, because a failed tab\n otherwise looks identical to a loaded one until you focus it. */}\n {file.status === 'error' ? <span className='shrink-0 text-danger'>!</span> : null}\n <button\n type='button'\n aria-label={dirty ? `Close ${file.name} (unsaved changes)` : `Close ${file.name}`}\n onClick={(event) => {\n // Without this the click also activates the tab being closed, which\n // fights the reducer's focus-the-neighbour rule.\n event.stopPropagation()\n onClose()\n }}\n className={cn(\n 'shrink-0 rounded p-0.5 text-fg-4 transition-opacity hover:bg-surface-hover hover:text-fg-1',\n // Always reachable by keyboard and on touch; only *shown* on hover for\n // the tab you are pointing at, as VS Code does.\n active || dirty\n ? 'opacity-70'\n : 'opacity-0 group-hover:opacity-70 focus-visible:opacity-100',\n )}>\n {/* VS Code's move: a dirty tab shows a dot where the ✕ goes, and the ✕\n comes back when you point at it — so unsaved work is visible at rest\n without taking away the way to close it. */}\n <span className={cn('block', dirty && 'group-hover:hidden')}>\n {dirty ? <span className='block size-3 rounded-full bg-fg-2' /> : <X className='size-3' />}\n </span>\n {dirty ? (\n <span className='hidden group-hover:block'>\n <X className='size-3' />\n </span>\n ) : null}\n </button>\n </Tip>\n )\n}\n","import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'\nimport type { UseHostFileSearchResult, UseHostFileTreeResult } from '@workerdeck/react'\nimport type { HostDirEntry, HostFileMatch } from '@workerdeck/protocol'\nimport {\n ChevronRight,\n File,\n Folder,\n FolderOpen,\n Link2,\n PanelLeftClose,\n RefreshCw,\n Search,\n X,\n} from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\n\nexport interface FileTreeProps {\n /** Tree state from `useHostFileTree` — this component holds none of its own. */\n tree: UseHostFileTreeResult\n /** Optional search from `useHostFileSearch`; omit and the box is not offered. */\n search?: UseHostFileSearchResult\n /** Path of the focused file, highlighted in the tree. */\n activePath?: string\n onOpenFile: (path: string) => void\n /** Offered as a button in the header when given. */\n onCollapse?: () => void\n style?: CSSProperties\n className?: string\n}\n\n/** How far one level of nesting indents, in pixels. Inline rather than a Tailwind\n * class because depth is a number at runtime and `pl-${n}` is not a class. */\nconst INDENT = 12\n\n/**\n * The workspace's left rail: an expandable tree of the session's project,\n * with a search box over the same fuzzy route `@file` completion uses.\n *\n * Presentational by construction — every piece of state it renders comes from\n * the hooks in `@workerdeck/react`, and the only thing it owns is the search\n * query, which is the text in its own input.\n *\n * Searching replaces the tree with matches rather than filtering it: the route\n * answers with paths from all over the project, and threading those back into\n * tree positions would mean expanding a dozen directories to show six results.\n */\nexport function FileTree({\n tree,\n search,\n activePath,\n onOpenFile,\n onCollapse,\n style,\n className,\n}: FileTreeProps) {\n const [query, setQuery] = useState('')\n const [matches, setMatches] = useState<HostFileMatch[] | undefined>()\n const searching = query.trim().length > 0\n\n // Debounced, and only while there is something to search for — an empty box\n // means \"show me the tree again\", not \"match everything\".\n useEffect(() => {\n if (!search?.available) return\n const q = query.trim()\n if (!q) {\n setMatches(undefined)\n return\n }\n const controller = new AbortController()\n const timer = setTimeout(() => {\n void search.search(q, { limit: 60, signal: controller.signal }).then((found) => {\n if (!controller.signal.aborted) setMatches(found)\n })\n }, 150)\n return () => {\n controller.abort()\n clearTimeout(timer)\n }\n }, [search, query])\n\n return (\n <div\n data-slot='file-tree'\n style={style}\n className={cn('flex min-h-0 min-w-0 flex-col bg-surface', className)}>\n <div className='flex items-center gap-1 px-2 pt-2'>\n {search?.available ? (\n <div className='relative min-w-0 flex-1'>\n <Search className='absolute top-1/2 left-2 size-3.5 -translate-y-1/2 text-fg-4' />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder='Search files…'\n className='h-7 pr-7 pl-7 text-label'\n spellCheck={false}\n />\n {searching ? (\n <button\n type='button'\n onClick={() => setQuery('')}\n aria-label='Clear search'\n className='absolute top-1/2 right-1.5 -translate-y-1/2 text-fg-4 transition-colors hover:text-fg-2'>\n <X className='size-3.5' />\n </button>\n ) : null}\n </div>\n ) : (\n <span className='min-w-0 flex-1 truncate px-1 font-mono text-label text-fg-4'>\n {tree.root}\n </span>\n )}\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Refresh the file tree'\n onClick={() => tree.refresh()}>\n <RefreshCw className='size-3.5 text-fg-3' />\n </Button>\n {onCollapse ? (\n <Button variant='ghost' size='icon-sm' aria-label='Hide project files' onClick={onCollapse}>\n <PanelLeftClose className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n\n <div className='min-h-0 flex-1 overflow-auto px-1 py-1'>\n {tree.error ? (\n <p className='px-2 py-3 text-body-sm text-danger'>{tree.error}</p>\n ) : searching ? (\n matches === undefined ? (\n <div className='py-6 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n ) : matches.length === 0 ? (\n <p className='px-2 py-6 text-center text-body-sm text-fg-4'>No matching files.</p>\n ) : (\n <ul>\n {matches.map((match) => (\n <li key={match.path}>\n <Row\n label={fileName(match.relative)}\n // The directory, dimmed and truncated separately, so a deep\n // path cannot push the filename out of the row — a rail full\n // of `apps/ios/DerivedData/B…` says nothing about which file\n // each hit is.\n detail={directoryOf(match.relative)}\n title={match.relative}\n icon={<File className='size-3.5 shrink-0 text-fg-4' />}\n active={match.path === activePath}\n onClick={() => onOpenFile(match.path)}\n />\n </li>\n ))}\n </ul>\n )\n ) : tree.loading ? (\n <div className='py-6 text-center'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n ) : tree.rows.length === 0 ? (\n <p className='px-2 py-6 text-center text-body-sm text-fg-4'>This project is empty.</p>\n ) : (\n <ul role='tree' aria-label='Project files'>\n {tree.rows.map((row) => (\n <li key={row.entry.path} role='treeitem' aria-expanded={row.expanded} aria-level={row.depth + 1}>\n <Row\n label={row.entry.name}\n indent={row.depth}\n active={row.entry.path === activePath}\n icon={\n row.entry.type === 'dir' ? (\n row.loading ? (\n <Spinner className='size-3.5 shrink-0 text-fg-4' />\n ) : row.expanded ? (\n <FolderOpen className='size-3.5 shrink-0 text-accent' />\n ) : (\n <Folder className='size-3.5 shrink-0 text-accent' />\n )\n ) : (\n <EntryIcon type={row.entry.type} />\n )\n }\n chevron={\n row.entry.type === 'dir' ? (\n <ChevronRight\n className={cn(\n 'size-3 shrink-0 text-fg-4 transition-transform',\n row.expanded && 'rotate-90',\n )}\n />\n ) : undefined\n }\n onClick={() =>\n row.entry.type === 'dir'\n ? tree.toggle(row.entry.path)\n : onOpenFile(row.entry.path)\n }\n />\n {row.truncated ? (\n <p\n className='truncate py-0.5 text-label text-fg-4'\n style={{ paddingLeft: (row.depth + 1) * INDENT + 22 }}>\n More entries than the server will return — use search.\n </p>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n )\n}\n\n/** One clickable line. A directory and a file differ only in what the click does\n * and whether there is a chevron — visually they are the same row. */\nfunction Row({\n label,\n detail,\n title,\n icon,\n chevron,\n indent = 0,\n active,\n onClick,\n}: {\n label: string\n /** Secondary text after the label, dimmed and the first thing to be truncated. */\n detail?: string\n title?: string\n icon: ReactNode\n chevron?: ReactNode\n indent?: number\n active?: boolean\n onClick: () => void\n}) {\n // Scroll a row that became active elsewhere (a search hit, a `reveal`) into\n // view, but never yank the list while someone is reading it — `nearest` moves\n // the minimum and does nothing when the row is already visible.\n const ref = useRef<HTMLButtonElement>(null)\n useEffect(() => {\n if (active) ref.current?.scrollIntoView({ block: 'nearest' })\n }, [active])\n\n return (\n <button\n ref={ref}\n type='button'\n onClick={onClick}\n title={title ?? label}\n style={{ paddingLeft: indent * INDENT + 4 }}\n className={cn(\n 'flex w-full items-center gap-1 rounded py-1 pr-2 text-left transition-colors',\n active ? 'bg-surface-hover text-fg-1' : 'text-fg-2 hover:bg-surface-hover',\n )}>\n <span className='flex size-3 shrink-0 items-center justify-center'>{chevron}</span>\n {icon}\n {/* `shrink-0` on the name and `min-w-0` on the detail: when the row runs\n out of room the directory gives way and the filename stays whole. */}\n <span className='shrink-0 truncate font-mono text-label'>{label}</span>\n {detail ? (\n <span className='min-w-0 flex-1 truncate font-mono text-label text-fg-4'>{detail}</span>\n ) : null}\n </button>\n )\n}\n\n/** Last segment of a relative match. */\nfunction fileName(relative: string): string {\n return relative.slice(relative.lastIndexOf('/') + 1)\n}\n\n/** Everything before it, or `undefined` for a file at the search root. */\nfunction directoryOf(relative: string): string | undefined {\n const cut = relative.lastIndexOf('/')\n return cut === -1 ? undefined : relative.slice(0, cut)\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 === '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 { useEffect, useRef, useState } from 'react'\nimport type * as Monaco from 'monaco-editor'\nimport { cn } from '../../lib/utils.ts'\nimport { Spinner } from '../ui/Spinner.tsx'\n\nexport interface CodeEditorProps {\n /** Absolute path — decides the language and identifies the model. */\n path: string\n /** Text to show. Applied to the model when it differs from what is on screen,\n * so an external reload lands without fighting the user's cursor. */\n value: string\n onChange?: (value: string) => void\n /** Ctrl/Cmd+S. Wired inside Monaco because the editor swallows keydown. */\n onSave?: () => void\n readOnly?: boolean\n className?: string\n}\n\n/**\n * Monaco — VS Code's own editor — behind a small React surface.\n *\n * Two deliberate choices, both about `@workerdeck/ui` being a **published\n * library** rather than an app:\n *\n * 1. **Loaded on demand.** The `import()` is inside an effect, so Monaco is a\n * separate chunk that arrives when someone first opens a file. A dashboard\n * that never opens one never pays for it, and Monaco's ~90 language grammars\n * are themselves lazy (each `registerLanguage` carries an `import()` loader),\n * so opening a `.ts` file fetches the TypeScript grammar and nothing else.\n * 2. **No `MonacoEnvironment` is configured here, and none is needed.** Workers\n * in a library become every embedder's bootstrapping problem, and\n * `packages/web` ships prebuilt static files at a domain root, which is\n * exactly where hardcoded worker URLs break. The editor is configured so it\n * never asks for one: `wordBasedSuggestions` and `quickSuggestions` off,\n * no diff editor. A host that wants the worker-backed language services\n * (TypeScript IntelliSense, JSON schema validation) sets `MonacoEnvironment`\n * itself before the first file is opened — Monaco is a singleton and nothing\n * here fights that.\n *\n * One model per path, kept across tab switches, so undo history and view state\n * survive clicking away and back — which is most of what makes tabs feel like\n * tabs rather than like re-opening a file.\n */\nexport function CodeEditor({\n path,\n value,\n onChange,\n onSave,\n readOnly,\n className,\n}: CodeEditorProps) {\n const host = useRef<HTMLDivElement>(null)\n const editor = useRef<Monaco.editor.IStandaloneCodeEditor | null>(null)\n const monaco = useRef<typeof Monaco | null>(null)\n const [ready, setReady] = useState(false)\n const theme = useDocumentTheme()\n\n // Callbacks through refs: they change identity every render, and re-creating\n // the editor (or re-registering its listeners) on each one would drop the\n // cursor mid-keystroke.\n const handlers = useRef({ onChange, onSave })\n handlers.current = { onChange, onSave }\n\n useEffect(() => {\n let disposed = false\n void loadMonaco().then((api) => {\n if (disposed || !host.current) return\n monaco.current = api\n const instance = api.editor.create(host.current, {\n value,\n language: languageOf(path),\n readOnly,\n automaticLayout: true,\n theme: monacoTheme(document.documentElement.getAttribute('data-theme')),\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n fontSize: 12,\n lineHeight: 20,\n fontFamily:\n 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace',\n renderLineHighlight: 'line',\n smoothScrolling: true,\n padding: { top: 8, bottom: 8 },\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n // Without a worker there is no word-based suggestion provider to ask, so\n // asking would surface an empty popup on every identifier.\n wordBasedSuggestions: 'off',\n quickSuggestions: false,\n })\n editor.current = instance\n instance.onDidChangeModelContent(() => {\n handlers.current.onChange?.(instance.getValue())\n })\n instance.addCommand(api.KeyMod.CtrlCmd | api.KeyCode.KeyS, () => {\n handlers.current.onSave?.()\n })\n setReady(true)\n })\n return () => {\n disposed = true\n // Dispose the editor but NOT its model — the model is keyed by path and\n // outlives this mount so that reopening a tab restores its undo stack.\n editor.current?.dispose()\n editor.current = null\n setReady(false)\n }\n // Deliberately created once, with no dependencies. Path, value and readOnly\n // are applied by the effects below instead, because re-creating the editor\n // to change any of them would lose the cursor, the scroll position and the\n // undo history.\n }, [])\n\n // Swap the model when the focused file changes. One model per path, created\n // lazily and kept, so each tab keeps its own undo history and view state.\n useEffect(() => {\n const api = monaco.current\n const instance = editor.current\n if (!api || !instance || !ready) return\n const uri = api.Uri.parse(`workerdeck://host${path}`)\n const model = api.editor.getModel(uri) ?? api.editor.createModel(value, languageOf(path), uri)\n if (instance.getModel() !== model) instance.setModel(model)\n }, [path, ready, value])\n\n // Apply an external change — a reload from disk, a revert — without\n // disturbing anything when the text already matches, which is the common case\n // because most changes to `value` are echoes of the user's own typing.\n useEffect(() => {\n const instance = editor.current\n if (!instance || !ready) return\n const model = instance.getModel()\n if (!model || model.getValue() === value) return\n // `pushEditOperations` rather than `setValue` so the replacement joins the\n // undo stack instead of clearing it.\n model.pushEditOperations(\n [],\n [{ range: model.getFullModelRange(), text: value }],\n () => null,\n )\n }, [value, ready])\n\n useEffect(() => {\n if (ready) editor.current?.updateOptions({ readOnly })\n }, [readOnly, ready])\n\n // The theme is global to Monaco, not per-editor — `setTheme` is on the\n // namespace for that reason.\n useEffect(() => {\n if (ready) monaco.current?.editor.setTheme(monacoTheme(theme))\n }, [theme, ready])\n\n return (\n <div className={cn('relative min-h-0 min-w-0 flex-1', className)}>\n <div ref={host} className='absolute inset-0' />\n {!ready ? (\n <div className='absolute inset-0 flex items-center justify-center bg-bg'>\n <Spinner className='size-4 text-fg-4' />\n </div>\n ) : null}\n </div>\n )\n}\n\n/**\n * Follow the `data-theme` the design tokens already swap on, so the editor is\n * never the one light rectangle in a dark app (or the reverse). An attribute\n * observer rather than a media query: the app has a manual toggle, and the\n * attribute is what that toggle writes.\n */\nfunction useDocumentTheme(): string | null {\n const [theme, setTheme] = useState<string | null>(() =>\n typeof document === 'undefined' ? null : document.documentElement.getAttribute('data-theme'),\n )\n useEffect(() => {\n const root = document.documentElement\n const sync = () => setTheme(root.getAttribute('data-theme'))\n sync()\n const observer = new MutationObserver(sync)\n observer.observe(root, { attributes: true, attributeFilter: ['data-theme'] })\n return () => observer.disconnect()\n }, [])\n return theme\n}\n\n/** An unset attribute means the host never opted into the token themes, in which\n * case dark matches this package's default surface. */\nfunction monacoTheme(theme: string | null): string {\n return theme === 'light' ? 'wd-light' : 'wd-dark'\n}\n\n/**\n * Load Monaco once, register the themes, and hand back the API.\n *\n * Cached as a promise rather than a value so that several editors mounting in\n * the same frame share one load instead of racing three of them.\n */\nlet monacoPromise: Promise<typeof Monaco> | undefined\nfunction loadMonaco(): Promise<typeof Monaco> {\n monacoPromise ??= (async () => {\n const api = await import('monaco-editor')\n // Themes that inherit the surrounding surface instead of Monaco's own\n // near-black, so the editor does not sit in the layout as a differently\n // coloured rectangle. Both are defined; the CSS variable decides nothing\n // here, so the panel picks by `prefers-color-scheme` on the document.\n api.editor.defineTheme('wd-dark', {\n base: 'vs-dark',\n inherit: true,\n rules: [],\n colors: { 'editor.background': '#00000000' },\n })\n api.editor.defineTheme('wd-light', {\n base: 'vs',\n inherit: true,\n rules: [],\n colors: { 'editor.background': '#00000000' },\n })\n return api\n })()\n return monacoPromise\n}\n\n/**\n * Monaco's language id for a path.\n *\n * By extension, with a short table for the files that have none — Monaco's own\n * registry is keyed on extensions it knows, and an unmatched file falls through\n * to `plaintext`, which is the honest answer rather than a guess.\n */\nfunction languageOf(path: string): string {\n const name = path.slice(path.lastIndexOf('/') + 1)\n const byName = FILENAME_LANGUAGES[name.toLowerCase()]\n if (byName) return byName\n const extension = name.slice(name.lastIndexOf('.') + 1).toLowerCase()\n return EXTENSION_LANGUAGES[extension] ?? 'plaintext'\n}\n\n/** Files whose type is their whole name. */\nconst FILENAME_LANGUAGES: Record<string, string> = {\n dockerfile: 'dockerfile',\n makefile: 'plaintext',\n '.gitignore': 'plaintext',\n '.env': 'shell',\n}\n\n/** Extension → Monaco language id. Only where the id differs from the extension\n * or the extension is ambiguous; everything else Monaco resolves itself. */\nconst EXTENSION_LANGUAGES: Record<string, string> = {\n ts: 'typescript',\n tsx: 'typescript',\n mts: 'typescript',\n cts: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n mjs: 'javascript',\n cjs: 'javascript',\n json: 'json',\n jsonc: 'json',\n md: 'markdown',\n mdx: 'markdown',\n css: 'css',\n scss: 'scss',\n less: 'less',\n html: 'html',\n htm: 'html',\n xml: 'xml',\n svg: 'xml',\n yml: 'yaml',\n yaml: 'yaml',\n toml: 'plaintext',\n sh: 'shell',\n bash: 'shell',\n zsh: 'shell',\n fish: 'shell',\n py: 'python',\n rb: 'ruby',\n rs: 'rust',\n go: 'go',\n swift: 'swift',\n java: 'java',\n kt: 'kotlin',\n c: 'c',\n h: 'c',\n cpp: 'cpp',\n cc: 'cpp',\n hpp: 'cpp',\n cs: 'csharp',\n php: 'php',\n sql: 'sql',\n graphql: 'graphql',\n gql: 'graphql',\n lua: 'lua',\n r: 'r',\n pl: 'perl',\n dart: 'dart',\n scala: 'scala',\n ini: 'ini',\n cfg: 'ini',\n conf: 'ini',\n txt: 'plaintext',\n log: 'plaintext',\n}\n","import type { ReactNode } from 'react'\nimport type { OpenFile } from '@workerdeck/react'\nimport { currentText, isDirty } from '@workerdeck/react'\nimport { FileWarning, TriangleAlert } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { CodeEditor } from './CodeEditor.tsx'\n\nexport interface FileViewerProps {\n file: OpenFile | undefined\n /** From `/fs/roots`. False renders the editor read-only rather than letting\n * someone type into a file this gateway will refuse to write. */\n canWrite?: boolean\n onChange?: (path: string, content: string) => void\n onSave?: (path: string) => void\n /** Discard this tab's edits — local only, no re-read. */\n onRevert?: (path: string) => void\n /** Take the version on disk, discarding this tab's edits. */\n onReload?: (path: string) => void\n /** Take this tab's version, over whatever is on disk now. */\n onOverwrite?: (path: string) => void\n onDismissConflict?: (path: string) => void\n className?: string\n}\n\n/**\n * The focused file: Monaco, plus the states a file can be in that are not\n * \"here is some text\".\n *\n * No path row — the tab's tooltip carries the path and the size, and a line of\n * monospace above every file is chrome that never earns its height.\n */\nexport function FileViewer({\n file,\n canWrite,\n onChange,\n onSave,\n onRevert,\n onReload,\n onOverwrite,\n onDismissConflict,\n className,\n}: FileViewerProps) {\n if (!file) return null\n\n return (\n <div\n data-slot='file-viewer'\n className={cn('flex min-h-0 min-w-0 flex-1 flex-col bg-bg', className)}>\n {/* The one failure with a choice attached, so it gets a bar rather than a\n toast: the agent rewrote this file while it was open, and which\n version wins is not something to decide on the user's behalf. */}\n {file.conflict ? (\n <div className='flex shrink-0 flex-wrap items-center gap-2 border-b border-warning/40 bg-warning-bg px-3 py-2'>\n <TriangleAlert className='size-3.5 shrink-0 text-warning' />\n <span className='min-w-0 flex-1 text-body-sm text-warning'>\n This file changed on disk since you opened it.\n </span>\n <Button variant='outline' size='xs' onClick={() => onReload?.(file.path)}>\n Use the version on disk\n </Button>\n <Button variant='outline' size='xs' onClick={() => onOverwrite?.(file.path)}>\n Keep mine\n </Button>\n <Button variant='ghost' size='xs' onClick={() => onDismissConflict?.(file.path)}>\n Dismiss\n </Button>\n </div>\n ) : file.saveError ? (\n <div className='flex shrink-0 items-center gap-2 border-b border-danger/40 bg-danger-bg px-3 py-2'>\n <TriangleAlert className='size-3.5 shrink-0 text-danger' />\n <span className='min-w-0 flex-1 text-body-sm text-danger'>{file.saveError}</span>\n <Button variant='ghost' size='xs' onClick={() => onDismissConflict?.(file.path)}>\n Dismiss\n </Button>\n </div>\n ) : null}\n\n {file.status === 'loading' ? (\n <Centred>\n <Spinner className='size-4 text-fg-4' />\n </Centred>\n ) : file.status === 'error' ? (\n <Centred>\n <TriangleAlert className='size-4 text-danger' />\n <p className='text-body-sm text-danger'>{file.error}</p>\n </Centred>\n ) : file.status === 'binary' ? (\n <Centred>\n <FileWarning className='size-4 text-fg-4' />\n <p className='text-body-sm text-fg-4'>This file isn’t text.</p>\n {/* Said out loud, because \"can't show it\" and \"editing it here would\n destroy it\" are different reassurances. */}\n <p className='text-label text-fg-4'>It can’t be edited here without corrupting it.</p>\n </Centred>\n ) : (\n <CodeEditor\n path={file.path}\n value={currentText(file)}\n readOnly={!canWrite}\n onChange={onChange ? (content) => onChange(file.path, content) : undefined}\n onSave={onSave ? () => onSave(file.path) : undefined}\n />\n )}\n\n {/* A status strip only while there is something to say. Saving is fast\n enough that a permanent row would mostly be blank. */}\n {file.status === 'ready' && (file.saving || isDirty(file) || !canWrite) ? (\n <div className='flex shrink-0 items-center gap-2 border-t border-border px-3 py-1'>\n {file.saving ? (\n <>\n <Spinner className='size-3 text-fg-4' />\n <span className='text-label text-fg-4'>Saving…</span>\n </>\n ) : !canWrite ? (\n <span className='text-label text-fg-4'>\n Read-only — this gateway doesn’t allow writes.\n </span>\n ) : (\n <>\n <span className='text-label text-fg-3'>Unsaved changes</span>\n <span className='flex-1' />\n {/* Revert, not reload: discard *my* edits and go back to what\n this tab read. Re-reading is the conflict bar's job, and it is\n a different question. */}\n <Button variant='ghost' size='xs' onClick={() => onRevert?.(file.path)}>\n Revert\n </Button>\n <Button variant='outline' size='xs' onClick={() => onSave?.(file.path)}>\n Save\n <span className='ml-1 text-fg-4'>⌘S</span>\n </Button>\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction Centred({ children }: { children: ReactNode }) {\n return (\n <div className='flex min-h-0 flex-1 flex-col items-center justify-center gap-2 p-6'>\n {children}\n </div>\n )\n}\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from 'react'\nimport { createPortal } from 'react-dom'\nimport type { WorkerDeckClient } from '@workerdeck/client'\nimport {\n useHostFileRoots,\n useHostFileSearch,\n useHostFileTree,\n useOpenFiles,\n useSessionInfo,\n isDirty,\n} from '@workerdeck/react'\nimport { PanelLeftOpen } from 'lucide-react'\nimport { cn } from '../../lib/utils.ts'\nimport { Button } from '../ui/Button.tsx'\nimport { Splitter } from '../ui/Splitter.tsx'\nimport { EditorTabs } from './EditorTabs.tsx'\nimport { FileTree } from './FileTree.tsx'\nimport { FileViewer } from './FileViewer.tsx'\nimport { SessionPanel, type SessionPanelProps } from './SessionPanel.tsx'\n\nexport interface SessionWorkspaceProps {\n client: WorkerDeckClient\n sessionId: string | undefined\n /** Passed straight through to {@link SessionPanel} — including the render-prop\n * form that claims the session-actions menu. */\n header?: SessionPanelProps['header']\n /** Rail width in pixels on first render. */\n defaultRailWidth?: number\n /** Start with the file rail collapsed even on a wide viewport. */\n defaultRailCollapsed?: boolean\n className?: string\n}\n\nconst RAIL_MIN = 180\nconst RAIL_MAX = 520\n/** How little of the agent column the editor may leave. Below this the composer\n * and a line of transcript stop fitting together, which is the point at which the\n * agent has stopped being usable rather than merely small. */\nconst AGENT_MIN = 220\nconst EDITOR_MIN = 120\n\n/**\n * A VS Code-shaped workspace around a live session: file tree on the left, open\n * files above, the agent below.\n *\n * **Strictly additive.** {@link SessionPanel} is untouched and still the whole\n * session surface on its own — an embedder picks one or the other, and one that\n * has its own file tree keeps using the panel.\n *\n * Two things here are load-bearing and easy to break:\n *\n * 1. **The editor region is absent from the layout when nothing is open**, not\n * collapsed to zero height. A zero-height pane leaves a draggable splitter and\n * parks the composer at an odd offset; absence is what makes the agent's\n * \"claims the full column\" state actually look like the panel alone.\n * 2. **`SessionPanel` keeps its position in the tree across that transition.** It\n * holds the WebSocket attach and the entire transcript, so moving it between\n * parents — or wrapping it conditionally — would remount it and drop the\n * session's rendered history on the floor the first time someone opens a file.\n * The conditional children before it are `? :` expressions that leave a null in\n * their slot, which is exactly what keeps its index stable.\n */\nexport function SessionWorkspace({\n client,\n sessionId,\n header,\n defaultRailWidth = 260,\n defaultRailCollapsed,\n className,\n}: SessionWorkspaceProps) {\n // The cwd is the tree's root, and it comes from the registry rather than from\n // the panel: reading it off the panel's own session hook would mean attaching\n // a second WebSocket client, and the tool bridge asks the *first* attached\n // client — a second one changes who answers.\n const { info } = useSessionInfo(client, sessionId)\n const cwd = info?.cwd\n\n const tree = useHostFileTree(client, cwd)\n const search = useHostFileSearch(client, cwd)\n const files = useOpenFiles(client)\n // Writing is a separate server opt-in from reading and defaults off, so the\n // editor asks before it offers to save anything.\n const { canWrite } = useHostFileRoots(client)\n\n // Nothing here can save on the user's behalf when the tab is going away, so\n // the browser's own guard is the last line. Registered only while there is\n // something to lose — an unconditional handler makes every navigation prompt.\n useEffect(() => {\n if (!files.hasUnsaved) return\n const warn = (event: BeforeUnloadEvent) => event.preventDefault()\n window.addEventListener('beforeunload', warn)\n return () => window.removeEventListener('beforeunload', warn)\n }, [files.hasUnsaved])\n\n const wide = useIsWide()\n const [railCollapsed, setRailCollapsed] = useState(defaultRailCollapsed ?? false)\n const [railWidth, setRailWidth] = useState(defaultRailWidth)\n const [editorHeight, setEditorHeight] = useState(360)\n\n // Closing every tab returns the agent to the full column; opening one again\n // should restore the height the user had chosen, so this is not reset here.\n const hasFiles = files.files.length > 0\n\n // A narrow viewport cannot spare 260px of rail beside a transcript, so there\n // the rail overlays the workspace instead of sitting next to it — and starts\n // out of the way.\n const overlayRail = !wide\n const railOpen = tree.available && !railCollapsed\n useEffect(() => {\n if (overlayRail) setRailCollapsed(true)\n }, [overlayRail])\n\n // Closing a dirty tab is the one destructive thing the strip can do, and the\n // edits are not recoverable once the tab is gone. `confirm` rather than a\n // styled dialog on purpose: it is modal, it cannot be missed, and a\n // custom one here would be a second modal system inside a component an\n // embedder already renders inside their own.\n const closeTab = useCallback(\n (path: string) => {\n const file = files.files.find((f) => f.path === path)\n if (file && isDirty(file)) {\n const name = file.name\n if (!window.confirm(`${name} has unsaved changes. Close it and lose them?`)) return\n }\n files.close(path)\n },\n [files],\n )\n\n const column = useRef<HTMLDivElement>(null)\n const columnHeight = useElementHeight(column)\n const editorMax = Math.max(EDITOR_MIN, columnHeight - AGENT_MIN)\n\n // The embedder's header is app chrome and belongs above everything — but only\n // `SessionPanel` can *build* the `⋯` menu it is handed, and only if it is the\n // one calling the render-prop. So the panel still calls it, in its own tree,\n // and the result is portalled up here. That keeps `SessionPanel` untouched and\n // keeps the menu's own context (it is a Base UI popup) intact.\n const [topBar, setTopBar] = useState<HTMLDivElement | null>(null)\n const hoisted: SessionPanelProps['header'] =\n header === undefined || topBar === null\n ? undefined\n : typeof header === 'function'\n ? (slots) => createPortal(header(slots), topBar)\n : createPortal(header, topBar)\n\n return (\n <div\n data-slot='session-workspace'\n className={cn('flex h-full min-h-0 w-full flex-col overflow-hidden bg-bg', className)}>\n {header !== undefined ? <div ref={setTopBar} className='shrink-0' /> : null}\n <div className='relative flex min-h-0 flex-1'>\n {railOpen ? (\n <FileTree\n tree={tree}\n search={search}\n activePath={files.activePath}\n onOpenFile={files.open}\n onCollapse={() => setRailCollapsed(true)}\n style={{ width: overlayRail ? Math.min(railWidth, 320) : railWidth }}\n className={cn(\n 'shrink-0 border-r border-border',\n overlayRail && 'absolute inset-y-0 left-0 z-20 shadow-lg',\n )}\n />\n ) : tree.available ? (\n // Collapsed: a slim strip that keeps the rail one click away. In flow\n // rather than floating over the panel, so it can never land on top of an\n // embedder's own header controls.\n <div className='flex w-8 shrink-0 flex-col items-center border-r border-border bg-surface pt-1.5'>\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Show project files'\n onClick={() => setRailCollapsed(false)}>\n <PanelLeftOpen className='size-4 text-fg-3' />\n </Button>\n </div>\n ) : null}\n {/* No splitter over an overlay rail — dragging a drawer's edge on a phone\n fights the scroll it is sitting on top of. */}\n {railOpen && !overlayRail ? (\n <Splitter\n orientation='vertical'\n value={railWidth}\n onValueChange={setRailWidth}\n min={RAIL_MIN}\n max={RAIL_MAX}\n aria-label='Resize the file tree'\n />\n ) : null}\n\n <div ref={column} className='flex min-h-0 min-w-0 flex-1 flex-col'>\n {/* Slot 1 of 3. The `? :` leaves a null here when nothing is open, which\n is what holds the agent's slot below and keeps it from remounting. */}\n {hasFiles ? (\n <div\n className='flex min-h-0 shrink-0 flex-col overflow-hidden'\n style={{ height: Math.min(editorHeight, editorMax || editorHeight) }}>\n <EditorTabs\n files={files.files}\n activePath={files.activePath}\n onActivate={files.activate}\n onClose={closeTab}\n />\n <FileViewer\n file={files.active}\n canWrite={canWrite}\n onChange={files.edit}\n onSave={(path) => void files.save(path)}\n onRevert={files.revert}\n onReload={files.reload}\n onOverwrite={(path) => void files.overwrite(path)}\n onDismissConflict={files.dismissConflict}\n />\n </div>\n ) : null}\n {/* Slot 2 of 3. */}\n {hasFiles ? (\n <Splitter\n orientation='horizontal'\n value={Math.min(editorHeight, editorMax || editorHeight)}\n onValueChange={setEditorHeight}\n min={EDITOR_MIN}\n max={editorMax}\n aria-label='Resize the open file'\n />\n ) : null}\n {/* Slot 3 of 3 — always here, always at this index. */}\n <SessionPanel\n client={client}\n sessionId={sessionId}\n header={hoisted}\n className='min-h-0 flex-1'\n />\n </div>\n\n {/* Tapping away closes the drawer, which is the only way back to the\n transcript on a narrow screen. */}\n {overlayRail && railOpen ? (\n <button\n type='button'\n aria-label='Close the file tree'\n onClick={() => setRailCollapsed(true)}\n className='absolute inset-0 z-10 bg-black/30'\n />\n ) : null}\n </div>\n </div>\n )\n}\n\n/** Live height of an element, for a splitter that needs to know how much room it\n * is dividing. Zero until the first observation, which callers treat as\n * \"unmeasured\" rather than as a real bound. */\nfunction useElementHeight(ref: RefObject<HTMLElement | null>): number {\n const [height, setHeight] = useState(0)\n useLayoutEffect(() => {\n const element = ref.current\n if (!element) return\n const observer = new ResizeObserver(([entry]) => {\n if (entry) setHeight(entry.contentRect.height)\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [ref])\n return height\n}\n\n/** Whether there is room for a rail beside the content. Presentation only — the\n * workspace's actual state lives in the hooks from `@workerdeck/react`. */\nfunction useIsWide(): boolean {\n const [wide, setWide] = useState(true)\n useEffect(() => {\n const query = window.matchMedia('(min-width: 768px)')\n const sync = () => setWide(query.matches)\n sync()\n query.addEventListener('change', sync)\n return () => query.removeEventListener('change', sync)\n }, [])\n return wide\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n"],"mappings":"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"}