@workerdeck/ui 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -3
- package/build/SessionPanel-CyhygZx_.d.mts +277 -0
- package/build/SessionPanel-_U8tjX29.mjs +8409 -0
- package/build/SessionPanel-_U8tjX29.mjs.map +1 -0
- package/build/format-DqR56Y8l.mjs +162 -0
- package/build/format-DqR56Y8l.mjs.map +1 -0
- package/build/format-ljc3lKpA.d.mts +59 -0
- package/build/format.d.mts +2 -0
- package/build/format.mjs +2 -0
- package/build/index.d.mts +610 -87
- package/build/index.mjs +6 -5104
- package/build/index.mjs.map +1 -1
- package/build/workspace.d.mts +199 -0
- package/build/workspace.mjs +849 -0
- package/build/workspace.mjs.map +1 -0
- package/package.json +22 -4
- package/src/components/agent/CodeEditor.tsx +300 -0
- package/src/components/agent/Composer.tsx +522 -87
- package/src/components/agent/ContextDialog.tsx +99 -0
- package/src/components/agent/Conversation.tsx +11 -3
- package/src/components/agent/EditorTabs.tsx +165 -0
- package/src/components/agent/FileCard.tsx +26 -0
- package/src/components/agent/FileTree.tsx +287 -0
- package/src/components/agent/FileViewer.tsx +148 -0
- package/src/components/agent/HostFilesDialog.tsx +218 -0
- package/src/components/agent/Loader.tsx +120 -14
- package/src/components/agent/McpDialog.tsx +363 -0
- package/src/components/agent/Message.tsx +51 -17
- package/src/components/agent/ModelSelect.tsx +34 -6
- package/src/components/agent/PermissionModeSelect.tsx +133 -22
- package/src/components/agent/PermissionPrompt.tsx +164 -6
- package/src/components/agent/PromptTokenText.tsx +39 -0
- package/src/components/agent/QuestionPrompt.tsx +122 -0
- package/src/components/agent/Reasoning.tsx +20 -5
- package/src/components/agent/Response.tsx +128 -0
- package/src/components/agent/SessionEmptyState.tsx +65 -0
- package/src/components/agent/SessionInfoDialog.tsx +163 -0
- package/src/components/agent/SessionPanel.tsx +756 -91
- package/src/components/agent/SessionWorkspace.tsx +282 -0
- package/src/components/agent/SkillsDialog.tsx +195 -0
- package/src/components/agent/StatusBar.tsx +85 -18
- package/src/components/agent/ToolCallCard.tsx +243 -30
- package/src/components/agent/Transcript.tsx +495 -30
- package/src/components/agent/UsageDialog.tsx +168 -0
- package/src/components/agent/line-prompt.tsx +249 -0
- package/src/components/agent/transcript-variant.tsx +61 -0
- package/src/components/prompt-area/prompt-area-engine.ts +53 -0
- package/src/components/prompt-area/types.ts +15 -0
- package/src/components/prompt-area/use-prompt-area.ts +20 -0
- package/src/components/ui/CodeBlock.tsx +40 -2
- package/src/components/ui/CopyButton.tsx +28 -3
- package/src/components/ui/Dialog.tsx +92 -0
- package/src/components/ui/Menu.tsx +55 -0
- package/src/components/ui/Splitter.tsx +133 -0
- package/src/components/ui/Tooltip.tsx +22 -5
- package/src/format.ts +10 -0
- package/src/index.ts +63 -2
- package/src/lib/clipboard.ts +56 -0
- package/src/lib/format.ts +114 -0
- package/src/lib/tool-icon.ts +96 -0
- package/src/workspace.ts +28 -0
package/build/index.mjs.map
CHANGED
|
@@ -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. ` ` -> U+00A0) to single spaces. */\nfunction collapseWhitespace(text: string): string {\n return text.replace(/\\s+/g, ' ')\n}\n\n/** Escapes literal `*` from HTML text so prose isn't re-read as emphasis. */\nfunction escapeText(text: string): string {\n return text.replace(/\\*/g, '\\\\*')\n}\n\n// ---------------------------------------------------------------------------\n// Block serializers\n// ---------------------------------------------------------------------------\n\n/** Derives a fenced-block language from `class=\"language-ts\"` or `lang=\"ts\"`. */\nfunction detectCodeLang(pre: HTMLElement): string {\n const code = pre.querySelector('code')\n const classNames = `${pre.className} ${code?.className ?? ''}`\n const fromClass = /(?:language|lang)-([\\w-]+)/.exec(classNames)\n if (fromClass) return fromClass[1]\n return pre.getAttribute('lang') ?? code?.getAttribute('lang') ?? ''\n}\n\nfunction serializePre(pre: HTMLElement): string {\n const lang = detectCodeLang(pre)\n const raw = (pre.textContent ?? '').replace(/\\n$/, '')\n return `\\n\\n\\`\\`\\`${lang}\\n${raw}\\n\\`\\`\\`\\n\\n`\n}\n\nfunction serializeInlineCode(node: HTMLElement): string {\n const content = node.textContent ?? ''\n if (content.includes('`')) return `\\`\\` ${content} \\`\\``\n return `\\`${content}\\``\n}\n\n/** http(s)/mailto only; drops `#`, empty, and `javascript:` hrefs. */\nfunction isSafeHref(href: string): boolean {\n return /^(https?:|mailto:)/i.test(href)\n}\n\nfunction serializeAnchor(node: HTMLElement, depth: number): string {\n const href = node.getAttribute('href') ?? ''\n const label = serializeChildren(node, depth).trim()\n if (!isSafeHref(href)) return label\n if (!label || label === href) return href\n return `[${label}](${href})`\n}\n\nfunction serializeImage(node: HTMLElement): string {\n const src = node.getAttribute('src') ?? ''\n // Gate the src through the same allow-list as anchors: only http(s)/mailto\n // survive, so a `javascript:`/`vbscript:`/`data:` src never reaches the\n // emitted markdown (defense-in-depth for consumers that render it as HTML).\n if (!src || !isSafeHref(src)) return ''\n return ``\n}\n\nfunction serializeBlockquote(node: HTMLElement, depth: number): string {\n const inner = serializeChildren(node, depth).trim()\n const quoted = inner\n .split('\\n')\n .map((line) => (line ? `> ${line}` : '>'))\n .join('\\n')\n return `\\n\\n${quoted}\\n\\n`\n}\n\n/**\n * Serializes a `<ul>`/`<ol>` at nesting `depth` (0 = top level). Each `<li>`'s\n * own inline content becomes the marker line; a nested `<ul>`/`<ol>` child is\n * serialized at `depth + 1` and appended indented below its parent item.\n */\nfunction serializeList(list: HTMLElement, depth: number): string {\n const ordered = list.tagName === 'OL'\n const start = Number.parseInt(list.getAttribute('start') ?? '', 10)\n let index = Number.isNaN(start) ? 1 : start\n const indent = ' '.repeat(depth)\n const lines: string[] = []\n\n for (const child of Array.from(list.childNodes)) {\n if (!isHTMLElement(child) || child.tagName !== 'LI') continue\n\n const marker = ordered ? `${index}. ` : '- '\n index++\n\n let label = ''\n let nested = ''\n for (const liChild of Array.from(child.childNodes)) {\n if (isHTMLElement(liChild) && (liChild.tagName === 'UL' || liChild.tagName === 'OL')) {\n nested += `\\n${serializeList(liChild, depth + 1)}`\n } else {\n label += serializeNode(liChild, depth)\n }\n }\n lines.push(`${indent}${marker}${label.trim()}${nested}`)\n }\n\n return lines.join('\\n')\n}\n\nfunction serializeTable(table: HTMLElement, depth: number): string {\n const rows = Array.from(table.querySelectorAll('tr'))\n if (rows.length === 0) return ''\n\n const cells = rows.map((row) =>\n Array.from(row.children)\n .filter((cell) => cell.tagName === 'TD' || cell.tagName === 'TH')\n .map((cell) =>\n serializeChildren(cell, depth).replace(/\\n+/g, ' ').replace(/\\|/g, '\\\\|').trim(),\n ),\n )\n\n const header = cells[0]\n const separator = header.map(() => '---')\n const toRow = (row: string[]): string => `| ${row.join(' | ')} |`\n\n return [toRow(header), toRow(separator), ...cells.slice(1).map(toRow)].join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Recursive walker\n// ---------------------------------------------------------------------------\n\nfunction serializeChildren(node: Node, depth: number): string {\n let out = ''\n node.childNodes.forEach((child) => {\n out += serializeNode(child, depth)\n })\n return out\n}\n\nfunction serializeNode(node: Node, depth: number): string {\n if (isTextNode(node)) return escapeText(collapseWhitespace(node.textContent ?? ''))\n if (!isHTMLElement(node)) return ''\n\n const tag = node.tagName\n switch (tag) {\n case 'SCRIPT':\n case 'STYLE':\n case 'NOSCRIPT':\n case 'HEAD':\n case 'TITLE':\n return ''\n case 'BR':\n return '\\n'\n case 'HR':\n return '\\n\\n---\\n\\n'\n case 'H1':\n case 'H2':\n case 'H3':\n case 'H4':\n case 'H5':\n case 'H6':\n return `\\n\\n${'#'.repeat(Number(tag[1]))} ${serializeChildren(node, depth).trim()}\\n\\n`\n case 'P':\n return `\\n\\n${serializeChildren(node, depth).trim()}\\n\\n`\n case 'DIV':\n return `\\n${serializeChildren(node, depth).trim()}\\n`\n case 'BLOCKQUOTE':\n return serializeBlockquote(node, depth)\n case 'UL':\n case 'OL':\n return `\\n\\n${serializeList(node, depth)}\\n\\n`\n case 'LI':\n // A stray <li> outside a list wrapper — emit its content as a line.\n return `${serializeChildren(node, depth).trim()}\\n`\n case 'PRE':\n return serializePre(node)\n case 'CODE':\n // Inline code only: <pre> handles its own <code> via textContent.\n return serializeInlineCode(node)\n case 'A':\n return serializeAnchor(node, depth)\n case 'IMG':\n return serializeImage(node)\n case 'TABLE':\n return `\\n\\n${serializeTable(node, depth)}\\n\\n`\n default: {\n // Inline emphasis (B/STRONG/I/EM/S/DEL + styled SPAN/FONT) and the\n // \"span soup\" unwrap case both resolve here: emphasis markers when the\n // tag or inline style is meaningful, otherwise a bare unwrap.\n const { prefix, suffix } = inlineEmphasis(node)\n return prefix + serializeChildren(node, depth) + suffix\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Output normalization\n// ---------------------------------------------------------------------------\n\n/** Trims trailing spaces and caps consecutive blank lines at one. */\nfunction normalizeOutput(markdown: string): string {\n return markdown\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n[ \\t]*\\n[ \\t\\n]*/g, '\\n\\n')\n .trim()\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an HTML string to markdown source text. Returns '' for empty or\n * body-less input. Block markdown (headings, lists, quotes, fences, tables,\n * links) is emitted as literal markdown text — that is the editor's intended\n * display; only `*`/`**`/`***` and bare URLs get visually decorated inline.\n */\nexport function htmlToMarkdown(html: string): string {\n if (!html) return ''\n const doc = new DOMParser().parseFromString(html, 'text/html')\n if (!doc.body) return ''\n return normalizeOutput(serializeChildren(doc.body, 0))\n}\n","'use client'\n\nimport { useCallback, useRef } from 'react'\nimport type { Segment, ChipSegment, TriggerConfig } from './types.ts'\nimport { resolveTriggersInSegments } from './prompt-area-engine.ts'\nimport { normalizeEditorDOM, safeJsonStringify, getSelectionRange } from './dom-helpers.ts'\nimport {\n serializeFragmentToPlainText,\n serializeFragmentToSegments,\n parseSegmentsFromClipboard,\n insertSegmentsAtCursor,\n} from './clipboard-helpers.ts'\nimport { htmlToMarkdown } from './html-to-markdown.ts'\nimport {\n normalizeListPrefixText,\n renumberOrderedListLines,\n hasOrderedListRun,\n} from './prompt-area-list-ops.ts'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype EventHandlerDeps = {\n editorRef: React.RefObject<HTMLDivElement | null>\n readSegmentsFromDOM: () => Segment[]\n onChange: (segments: Segment[]) => void\n renderSegmentsToDOM: (segments: Segment[]) => void\n runTriggerDetection: () => void\n dismissTrigger: () => void\n triggers: TriggerConfig[]\n /** When true, rich `text/html` on the clipboard is converted to markdown. */\n markdownEnabled: boolean\n /** When true, pasted list markers (\"- \") are normalized to the \"•\" glyph. */\n normalizeBullets: boolean\n onPaste?: (data: { segments: Segment[]; source: 'internal' | 'external' }) => void\n onUndo?: (segments: Segment[]) => void\n onRedo?: (segments: Segment[]) => void\n onChipAdd?: (chip: ChipSegment) => void\n onImagePaste?: (file: File) => void\n onRawPaste?: (e: React.ClipboardEvent<HTMLDivElement>) => void\n}\n\ntype PromptAreaEventHandlers = {\n handlePaste: (e: React.ClipboardEvent<HTMLDivElement>) => void\n handleCopy: (e: React.ClipboardEvent<HTMLDivElement>) => void\n handleCut: (e: React.ClipboardEvent<HTMLDivElement>) => void\n handleDrop: (e: React.DragEvent<HTMLDivElement>) => void\n handleDragOver: (e: React.DragEvent<HTMLDivElement>) => void\n handleCompositionStart: () => void\n handleCompositionEnd: () => void\n handleBlur: () => void\n handleKeyDownForUndoRedo: (e: React.KeyboardEvent<HTMLDivElement>) => boolean\n pushUndo: (segments: Segment[]) => void\n resetUndoHistory: () => void\n isComposing: React.RefObject<boolean>\n}\n\n// ---------------------------------------------------------------------------\n// Undo/Redo Stack\n// ---------------------------------------------------------------------------\n\nconst MAX_UNDO_HISTORY = 100\n\n/** Delay before dismissing trigger on blur, so popover clicks register first */\nexport const BLUR_DELAY_MS = 150\n\ntype UndoState = {\n undoStack: Segment[][]\n redoStack: Segment[][]\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Encapsulates all edge-case event handlers for the prompt area component:\n * paste, copy, cut, drag/drop, IME composition, blur, and undo/redo.\n */\nexport function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHandlers {\n const {\n editorRef,\n readSegmentsFromDOM,\n onChange,\n renderSegmentsToDOM,\n runTriggerDetection,\n dismissTrigger,\n triggers,\n markdownEnabled,\n normalizeBullets,\n onPaste: onPasteCallback,\n onUndo,\n onRedo,\n onChipAdd,\n onImagePaste,\n onRawPaste,\n } = deps\n\n const isComposing = useRef(false)\n\n // -----------------------------------------------------------------------\n // Undo/redo stack (MAX_UNDO_HISTORY entries; clears redo on new push).\n // Invariants: stacks live in refs (not useState) so pushUndo /\n // resetUndoHistory / handleKeyDownForUndoRedo keep stable identity.\n // Destabilizing this would re-create handleInput / handleKeyDown /\n // imperative handle on every render and silently regress IME + debounced\n // undo in the parent hook.\n // -----------------------------------------------------------------------\n const undoState = useRef<UndoState>({ undoStack: [], redoStack: [] })\n\n const pushUndo = useCallback((segments: Segment[]) => {\n const state = undoState.current\n state.undoStack.push(segments)\n if (state.undoStack.length > MAX_UNDO_HISTORY) {\n state.undoStack.shift()\n }\n // Clear redo stack on new change\n state.redoStack = []\n }, [])\n\n const resetUndoHistory = useCallback(() => {\n undoState.current = { undoStack: [], redoStack: [] }\n }, [])\n\n // -----------------------------------------------------------------------\n // Paste: strip HTML, insert plain text only\n // -----------------------------------------------------------------------\n\n const handlePaste = useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n // Let a consumer take over the paste entirely (e.g. divert large text or\n // arbitrary files to an upload pipeline) by calling preventDefault().\n onRawPaste?.(e)\n if (e.defaultPrevented) return\n e.preventDefault()\n\n const editor = editorRef.current\n if (!editor) return\n\n // Check for image files in clipboard before processing text\n // Some browsers/OSes provide pasted images via `items` instead of `files` (e.g. screenshots)\n const imageFile =\n Array.from(e.clipboardData.files).find((f) => f.type.startsWith('image/')) ??\n (() => {\n const item = Array.from(e.clipboardData.items).find((i) => i.type.startsWith('image/'))\n return item?.getAsFile() ?? null\n })()\n if (imageFile) {\n onImagePaste?.(imageFile)\n return\n }\n\n // Record undo snapshot\n const currentSegments = readSegmentsFromDOM()\n pushUndo(currentSegments)\n\n // Check for internal segment data (copy/paste within the editor)\n const segmentJson = e.clipboardData.getData('text/prompt-area-segments')\n if (segmentJson) {\n const parsed = parseSegmentsFromClipboard(segmentJson)\n if (parsed && parsed.length > 0) {\n // Insert the copied segments at cursor position\n const range = getSelectionRange()\n if (!range) return\n\n range.deleteContents()\n\n // Merge pasted segments into current segments at cursor position\n const beforePaste = readSegmentsFromDOM()\n const merged = insertSegmentsAtCursor(beforePaste, parsed, editor)\n onChange(merged)\n renderSegmentsToDOM(merged)\n\n // Notify: internal paste with chip data preserved\n onPasteCallback?.({ segments: merged, source: 'internal' })\n for (const seg of parsed) {\n if (seg.type === 'chip') {\n onChipAdd?.(seg)\n }\n }\n\n runTriggerDetection()\n return\n }\n }\n\n // When markdown mode is on, prefer the richest clipboard flavor:\n // 1. text/markdown — some apps (e.g. Slack) hand out markdown directly,\n // preserving nested lists that their text/plain flattens.\n // 2. text/html — convert web/Notion/Docs/GitHub HTML to markdown.\n // Otherwise (markdown off, or neither present) fall back to plain text.\n let text = ''\n if (markdownEnabled) {\n text = e.clipboardData.getData('text/markdown')\n if (text) {\n // Slack over-escapes inert punctuation (e.g. `\\(` `\\)`); unescape\n // parentheses so the source reads cleanly. They carry no markdown\n // meaning, unlike `\\*` / `\\.` / `\\-` which are left intact.\n text = text.replace(/\\\\([()])/g, '$1')\n } else {\n const html = e.clipboardData.getData('text/html')\n // A converter failure (e.g. stack overflow on pathologically deep\n // nesting) must not drop the paste — leave text empty so the\n // text/plain fallback below still runs.\n if (html) {\n try {\n text = htmlToMarkdown(html)\n } catch {\n text = ''\n }\n }\n }\n }\n if (!text) text = e.clipboardData.getData('text/plain')\n if (!text) return\n\n // Normalize pasted list markers (\"- \" → \"•\") so pasted bullets match\n // typed input. Applies to both the HTML→markdown and plain-text paths.\n if (markdownEnabled && normalizeBullets) {\n text = normalizeListPrefixText(text, true)\n }\n\n // Rebuild ordered-list numbering in the pasted block so a copied list with\n // stale numbers lands sequential. Gated on `hasOrderedListRun` so incidental\n // numeric-leading prose (e.g. `1985. Born / 2020. Died`) is left untouched.\n // Done at the raw-string level (the caret collapses to end-of-content after\n // insertion, so no offset remap needed).\n if (markdownEnabled && hasOrderedListRun(text)) {\n text = renumberOrderedListLines(text).text\n }\n\n // Insert plain text at cursor position using Selection API\n const range = getSelectionRange()\n if (!range) return\n\n range.deleteContents()\n\n // Handle multi-line paste: split into lines with BR elements\n const lines = text.split('\\n')\n const fragment = document.createDocumentFragment()\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i]) {\n fragment.appendChild(document.createTextNode(lines[i]))\n }\n if (i < lines.length - 1) {\n fragment.appendChild(document.createElement('br'))\n }\n }\n\n range.insertNode(fragment)\n\n // Move cursor to end of pasted content\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n\n // Normalize DOM, sync model, detect triggers\n normalizeEditorDOM(editor)\n const newSegments = readSegmentsFromDOM()\n\n // Auto-resolve trigger patterns in pasted text (e.g., #readme -> chip)\n const resolvedSegments = resolveTriggersInSegments(newSegments, triggers)\n\n if (resolvedSegments !== newSegments) {\n onChange(resolvedSegments)\n renderSegmentsToDOM(resolvedSegments)\n\n // Notify about auto-resolved chips from pasted text\n for (const seg of resolvedSegments) {\n if (\n seg.type === 'chip' &&\n !newSegments.some(\n (s) =>\n s.type === 'chip' &&\n s.trigger === seg.trigger &&\n s.value === seg.value &&\n s.displayText === seg.displayText,\n )\n ) {\n onChipAdd?.(seg)\n }\n }\n } else {\n onChange(newSegments)\n }\n\n onPasteCallback?.({ segments: resolvedSegments, source: 'external' })\n runTriggerDetection()\n },\n [\n editorRef,\n readSegmentsFromDOM,\n onChange,\n pushUndo,\n runTriggerDetection,\n renderSegmentsToDOM,\n triggers,\n markdownEnabled,\n normalizeBullets,\n onPasteCallback,\n onChipAdd,\n onImagePaste,\n onRawPaste,\n ],\n )\n\n // -----------------------------------------------------------------------\n // Copy: serialize chips into plain text\n // -----------------------------------------------------------------------\n\n const handleCopy = useCallback((e: React.ClipboardEvent<HTMLDivElement>) => {\n e.preventDefault()\n\n const range = getSelectionRange()\n if (!range) return\n\n const fragment = range.cloneContents()\n\n // Walk fragment and serialize, converting chips to their text representation\n const plainText = serializeFragmentToPlainText(fragment)\n e.clipboardData.setData('text/plain', plainText)\n\n // Also serialize chip segments as JSON for internal paste\n const fragmentSegments = serializeFragmentToSegments(fragment)\n const hasChips = fragmentSegments.some((s) => s.type === 'chip')\n if (hasChips) {\n const json = safeJsonStringify(fragmentSegments)\n if (json) {\n e.clipboardData.setData('text/prompt-area-segments', json)\n }\n }\n }, [])\n\n // -----------------------------------------------------------------------\n // Cut: copy + delete\n // -----------------------------------------------------------------------\n\n const handleCut = useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n // First, do the copy\n handleCopy(e)\n\n // Then delete the selection\n const range = getSelectionRange()\n if (!range) return\n\n const currentSegments = readSegmentsFromDOM()\n pushUndo(currentSegments)\n\n range.deleteContents()\n\n const editor = editorRef.current\n if (editor) {\n normalizeEditorDOM(editor)\n }\n\n const newSegments = readSegmentsFromDOM()\n onChange(newSegments)\n runTriggerDetection()\n },\n [handleCopy, editorRef, readSegmentsFromDOM, onChange, pushUndo, runTriggerDetection],\n )\n\n // -----------------------------------------------------------------------\n // Drag & Drop: prevent to avoid unpredictable DOM mutations\n // -----------------------------------------------------------------------\n\n const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n }, [])\n\n const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n }, [])\n\n // -----------------------------------------------------------------------\n // IME Composition: track state, defer trigger detection\n // -----------------------------------------------------------------------\n\n const handleCompositionStart = useCallback(() => {\n isComposing.current = true\n }, [])\n\n const handleCompositionEnd = useCallback(() => {\n isComposing.current = false\n // Run trigger detection after composition ends\n runTriggerDetection()\n }, [runTriggerDetection])\n\n // -----------------------------------------------------------------------\n // Blur: dismiss trigger dropdown with delay (so popover clicks work)\n // -----------------------------------------------------------------------\n\n const handleBlur = useCallback(() => {\n setTimeout(() => {\n const editor = editorRef.current\n if (!editor) return\n\n // Only dismiss if focus didn't move to an element within the editor container\n const activeEl = document.activeElement\n if (activeEl && editor.parentElement?.contains(activeEl)) return\n\n dismissTrigger()\n }, BLUR_DELAY_MS)\n }, [editorRef, dismissTrigger])\n\n // -----------------------------------------------------------------------\n // Undo/Redo: intercept Ctrl+Z / Ctrl+Shift+Z\n // -----------------------------------------------------------------------\n\n const handleKeyDownForUndoRedo = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>): boolean => {\n const isMeta = e.metaKey || e.ctrlKey\n\n if (!isMeta || e.key !== 'z') return false\n\n e.preventDefault()\n const state = undoState.current\n\n if (e.shiftKey) {\n // Redo: Ctrl+Shift+Z\n if (state.redoStack.length === 0) return true\n\n const segments = state.redoStack.pop()\n if (!segments) return true\n\n const current = readSegmentsFromDOM()\n state.undoStack.push(current)\n\n onChange(segments)\n renderSegmentsToDOM(segments)\n onRedo?.(segments)\n } else {\n // Undo: Ctrl+Z\n if (state.undoStack.length === 0) return true\n\n const segments = state.undoStack.pop()\n if (!segments) return true\n\n const current = readSegmentsFromDOM()\n state.redoStack.push(current)\n\n onChange(segments)\n renderSegmentsToDOM(segments)\n onUndo?.(segments)\n }\n\n return true\n },\n [readSegmentsFromDOM, onChange, renderSegmentsToDOM, onUndo, onRedo],\n )\n\n return {\n handlePaste,\n handleCopy,\n handleCut,\n handleDrop,\n handleDragOver,\n handleCompositionStart,\n handleCompositionEnd,\n handleBlur,\n handleKeyDownForUndoRedo,\n pushUndo,\n resetUndoHistory,\n isComposing,\n }\n}\n","'use client'\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { TriggerConfig, TriggerSuggestion } from './types.ts'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype UseTriggerSearchReturn = {\n suggestions: TriggerSuggestion[]\n suggestionsLoading: boolean\n suggestionsError: string | null\n /** Run a search for the given query using the trigger's onSearch config. */\n search: (query: string, config: TriggerConfig) => void\n /** Cancel any in-flight search and reset state. */\n reset: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Manages async trigger search lifecycle: debouncing, AbortController\n * cancellation, race-condition prevention, loading/error state.\n *\n * Extracted from `usePromptArea` so the main hook stays focused on\n * editing concerns while this hook owns the data-fetching side.\n */\nexport function useTriggerSearch(): UseTriggerSearchReturn {\n const [suggestions, setSuggestions] = useState<TriggerSuggestion[]>([])\n const [suggestionsLoading, setSuggestionsLoading] = useState(false)\n const [suggestionsError, setSuggestionsError] = useState<string | null>(null)\n\n // Version counter – belt-and-suspenders alongside AbortController\n const searchVersion = useRef(0)\n // AbortController for cancelling in-flight async searches\n const abortController = useRef<AbortController | null>(null)\n // Debounce timer for search queries\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const reset = useCallback(() => {\n abortController.current?.abort()\n if (debounceTimer.current) clearTimeout(debounceTimer.current)\n setSuggestions([])\n setSuggestionsLoading(false)\n setSuggestionsError(null)\n }, [])\n\n const search = useCallback((query: string, config: TriggerConfig) => {\n if (!config.onSearch) return\n\n // Cancel any previous in-flight request and pending debounce\n abortController.current?.abort()\n if (debounceTimer.current) clearTimeout(debounceTimer.current)\n\n setSuggestionsLoading(true)\n setSuggestionsError(null)\n searchVersion.current++\n const version = searchVersion.current\n\n const controller = new AbortController()\n abortController.current = controller\n const { onSearch, onSearchError, searchDebounceMs } = config\n\n const executeSearch = () => {\n const result = onSearch(query, { signal: controller.signal })\n\n if (result instanceof Promise) {\n void result.then(\n (items) => {\n if (controller.signal.aborted || searchVersion.current !== version) return\n setSuggestions(items)\n setSuggestionsLoading(false)\n },\n (error: unknown) => {\n if (controller.signal.aborted || searchVersion.current !== version) return\n // Silently ignore AbortError (expected when superseded)\n if (error instanceof DOMException && error.name === 'AbortError') return\n setSuggestionsError(error instanceof Error ? error.message : 'Search failed')\n setSuggestionsLoading(false)\n onSearchError?.(error)\n },\n )\n } else {\n setSuggestions(result)\n setSuggestionsLoading(false)\n }\n }\n\n // Debounce subsequent searches but fire immediately for the initial empty query\n if (searchDebounceMs && searchDebounceMs > 0 && query.length > 0) {\n debounceTimer.current = setTimeout(executeSearch, searchDebounceMs)\n } else {\n executeSearch()\n }\n }, [])\n\n // Clean up on unmount\n useEffect(() => {\n return () => {\n abortController.current?.abort()\n if (debounceTimer.current) clearTimeout(debounceTimer.current)\n }\n }, [])\n\n return {\n suggestions,\n suggestionsLoading,\n suggestionsError,\n search,\n reset,\n }\n}\n","'use client'\n\nimport { cn } from '../../lib/utils.ts'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type {\n Segment,\n TriggerConfig,\n ActiveTrigger,\n TriggerSuggestion,\n ChipSegment,\n PromptAreaHandle,\n} from './types.ts'\nimport {\n detectActiveTrigger,\n isValidTriggerPosition,\n segmentsToPlainText,\n plainTextToSegments,\n segmentsEqual,\n resolveChip,\n 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 { 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 { 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 attachmentUrl,\n}: {\n item: TranscriptItem\n fileUrl?: (path: string) => string\n attachmentUrl?: (attachmentId: string) => string\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 ? <MessageContent>{item.text}</MessageContent> : 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} />\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\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 className?: string\n}\n\nexport function Transcript({ state, fileUrl, attachmentUrl, 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\n key={`${item.kind}:${item.id}`}\n item={item}\n fileUrl={fileUrl}\n attachmentUrl={attachmentUrl}\n />\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 attachmentUrl={sessionId ? (id) => client.attachmentUrl(sessionId, id) : 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":["AlertDialogPrimitive","SonnerToaster"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/agent/SessionList.tsx"],"sourcesContent":["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 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 { 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 { 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 * 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 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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"}
|