@tangle-network/agent-app 0.60.194 → 0.60.196
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/dist/assistant/index.js +2 -2
- package/dist/chat-react/index.js +2 -2
- package/dist/chat-routes/attachment-store.d.ts +18 -0
- package/dist/chat-routes/attachment-upload.d.ts +20 -2
- package/dist/chat-routes/attachment-validation.d.ts +3 -0
- package/dist/chat-routes/binary-sniff.d.ts +28 -0
- package/dist/chat-routes/index.js +279 -15
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/{chunk-6RFNLZCK.js → chunk-2XUIWCRW.js} +26 -5
- package/dist/{chunk-6RFNLZCK.js.map → chunk-2XUIWCRW.js.map} +1 -1
- package/dist/{chunk-PBQYVAB6.js → chunk-2ZRXJ3NO.js} +138 -34
- package/dist/chunk-2ZRXJ3NO.js.map +1 -0
- package/dist/operator/chat-adapter.d.ts +2 -0
- package/dist/operator/client.d.ts +10 -0
- package/dist/operator/contract.d.ts +12 -4
- package/dist/operator/index.js +41 -14
- package/dist/operator/index.js.map +1 -1
- package/dist/operator/server.d.ts +18 -0
- package/dist/web-react/index.js +7 -3
- package/dist/web-react/use-composer-attachments.d.ts +13 -1
- package/package.json +1 -1
- package/dist/chunk-PBQYVAB6.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web-react/use-dictation.ts","../src/web-react/lazy-load-boundary.tsx","../src/web-react/chat-composer.tsx","../src/web-react/use-composer-attachments.ts","../src/web-react/composer-mode-controls.tsx","../src/web-react/entry-composer.tsx"],"sourcesContent":["/**\n * `useDictation` — the capture half of composer dictation.\n *\n * Dictation splits at a clean seam: the browser owns capture (`getUserMedia` +\n * `MediaRecorder`), the host owns what the audio MEANS (transcription —\n * `sequences-react`'s Whisper provider — or a straight upload). This hook is\n * the capture half and nothing else: it asks for the mic, records, ticks whole\n * seconds while it does, and hands the assembled `Blob` to the host's\n * `onDictate`. A hook rather than composer-private code, because a host whose\n * composer is fully composed (hotkey, push-to-talk) needs the same capture\n * without re-deriving it.\n *\n * The rules the implementation exists to hold:\n *\n * - **Unsupported is a render signal, not an exception.** A browser without\n * `MediaRecorder`/`getUserMedia` gets `supported: false`, and the composer\n * renders no dead button. `start()` stays a no-op rather than throwing, so\n * a host that wired it to a hotkey cannot crash on such a browser.\n * - **The mic is released the moment recording ends.** Tracks are stopped in\n * every exit — stop, error, cancel-during-prompt, unmount. A red dot the\n * browser keeps showing after the composer says \"idle\" is the failure this\n * is written against.\n * - **A denied prompt is a message, not a crash.** `NotAllowedError` and a\n * missing device are reported through `onError` as words the composer can\n * show; the hook returns to idle.\n * - **Unmount discards.** A composer that unmounts mid-recording delivers\n * nothing: the host it would have called has moved on, and an arriving\n * transcript would land in a conversation the user left.\n * - **Duration is measured, not counted.** `durationSeconds` comes off the\n * clock at stop; the one-second ticker drives only the visible elapsed\n * display, so a throttled timer never falsifies the delivered figure.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n/** The audio a finished recording hands to the host. */\nexport interface DictationAudio {\n /** The assembled recording, typed with the MIME the recorder actually used. */\n readonly blob: Blob\n /** `blob.type`, surfaced so a host can switch on it without touching the blob. */\n readonly mimeType: string\n /** Clock-measured whole seconds between start and stop. */\n readonly durationSeconds: number\n}\n\nexport interface UseDictationOptions {\n /** The host callback: receive the recording. Transcription is the host's. */\n onDictate: (audio: DictationAudio) => void\n /** Capture failures in words (\"Microphone access was denied…\"). Optional —\n * the composer shows its own notice either way; this is for hosts that log. */\n onError?: (message: string) => void\n}\n\nexport interface DictationControls {\n /** Whether this browser can record at all. When false, render no affordance. */\n readonly supported: boolean\n readonly recording: boolean\n /** Whole seconds since the current recording started; drives the indicator. */\n readonly elapsedSeconds: number\n /** Ask for the mic and start. A no-op while a recording or a prompt is open. */\n readonly start: () => void\n /** Stop and deliver. Cancels a still-pending permission prompt instead. */\n readonly stop: () => void\n}\n\n/** Preference order: opus-in-webm first, Safari's mp4 last, UA default if none. */\nconst PREFERRED_MIME_TYPES = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'] as const\n\n/** The mime to ask the recorder for, or `undefined` to take the UA default. */\nexport function pickDictationMimeType(): string | undefined {\n if (typeof MediaRecorder === 'undefined' || typeof MediaRecorder.isTypeSupported !== 'function') {\n return undefined\n }\n for (const type of PREFERRED_MIME_TYPES) {\n if (MediaRecorder.isTypeSupported(type)) return type\n }\n return undefined\n}\n\n/** Capture support is a property of the browser, so it is read once per mount. */\nfunction detectDictationSupport(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n typeof navigator.mediaDevices?.getUserMedia === 'function' &&\n typeof MediaRecorder !== 'undefined'\n )\n}\n\n/** The failure as a sentence. The denied prompt is the common case and the one\n * whose generic name (\"NotAllowedError\") says nothing to a reader. */\nexport function dictationErrorMessage(error: unknown): string {\n if (error instanceof DOMException) {\n if (error.name === 'NotAllowedError') return 'Microphone access was denied — allow it in the browser to dictate.'\n if (error.name === 'NotFoundError') return 'No microphone found on this device.'\n }\n return 'Could not start recording.'\n}\n\n/** `0:00`, `0:09`, `1:05`, `60:00` — minutes unbounded, seconds always two digits. */\nexport function formatDictationElapsed(totalSeconds: number): string {\n const safe = Number.isFinite(totalSeconds) && totalSeconds > 0 ? Math.floor(totalSeconds) : 0\n const minutes = Math.floor(safe / 60)\n const seconds = safe % 60\n return `${minutes}:${String(seconds).padStart(2, '0')}`\n}\n\n/** One capture's mutable internals, kept in a ref: they move with recorder\n * events, not with renders. */\ninterface DictationSession {\n readonly stream: MediaStream\n readonly recorder: MediaRecorder\n readonly chunks: Blob[]\n readonly mimeType: string\n readonly startedAt: number\n /** Set when the capture must deliver nothing (unmount, recorder failure). */\n cancelled: boolean\n}\n\n/** Release the mic. Idempotent — every exit path ends here. */\nfunction releaseStream(stream: MediaStream): void {\n for (const track of stream.getTracks()) track.stop()\n}\n\nexport function useDictation({ onDictate, onError }: UseDictationOptions): DictationControls {\n const [supported] = useState(detectDictationSupport)\n const [recording, setRecording] = useState(false)\n const [elapsedSeconds, setElapsedSeconds] = useState(0)\n\n const sessionRef = useRef<DictationSession | null>(null)\n /** Cancels a start whose getUserMedia has not resolved yet. */\n const cancelPendingStartRef = useRef<(() => void) | null>(null)\n // The recorder's event handlers fire outside React's render, so they read the\n // LATEST callbacks — a re-rendered host must not have its audio delivered to\n // the props the recording started with.\n const callbacksRef = useRef({ onDictate, onError })\n callbacksRef.current = { onDictate, onError }\n\n // The visible elapsed ticker. Follows `recording`; reset on each start so a\n // reused composer never opens at the previous capture's stale count.\n useEffect(() => {\n if (!recording) return\n setElapsedSeconds(0)\n const id = setInterval(() => setElapsedSeconds((s) => s + 1), 1000)\n return () => clearInterval(id)\n }, [recording])\n\n /** End the session: release the mic, reset state. Delivery is onstop's job. */\n const teardown = useCallback((cancelled: boolean) => {\n const session = sessionRef.current\n if (session === null) return\n session.cancelled = session.cancelled || cancelled\n sessionRef.current = null\n releaseStream(session.stream)\n setRecording(false)\n }, [])\n\n const stop = useCallback(() => {\n // A stop while the permission prompt is still open cancels the start: when\n // the stream arrives it is released unused, and nothing ever records.\n cancelPendingStartRef.current?.()\n cancelPendingStartRef.current = null\n const session = sessionRef.current\n if (session === null || session.cancelled) return\n // stop() flushes the buffered chunk (dataavailable) and THEN fires stop —\n // the blob is assembled in onstop, so a stop mid-chunk loses nothing.\n if (session.recorder.state !== 'inactive') session.recorder.stop()\n }, [])\n\n const start = useCallback(() => {\n if (!supported) return\n if (sessionRef.current !== null || cancelPendingStartRef.current !== null) return\n\n let pendingCancelled = false\n cancelPendingStartRef.current = () => {\n pendingCancelled = true\n }\n\n navigator.mediaDevices.getUserMedia({ audio: true }).then(\n (stream) => {\n cancelPendingStartRef.current = null\n if (pendingCancelled) {\n releaseStream(stream)\n return\n }\n const mimeType = pickDictationMimeType()\n const recorder = new MediaRecorder(stream, mimeType === undefined ? undefined : { mimeType })\n const session: DictationSession = {\n stream,\n recorder,\n chunks: [],\n mimeType: recorder.mimeType || mimeType || '',\n startedAt: Date.now(),\n cancelled: false,\n }\n sessionRef.current = session\n\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) session.chunks.push(event.data)\n }\n\n recorder.onstop = () => {\n teardown(session.cancelled)\n if (session.cancelled) return\n const blob = new Blob(session.chunks, { type: session.mimeType })\n if (blob.size === 0) {\n // A tap on/off can produce no bytes at all. Handing the host a\n // 0-byte blob reads as a recording that happened; it did not.\n callbacksRef.current.onError?.('Nothing was recorded.')\n return\n }\n const durationSeconds = Math.max(0, Math.round((Date.now() - session.startedAt) / 1000))\n callbacksRef.current.onDictate({ blob, mimeType: session.mimeType, durationSeconds })\n }\n\n recorder.onerror = () => {\n // The capture is dead; what matters is that the mic is released and\n // the hook is not wedged — the next start builds a fresh session.\n teardown(true)\n callbacksRef.current.onError?.('Recording stopped unexpectedly.')\n }\n\n recorder.start()\n setRecording(true)\n },\n (error: unknown) => {\n cancelPendingStartRef.current = null\n if (pendingCancelled) return\n callbacksRef.current.onError?.(dictationErrorMessage(error))\n },\n )\n }, [supported, teardown])\n\n // Unmount mid-recording discards the capture: mark it cancelled so onstop\n // delivers nothing, then stop the recorder to flush its events, and release\n // the mic whether or not those events ever fire.\n useEffect(\n () => () => {\n cancelPendingStartRef.current?.()\n cancelPendingStartRef.current = null\n const session = sessionRef.current\n if (session === null) return\n session.cancelled = true\n sessionRef.current = null\n try {\n if (session.recorder.state !== 'inactive') session.recorder.stop()\n } finally {\n releaseStream(session.stream)\n }\n },\n [],\n )\n\n return { supported, recording, elapsedSeconds, start, stop }\n}\n","import { Component, createElement, lazy, useEffect, useState, type ComponentType, type ReactElement, type ReactNode } from 'react'\n\ninterface LazyLoadBoundaryProps {\n children: ReactNode\n renderFailure: (error: unknown) => ReactNode\n}\n\n/** Keep a failed lazy import inside the surface that requested it. */\nexport class LazyLoadBoundary extends Component<LazyLoadBoundaryProps, { failed: boolean; error: unknown }> {\n state = { failed: false, error: null as unknown }\n\n static getDerivedStateFromError(error: unknown) {\n return { failed: true, error }\n }\n\n render() {\n if (this.state.failed) return this.props.renderFailure(this.state.error)\n return this.props.children\n }\n}\n\nconst ROUTE_RELOAD_KEY = 'agent-app:route-chunk-reload-at'\nconst ROUTE_RELOAD_WINDOW_MS = 60_000\nconst ROUTE_RELOAD_PARAM = '__agent_app_route_reload'\nconst CHUNK_LOAD_FAILURE =\n /failed to fetch dynamically imported module|error loading dynamically imported module|importing a module script failed|loading chunk .* failed|failed to load module script/i\n\n/** A stale deployed HTML page can reference a chunk removed by the next deploy. */\nfunction isChunkLoadFailure(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false\n const candidate = error as { name?: unknown; message?: unknown }\n return candidate.name === 'ChunkLoadError' ||\n (typeof candidate.message === 'string' && CHUNK_LOAD_FAILURE.test(candidate.message))\n}\n\nfunction loadFreshDocument(): void {\n const url = new URL(window.location.href)\n url.searchParams.set(ROUTE_RELOAD_PARAM, Date.now().toString(36))\n window.location.replace(url.toString())\n}\n\nfunction RouteLoadFailure({ error, autoReloadOnChunkError }: { error: unknown; autoReloadOnChunkError: boolean }) {\n useEffect(() => {\n if (!autoReloadOnChunkError || !isChunkLoadFailure(error) || typeof window === 'undefined') return\n try {\n const lastReload = Number(window.sessionStorage.getItem(ROUTE_RELOAD_KEY) ?? 0)\n const elapsed = Date.now() - lastReload\n if (lastReload > 0 && elapsed >= 0 && elapsed < ROUTE_RELOAD_WINDOW_MS) return\n window.sessionStorage.setItem(ROUTE_RELOAD_KEY, String(Date.now()))\n loadFreshDocument()\n } catch {\n // Storage may be disabled. The visible reload action remains available.\n }\n }, [error, autoReloadOnChunkError])\n\n return (\n <div role=\"alert\" className=\"rounded-xl border border-border bg-card p-5 text-foreground\">\n <p className=\"font-medium\">This page couldn’t load.</p>\n <p className=\"mt-1 text-sm text-muted-foreground\">Reload to try again.</p>\n <button\n type=\"button\"\n className=\"mt-4 rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n onClick={loadFreshDocument}\n >\n Reload page\n </button>\n </div>\n )\n}\n\nexport interface RouteChunkBoundaryProps {\n children: ReactNode\n /** Enable only where a reload cannot discard unsaved work. Default: false. */\n autoReloadOnChunkError?: boolean\n}\n\n/** Wrap a lazy route and its Suspense fallback; recovery requests a fresh document URL. */\nexport function RouteChunkBoundary({ children, autoReloadOnChunkError = false }: RouteChunkBoundaryProps) {\n useEffect(() => {\n const url = new URL(window.location.href)\n if (!url.searchParams.has(ROUTE_RELOAD_PARAM)) return\n url.searchParams.delete(ROUTE_RELOAD_PARAM)\n window.history.replaceState(window.history.state, '', url)\n }, [])\n return (\n <LazyLoadBoundary renderFailure={(error) => <RouteLoadFailure error={error} autoReloadOnChunkError={autoReloadOnChunkError} />}>\n {children}\n </LazyLoadBoundary>\n )\n}\n\n/**\n * A lazily loaded route that renders synchronously once its module has loaded.\n *\n * `React.lazy` calls its loader only on first render, so even an already\n * downloaded chunk suspends once, and React holds that Suspense retry for its\n * ~300 ms fallback throttle. Hospitality measured 305 ms before the first data\n * read on every first visit to a lazy route, with the chunk already cached.\n * Each mount decides once which component it renders, so a route mounted\n * before its module arrived keeps its state when it re-renders later.\n */\nexport type LazyRoute<P extends object> = ((props: P) => ReactElement) & { preload: () => Promise<void> }\n\nexport function lazyRoute<P extends object>(load: () => Promise<ComponentType<P>>): LazyRoute<P> {\n let loaded: ComponentType<P> | null = null\n let pending: Promise<ComponentType<P>> | null = null\n const start = () => (pending ??= load().then(\n (component) => (loaded = component),\n (error: unknown) => { pending = null; throw error },\n ))\n const Lazy = lazy(() => start().then((component) => ({ default: component })))\n function Route(props: P) {\n const [Ready] = useState(() => loaded)\n return Ready ? createElement(Ready, props) : createElement(Lazy as unknown as ComponentType<P>, props)\n }\n return Object.assign(Route, { preload: () => start().then(() => undefined) })\n}\n\n/** Load routes while the browser is idle (at most `timeoutMs` later), so a first visit waits only on its data. */\nexport function preloadWhenIdle(routes: ReadonlyArray<{ preload: () => Promise<void> }>, timeoutMs = 3000): () => void {\n const run = () => { for (const route of routes) void route.preload().catch(() => { /* The route's boundary reports a failed load when it renders. */ }) }\n if (typeof requestIdleCallback === 'function') {\n const handle = requestIdleCallback(run, { timeout: timeoutMs })\n return () => cancelIdleCallback(handle)\n }\n const handle = setTimeout(run, Math.min(timeoutMs, 1000))\n return () => clearTimeout(handle)\n}\n","/**\n * ChatComposer — the shared message input every agent app used to hand-roll:\n * an auto-resizing textarea (Enter sends, Shift+Enter inserts a newline), an\n * opt-in attach + drag-and-drop + clipboard-paste surface with pending-file\n * chips, an opt-in `@`-mention mode (`mention`) that swaps the textarea for a\n * lazily loaded rich input with atomic mention pills, a streaming Stop/Send\n * toggle, a slot for inline controls (model picker, reasoning effort), and a\n * Cmd/Ctrl+L focus shortcut.\n *\n * Files arrive by three routes — the picker dialog, a drop, and a paste — and\n * all three funnel through `accept` (`./composer-file-accept`) before they\n * reach `onAttach`, so a type the picker will not offer cannot get in by\n * another route. What `accept` refuses goes to `onRejectFiles` with a reason;\n * without that prop a refusal is silent, which is what the native picker also\n * does. Size and count limits stay the host's job — `useComposerAttachments`\n * owns them, because they depend on what is already staged.\n *\n * A REJECTED send never destroys the draft. The input clears optimistically —\n * the composer stays editable while a turn streams precisely so the next\n * message can be typed against a live answer, and holding the sent text in the\n * box until the server confirms would put the clear on a collision course with\n * that typing. So the clear happens immediately and the draft is held until the\n * send is known to have landed: a handler that throws, rejects, or returns\n * `{ ok: false }` puts the exact bytes back with the caret where it was, names\n * the reason, and reports `onSendFailed` so the host can restore the\n * attachments it consumed. If the user has already typed a replacement, the\n * unsent text is shown in the notice with its own Retry instead of overwriting\n * what they typed — neither draft is ever destroyed.\n *\n * Styling contract matches the rest of `web-react`: Tailwind over the shared\n * design tokens (`bg-card`, `border-border`, `text-foreground`, `bg-primary`, …)\n * and inline-SVG glyphs. It defines NO `--chat-*` / `--brand-*` custom\n * properties, so it themes correctly in any shell that provides the standard\n * tokens — the input renders on-palette instead of collapsing to unstyled\n * fallbacks when a host hasn't defined a private chat-token set.\n */\n\nimport {\n lazy,\n Suspense,\n useCallback,\n useEffect,\n useMemo,\n useId,\n useRef,\n useState,\n type ChangeEvent,\n type ClipboardEvent,\n type DragEvent,\n type KeyboardEvent,\n type ReactNode,\n} from 'react'\n\nimport { focusFieldWithin } from '@tangle-network/ui/utils'\n\nimport {\n filterAcceptedFiles,\n renamePastedImages,\n type ComposerFileRejection,\n} from './composer-file-accept'\nimport { filterCommandPaletteItems, type CommandPaletteItem } from '../session-shell/index'\nimport { OVERLAY_SHADOW, POPOVER_OPTION_FOCUS, PopoverSurface } from './controls'\nimport { formatDictationElapsed, useDictation, type DictationAudio } from './use-dictation'\nimport type { ComposerMentionProp } from './use-file-mentions'\nimport { LazyLoadBoundary } from './lazy-load-boundary'\n\n/**\n * The TipTap editor is a lazy chunk: only consumers that pass `mention` pull\n * the editor stack into their bundle, and only when the mention path renders.\n * The `@tiptap/*` packages behind it are OPTIONAL peers reached only\n * through `loadMentionEditor`'s dynamic imports — a bundler replaces a missing\n * one with a runtime-throwing stub, so a consumer without them still builds.\n */\nfunction createLazyMentionEditor() {\n return lazy(() => import('./mention-editor').then((m) => m.loadMentionEditor()))\n}\n\n// ── glyphs (no icon-library dependency) ───────────────────────────────────\n\n/** The focus-shortcut hint names the platform's modifier: Cmd on Apple,\n * Ctrl everywhere else (the handler itself listens for both). SSR-safe —\n * defaults to Ctrl when there's no navigator to ask. */\nconst IS_APPLE_PLATFORM =\n typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/i.test(navigator.platform)\n\nfunction SendGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z\" />\n </svg>\n )\n}\n\nfunction StopGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden>\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n </svg>\n )\n}\n\nfunction ArrowUpGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 19V5M5 12l7-7 7 7\" />\n </svg>\n )\n}\n\nfunction PaperclipGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n </svg>\n )\n}\n\nfunction FolderGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z\" />\n <path d=\"M12 10v6m-3-3h6\" />\n </svg>\n )\n}\n\nfunction CloseGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n )\n}\n\nfunction RetryGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n )\n}\n\nfunction UploadGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12\" />\n </svg>\n )\n}\n\nfunction MicGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\" />\n <path d=\"M5 10v1a7 7 0 0 0 14 0v-1M12 18v4\" />\n </svg>\n )\n}\n\n// ── component ──────────────────────────────────────────────────────────────\n\n/** Prompt-part descriptor an uploaded file carries (the upload route's\n * `UploadedChatFile.part`), echoed back in the turn body on send. Mirrors\n * `/chat-routes`' wire shape structurally — no server import here. */\nexport interface ComposerFilePart {\n type: 'image' | 'file'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n content?: string\n}\n\nexport interface ComposerFile {\n id: string\n name: string\n size?: number\n kind: 'file' | 'folder'\n /** Number of files inside, for a folder chip. */\n fileCount?: number\n status: 'pending' | 'uploading' | 'ready' | 'error'\n /** Uploaded part descriptor; set once the upload route returns. Only\n * `status: 'ready'` files with a part travel on a parts-aware send. */\n part?: ComposerFilePart\n /** Object URL for an image thumbnail on the chip. The host owns the URL's\n * whole life — `URL.createObjectURL` when the file is staged,\n * `URL.revokeObjectURL` when it leaves — and the composer only reads it.\n * `useComposerAttachments` already does both. */\n previewUrl?: string\n /** Why this file failed, shown on the chip while `status: 'error'`. Without\n * it an error chip is red and mute, which tells the user nothing. */\n errorMessage?: string\n}\n\n/** A piece of context the agent will see beside the next message — an open\n * file, a selected record, a pinned document. Rendered as its own chip row,\n * separate from staged attachments: context is what the turn already carries,\n * an attachment is what the user is adding to it. */\nexport interface ComposerContextItem {\n id: string\n label: string\n icon?: ReactNode\n /** Omit for a chip the user cannot dismiss. */\n onRemove?: () => void\n}\n\n/** A send the host refused. `error` is shown verbatim in the composer's notice;\n * omit it for the generic copy. */\nexport interface ComposerSendRejected {\n ok: false\n error?: string\n}\n\n/**\n * What a send handler reports back. `void` — what every handler returned before\n * this existed — reads as accepted, so wiring stays unchanged; a thrown error, a\n * rejected promise, or `{ ok: false }` is the rejection that restores the draft.\n * A handler that resolves only when the whole turn finishes still reports\n * correctly: the input already cleared on dispatch, so the answer only decides\n * whether the draft comes back.\n */\nexport type ComposerSendOutcome = void | { ok: true } | ComposerSendRejected\nexport type ComposerSendResult = ComposerSendOutcome | Promise<ComposerSendOutcome>\n\n/**\n * A send handler, typed as a UNION with the legacy `=> void` signature rather\n * than as `(…) => ComposerSendResult` alone.\n *\n * TypeScript's return-type-`void` rule accepts a function returning ANYTHING\n * where a `=> void` is expected, and that rule fires only when the target's\n * return type is exactly `void` — not when it is a union that contains `void`.\n * So narrowing this prop to `ComposerSendResult` would reject handler shapes\n * that compiled against the shipped `onSend?: (message: string) => void`:\n * `onSend={(m) => rows.push(m)}` (returns `number`) and\n * `onSend={(m) => append({ role: 'user', content: m })}` (an ai-sdk append\n * returns `Promise<string | null | undefined>`) both stop compiling, on a\n * package whose pinned consumers must never need a source edit to take a minor.\n *\n * The union keeps both: a legacy handler lands on the first member, and a\n * handler that reports an outcome lands on the second. A call through it\n * resolves to `void | ComposerSendResult`, which IS `ComposerSendResult`, so\n * the composer reads the outcome exactly as before.\n */\nexport type ComposerSendHandler =\n | ((message: string) => void)\n | ((message: string) => ComposerSendResult)\n\n/** @see ComposerSendHandler — the parts-aware arity, same union for the same reason. */\nexport type ComposerSendPartsHandler =\n | ((message: string, parts: ComposerFilePart[]) => void)\n | ((message: string, parts: ComposerFilePart[]) => ComposerSendResult)\n\n/** The rejected send, handed to `onSendFailed` so the host can undo whatever it\n * cleared optimistically — most importantly the staged attachments, which the\n * composer does not own (`pendingFiles` is a prop). */\nexport interface ComposerSendFailure {\n /** The reason as the composer renders it. */\n message: string\n /** The user's exact draft, untrimmed. */\n text: string\n /** The parts the rejected send carried. */\n parts: ComposerFilePart[]\n /** Whatever the handler threw / rejected with, or the `{ ok: false }` value. */\n error: unknown\n /** True when the draft was put back in the textarea (the box was empty).\n * False means the user had typed a replacement, so the unsent text is held in\n * the notice instead. */\n restored: boolean\n}\n\n/**\n * One `/` command the composer offers. Typing `/` at position 0 opens the\n * command menu; the rest of the token filters it (the same prefix > substring\n * > token-order ranking as the command palette). Picking a command CLEARS the\n * token from the draft and calls `run` — what the command does (a route, a\n * dialog, a draft transformation) is the product's business.\n */\nexport interface SlashCommand {\n /** Command name without the leading slash: `model`, `clear`. */\n name: string\n /** One line of what it does, rendered beside the name. */\n description: string\n run: () => void\n}\n\n/** A skill the host has made eligible for this turn. The id is the value the\n * host validates and sends; the name and description are display metadata. */\nexport interface ComposerSkillOption {\n id: string\n name: string\n description: string\n}\n\nexport interface ChatComposerProps {\n /** Send the trimmed message, or an empty message when a skill is selected.\n * Attached files travel separately via\n * `onAttach` + `pendingFiles` (the host consumes and clears them on send).\n * Optional when `onSendParts` is wired.\n *\n * Report a refused send by throwing, rejecting, or returning `{ ok: false }`\n * — the composer restores the draft rather than losing it. */\n onSend?: ComposerSendHandler\n /** Parts-aware send: receives the trimmed message plus the `part`\n * descriptors of every `ready` pending file. Takes precedence over\n * `onSend`; enables file-only sends (empty text, ≥1 ready part).\n *\n * Same rejection contract as `onSend`. */\n onSendParts?: ComposerSendPartsHandler\n /** Notified when a send is rejected, after the composer has restored what it\n * owns. The host uses it to put back the `pendingFiles` it consumed. */\n onSendFailed?: (failure: ComposerSendFailure) => void\n /** Notice copy when the handler names no reason of its own. */\n sendFailureMessage?: string\n /** Stop the in-flight turn; shown in place of Send while `isStreaming`. */\n onCancel?: () => void\n isStreaming?: boolean\n /** Block input + send (e.g. while restoring). Distinct from `isStreaming`,\n * which keeps the textarea editable so the next turn can be composed. */\n disabled?: boolean\n /** Block sending while keeping the draft editable (e.g. model choices load). */\n sendDisabled?: boolean\n placeholder?: string\n\n /** Controlled value. Omit for self-managed internal state (cleared on send). */\n value?: string\n onValueChange?: (value: string) => void\n /** Initial text in uncontrolled mode; ignored when `value` is provided. */\n initialValue?: string\n\n /** One-shot external prefill: when this becomes a non-null string the\n * composer adopts it as the draft (replacing any current draft), focuses the\n * input with the caret at the end, and reports consumption via\n * `onSeedApplied` so the host can clear its seed state. */\n seed?: string | null\n onSeedApplied?: () => void\n\n /** Inline controls (e.g. `<ModelPicker/>` + `<EffortPicker/>` or\n * `<AgentSessionControls/>`). */\n controls?: ReactNode\n /**\n * Where {@link controls} sit. `inline` (default) puts them on the card's own\n * action row, beside attach and Send — the model a turn will use reads as\n * part of the input rather than as a separate widget floating above it.\n * `above` keeps them outside the card, for a host that wants the input to be\n * nothing but the input.\n */\n controlsPlacement?: 'above' | 'inline'\n\n /** Attachments are opt-in: pass `onAttach` to show the attach button, accept\n * drag-and-drop and clipboard paste onto the input, and render\n * `pendingFiles` chips. */\n onAttach?: (files: FileList) => void\n onAttachFolder?: (files: FileList) => void\n pendingFiles?: ComposerFile[]\n onRemoveFile?: (id: string) => void\n /** Pass it and a chip with `status: 'error'` gains a retry button. */\n onRetryFile?: (id: string) => void\n /**\n * File types the composer takes, in the native `<input accept>` grammar.\n * Enforced on every ingress route — the picker dialog (which the user can\n * override with \"All Files\"), drag-and-drop, and clipboard paste — so a type\n * the picker will not offer cannot arrive by another route. A non-matching\n * file goes to `onRejectFiles` and never reaches `onAttach`. Folder attach is\n * exempt: directory selection has no native accept semantics.\n */\n accept?: string\n /** Called with the files `accept` removed from a pick, drop, or paste, each\n * with a reason. Without it a refusal is silent — the same feedback the\n * native picker gives for a type it will not offer. */\n onRejectFiles?: (rejections: ComposerFileRejection[]) => void\n dropTitle?: string\n dropDescription?: string\n\n /** Context the agent will see beside the next message, as its own chip row\n * above the input. */\n contextItems?: ReadonlyArray<ComposerContextItem>\n\n /**\n * Let a staged file stand in for message text, so the send control stays live\n * while an upload is in flight instead of going dead with nothing to explain\n * it. Default false, where an empty message needs a `ready` file.\n *\n * It does NOT make an unfinished file sendable. A turn whose only content is a\n * file that is still uploading or has failed never reaches the send handler —\n * it would arrive empty and the attachment would be lost. The composer\n * refuses it and names the reason in its notice\n * ({@link attachmentsNotReadyMessage}). So the flag decides whether the\n * control is live, and the composer keeps the integrity gate rather than\n * leaving each host to re-derive it.\n */\n canSubmitAttachmentsOnly?: boolean\n /** Notice copy when a send is refused because no staged file is ready yet.\n * Defaults to wording chosen from whether a file failed or is still\n * uploading. */\n attachmentsNotReadyMessage?: string\n /**\n * Let Enter and Send keep firing while `isStreaming`, for a surface that\n * queues the next turn rather than blocking on the current one. Default\n * false. The button still flips to Stop while a turn streams, so this opens\n * the keyboard path, not a second button.\n */\n canSubmitWhileBusy?: boolean\n\n /** Focus the input on mount — for a surface whose whole job is the input\n * (an entry/hero composer), never for one docked under a transcript. */\n autoFocus?: boolean\n /** Rows the input shows before it grows. Default 2. */\n minRows?: number\n /** Pixel height the input grows to before it scrolls. Default 168. */\n maxHeight?: number\n /** Content between the controls slot and Send — a token meter, a cost, a\n * status line, a model picker. It sits outside the controls slot, so a\n * wrapping picker set cannot push it away; it travels with Send onto its\n * own line when the row is too narrow for both, and wraps inside that line\n * rather than overflowing the card. */\n trailing?: ReactNode\n /**\n * Opt-in `@`-mentions. Present ⇒ the textarea is swapped for a lazily\n * loaded TipTap rich input that renders mentions as atomic pills and\n * serializes them to `@<id>` in the value; absent ⇒ exactly the plain\n * textarea, with no TipTap in the bundle. Wire `useFileMentions().mention`\n * straight in. The six `@tiptap/*` packages (core, extension-mention, pm,\n * react, starter-kit, suggestion) are OPTIONAL peers — a consumer installs\n * them to use this prop.\n *\n * General `slashCommands` remain disabled while `mention` is set; the\n * skill picker has explicit keyboard handling in both input modes. Seed and\n * failed-send drafts still apply in mention mode, but caret placement (a\n * textarea affordance) degrades to content-only.\n */\n mention?: ComposerMentionProp\n /** `/` commands offered when the draft is exactly a leading slash token.\n * Omit (or pass []) and `/` types as ordinary text. Inert while `mention`\n * is set — see {@link mention}. */\n slashCommands?: SlashCommand[]\n /** Skills available in the current authenticated workspace and harness.\n * The host resolves eligibility; this list only drives selection UI. */\n eligibleSkills?: ReadonlyArray<ComposerSkillOption>\n /** The host-owned skill selected for the next turn. */\n selectedSkillId?: string | null\n /** Picking or removing a skill reports its typed id and display metadata.\n * The host persists selection through queued turns and rejected sends. */\n onSelectedSkillChange?: (skill: ComposerSkillOption | null) => void\n /** Dictation is opt-in: pass `onDictate` and the action row gains a mic\n * button (browsers without `MediaRecorder`/`getUserMedia` render none).\n * Click starts the capture; the button flips to a stop control with the\n * running elapsed seconds; stop hands the recorded audio blob here. The\n * composer owns capture only — turning the audio into text (e.g. the\n * Whisper provider from `sequences-react`) is the host's. */\n onDictate?: (audio: DictationAudio) => void\n /** Capture failures (a denied mic prompt, no device), after the composer has\n * shown its own dismissible notice. For hosts that log or track. */\n onDictateError?: (message: string) => void\n\n /** Cmd/Ctrl+L focuses the input and shows the hint. Default true. */\n focusShortcut?: boolean\n /** Called when focus enters the composer: the input or any of its controls.\n * Pass `useWorkspacePrewarm().reassert` so a box that suspended while the\n * tab sat open starts resuming before the member sends. */\n onFocusWithin?: () => void\n /** Float the card on a soft two-layer foreground-tinted shadow (opt-in).\n * Elevation only — radius, ring, and control layout are unchanged. */\n floating?: boolean\n /** Send button label. Default \"Send\". */\n sendLabel?: string\n /** Send control shape. `pill` (default) is the labeled button; `icon` is the\n * 34px circular inverted arrow (streaming: circular outlined stop) — the\n * grammar sandbox-ui's legacy AgentComposer used and the current agent-app\n * canon for new surfaces. */\n sendVariant?: 'pill' | 'icon'\n /** Circular icon send colour. `contrast` (default) preserves the inverted\n * foreground/background canon; `primary` opts into the host brand tokens.\n * The labeled pill already uses primary and is unchanged. */\n sendTone?: 'contrast' | 'primary'\n className?: string\n}\n\nconst DEFAULT_MAX_HEIGHT = 168\n\n/** The input's own `leading-6` line box and its `py-1` padding, in pixels. The\n * `minRows` floor is computed from them so the CSS floor and the `rows`\n * attribute cannot drift apart at a row count other than the default. */\nconst LINE_HEIGHT = 24\nconst TEXTAREA_PADDING_Y = 8\n\nconst DEFAULT_SEND_FAILURE = \"Message not sent. Your draft is still here — try again.\"\n\n/** A rejection the handler reported by value rather than by throwing. */\nfunction isRejectedOutcome(outcome: ComposerSendOutcome): outcome is ComposerSendRejected {\n return typeof outcome === 'object' && outcome !== null && outcome.ok === false\n}\n\nfunction isPromise(value: ComposerSendResult): value is Promise<ComposerSendOutcome> {\n return typeof (value as Promise<ComposerSendOutcome> | undefined)?.then === 'function'\n}\n\n/** The reason to show. A rejection's own `error` string wins; then an Error's\n * message; else the caller's copy — never an empty notice. */\nfunction sendFailureText(error: unknown, fallback: string): string {\n if (typeof error === 'object' && error !== null && 'ok' in error) {\n const named = (error as ComposerSendRejected).error\n if (typeof named === 'string' && named.trim() !== '') return named\n return fallback\n }\n if (typeof error === 'string' && error.trim() !== '') return error\n if (error instanceof Error && error.message.trim() !== '') return error.message\n return fallback\n}\n\ninterface FailedSend {\n message: string\n /** The user's exact draft, untrimmed — what a restore puts back. */\n text: string\n /** The trimmed form the handler was called with, so Retry sends the same\n * bytes the rejected attempt did. */\n trimmed: string\n parts: ComposerFilePart[]\n restored: boolean\n}\n\nexport function ChatComposer({\n onSend,\n onSendParts,\n onSendFailed,\n sendFailureMessage = DEFAULT_SEND_FAILURE,\n onCancel,\n isStreaming = false,\n disabled = false,\n sendDisabled = false,\n placeholder = 'Message the agent…',\n value,\n onValueChange,\n initialValue,\n seed,\n onSeedApplied,\n controls,\n controlsPlacement = 'inline',\n onAttach,\n onAttachFolder,\n pendingFiles = [],\n onRemoveFile,\n onRetryFile,\n accept,\n onRejectFiles,\n dropTitle = 'Drop files to add context',\n dropDescription = 'They attach to your next message.',\n contextItems = [],\n canSubmitAttachmentsOnly = false,\n attachmentsNotReadyMessage,\n canSubmitWhileBusy = false,\n autoFocus,\n minRows = 2,\n maxHeight = DEFAULT_MAX_HEIGHT,\n trailing,\n mention,\n slashCommands,\n eligibleSkills = [],\n selectedSkillId,\n onSelectedSkillChange,\n onDictate,\n onDictateError,\n\n focusShortcut = true,\n onFocusWithin,\n floating = false,\n sendLabel = 'Send',\n sendVariant = 'pill',\n sendTone = 'contrast',\n className,\n}: ChatComposerProps) {\n const isControlled = value !== undefined\n const [internal, setInternal] = useState(initialValue ?? '')\n const text = isControlled ? value : internal\n const MentionEditor = useMemo(createLazyMentionEditor, [])\n // A send outcome arrives after the render that dispatched it, so the restore\n // decision must read the LIVE draft, not the one captured in that closure.\n const textRef = useRef(text)\n textRef.current = text\n\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n // Set by the mention editor so autofocus-independent focus paths (the\n // Cmd/Ctrl+L shortcut) reach it in the rich path, where `textareaRef` stays\n // null. The editor registers `null` on unmount, so the shortcut can never\n // call into a destroyed editor.\n const richFocusRef = useRef<(() => void) | null>(null)\n // Stable identity so the mention editor's registration effect only reruns\n // when the editor instance itself changes, not on every parent render.\n const registerRichFocus = useCallback((focus: (() => void) | null) => {\n richFocusRef.current = focus\n }, [])\n const fileInputRef = useRef<HTMLInputElement>(null)\n const folderInputRef = useRef<HTMLInputElement>(null)\n const [dragOver, setDragOver] = useState(false)\n const dragDepth = useRef(0)\n // Counts every clipboard image this composer has renamed, so two pastes of\n // the same bitmap do not both arrive as `image.png`.\n const pastedImageCount = useRef(0)\n\n const setText = useCallback(\n (next: string) => {\n if (!isControlled) setInternal(next)\n onValueChange?.(next)\n },\n [isControlled, onValueChange],\n )\n\n // Dictation: capture only. The hook reports every failure in words; the\n // composer shows them in its own dismissible notice (the same shape as a\n // rejected send) AND forwards them for hosts that log.\n const [dictateError, setDictateError] = useState<string | null>(null)\n const handleDictated = useCallback(\n (audio: DictationAudio) => {\n setDictateError(null)\n onDictate?.(audio)\n },\n [onDictate],\n )\n const handleDictateError = useCallback(\n (message: string) => {\n setDictateError(message)\n onDictateError?.(message)\n },\n [onDictateError],\n )\n const dictation = useDictation({ onDictate: handleDictated, onError: handleDictateError })\n\n // Keep the textarea height in sync with the content for BOTH typed and\n // external (controlled) value changes — one effect covers both paths. It also\n // reruns when the bounds move: `rows` is what `scrollHeight` resolves the\n // measurement against, so a changed `minRows` that did not re-measure would\n // strand the previous inline height.\n useEffect(() => {\n const el = textareaRef.current\n if (!el) return\n el.style.height = 'auto'\n el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n }, [text, maxHeight, minRows])\n\n // Adopt a one-shot seed. Applies only when the `seed` PROP transitions to a\n // new string (host sets it → consumed here → host clears it via\n // onSeedApplied), so an unstable callback identity re-running this effect\n // can never re-apply a still-set seed over the user's typing. Like\n // `initialValue`, the seed is honored ONLY in uncontrolled mode — a\n // controlled host drives its own `value` (which would shadow `setText`), so\n // it seeds by updating that state itself.\n const prevSeedRef = useRef<string | null>(null)\n const pendingCaretRef = useRef<string | null>(null)\n useEffect(() => {\n const prev = prevSeedRef.current\n prevSeedRef.current = seed ?? null\n if (seed == null || seed === prev || isControlled) return\n setText(seed)\n onSeedApplied?.()\n const el = textareaRef.current\n if (el && el.value === seed) {\n // The DOM already shows the seed — setText was a no-op (the user had\n // typed the exact string), so no re-render is coming and the [text]\n // effect below won't fire. Position the caret now instead of leaving a\n // stranded pendingCaretRef.\n el.focus()\n el.setSelectionRange(seed.length, seed.length)\n } else {\n // Defer caret positioning until the seeded value renders (see below).\n pendingCaretRef.current = seed\n }\n }, [seed, setText, onSeedApplied, isControlled])\n\n // Focus + caret-to-end AFTER the seeded value has rendered into the DOM —\n // setSelectionRange in the applying effect would run against the pre-render\n // value and clamp the caret to the old text's length.\n useEffect(() => {\n if (pendingCaretRef.current == null || pendingCaretRef.current !== text)\n return\n pendingCaretRef.current = null\n const el = textareaRef.current\n if (!el) return\n el.focus()\n el.setSelectionRange(text.length, text.length)\n }, [text])\n\n // A restored draft gets the caret back where the user left it — same\n // post-render rule as the seed above, but to the recorded offsets rather than\n // to the end, so a failed send returns the user to the word they were on.\n const restoreCaretRef = useRef<{ text: string; start: number; end: number } | null>(null)\n useEffect(() => {\n const pending = restoreCaretRef.current\n if (!pending || pending.text !== text) return\n restoreCaretRef.current = null\n const el = textareaRef.current\n if (!el) return\n el.focus()\n const start = Math.min(pending.start, text.length)\n const end = Math.min(pending.end, text.length)\n el.setSelectionRange(start, end)\n }, [text])\n\n // Cmd/Ctrl+L focuses the composer from anywhere — the shortcut the hint\n // advertises. Scoped to when the shortcut is enabled and not disabled.\n // Depends on mention PRESENCE, not identity: hosts build the prop object\n // inline per render, and only the mode switch changes which input to focus.\n const mentionEnabled = mention != null\n useEffect(() => {\n if (!focusShortcut || disabled) return\n function onKeyDown(e: globalThis.KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'l') {\n if (mentionEnabled) {\n const focus = richFocusRef.current\n e.preventDefault()\n if (focus) focus()\n else textareaRef.current?.focus()\n } else {\n e.preventDefault()\n textareaRef.current?.focus()\n }\n }\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n }, [focusShortcut, disabled, mentionEnabled])\n\n // A ready file counts as sendable content even without a `part`: store-backed\n // attachments (`useComposerAttachments`) carry no prompt part — their\n // references ride the turn body's `attachments` field — but a file-only\n // message must still be sendable. `canSubmitAttachmentsOnly` widens that to a\n // file of ANY status, so an in-flight upload leaves the control live and the\n // host's handler decides what to do about it.\n const sendableFiles = canSubmitAttachmentsOnly\n ? pendingFiles\n : pendingFiles.filter((f) => f.status === 'ready')\n const hasSendable = text.trim().length > 0 || sendableFiles.length > 0 || Boolean(selectedSkillId)\n // Streaming blocks a send unless the host queues turns. The button still\n // shows Stop while streaming, so `canSubmitWhileBusy` opens Enter, not a\n // second visible control.\n const sendBlockedByStream = isStreaming && !canSubmitWhileBusy\n const canSend = hasSendable && !sendBlockedByStream && !disabled && !sendDisabled\n\n const [failedSend, setFailedSend] = useState<FailedSend | null>(null)\n\n // The draft comes back only when the box is still empty. If the user typed a\n // replacement while the send was in flight, overwriting it would trade one\n // lost message for another — the unsent text is held in the notice instead,\n // where Retry can send it without touching what they typed.\n const failSend = useCallback(\n (error: unknown, draft: string, trimmed: string, parts: ComposerFilePart[], caret: { start: number; end: number }) => {\n const message = sendFailureText(error, sendFailureMessage)\n const restored = textRef.current === ''\n if (restored) {\n const el = textareaRef.current\n setText(draft)\n if (el && el.value === draft) {\n // A handler that rejected SYNCHRONOUSLY did so inside the same event\n // as the clear, so React collapses clear+restore into no state change\n // at all — no re-render is coming and the effect below will never\n // fire. Place the caret now rather than stranding the pending ref.\n el.focus()\n el.setSelectionRange(Math.min(caret.start, draft.length), Math.min(caret.end, draft.length))\n } else {\n restoreCaretRef.current = { text: draft, start: caret.start, end: caret.end }\n }\n }\n setFailedSend({ message, text: draft, trimmed, parts, restored })\n onSendFailed?.({ message, text: draft, parts, error, restored })\n },\n [onSendFailed, sendFailureMessage, setText],\n )\n\n // Hand the message to the host and watch the outcome. The input has already\n // been cleared by the caller — this only decides whether it comes back.\n const dispatchSend = useCallback(\n (draft: string, trimmed: string, parts: ComposerFilePart[], caret: { start: number; end: number }) => {\n let outcome: ComposerSendResult\n try {\n outcome = onSendParts ? onSendParts(trimmed, parts) : onSend?.(trimmed)\n } catch (error) {\n failSend(error, draft, trimmed, parts, caret)\n return\n }\n if (isPromise(outcome)) {\n void outcome.then(\n (settled) => {\n if (isRejectedOutcome(settled)) failSend(settled, draft, trimmed, parts, caret)\n },\n (error: unknown) => failSend(error, draft, trimmed, parts, caret),\n )\n return\n }\n if (isRejectedOutcome(outcome)) failSend(outcome, draft, trimmed, parts, caret)\n },\n [onSend, onSendParts, failSend],\n )\n\n const send = useCallback(() => {\n const trimmed = text.trim()\n if (sendBlockedByStream || disabled || sendDisabled) return\n const readyFiles = pendingFiles.filter((f) => f.status === 'ready')\n const sendable = canSubmitAttachmentsOnly ? pendingFiles : readyFiles\n if (!trimmed && sendable.length === 0 && !selectedSkillId) return\n // `canSubmitAttachmentsOnly` keeps the control live while a file is staged,\n // but a turn carrying no text and no file the host can deliver must not go\n // out: it would arrive empty and the attachment would be lost. Refuse it\n // here and say why, rather than dispatching and trusting every host to\n // re-derive the same check.\n if (!trimmed && readyFiles.length === 0 && pendingFiles.length > 0) {\n const message =\n attachmentsNotReadyMessage ??\n (pendingFiles.some((f) => f.status === 'error')\n ? 'Retry or remove the failed attachment before sending.'\n : 'Wait for the attachment to finish uploading.')\n setFailedSend({ message, text: '', trimmed: '', parts: [], restored: true })\n return\n }\n // Only a parts-aware send carries parts; `onSend`'s files travel through the\n // host's own `pendingFiles`, so its failure payload names none. Parts come\n // from READY files whatever `canSubmitAttachmentsOnly` says — an unfinished\n // upload has no part to send.\n const parts = onSendParts\n ? readyFiles.filter((f) => f.part).map((f) => f.part as ComposerFilePart)\n : []\n const el = textareaRef.current\n const caret = { start: el?.selectionStart ?? text.length, end: el?.selectionEnd ?? text.length }\n setFailedSend(null)\n setText('')\n textRef.current = ''\n dispatchSend(text, trimmed, parts, caret)\n }, [\n text,\n sendBlockedByStream,\n disabled,\n sendDisabled,\n canSubmitAttachmentsOnly,\n attachmentsNotReadyMessage,\n onSendParts,\n pendingFiles,\n selectedSkillId,\n setText,\n dispatchSend,\n ])\n\n // Re-send the message the notice is holding. Reached only when the draft was\n // NOT restored (the restored path leaves the text in the box, where Send is\n // the affordance), so it never competes with the primary control.\n const retryFailedSend = useCallback(() => {\n const failure = failedSend\n if (!failure || sendBlockedByStream || disabled || sendDisabled) return\n setFailedSend(null)\n const caret = { start: failure.text.length, end: failure.text.length }\n dispatchSend(failure.text, failure.trimmed, failure.parts, caret)\n }, [failedSend, sendBlockedByStream, disabled, sendDisabled, dispatchSend])\n\n // ── '/' commands ─────────────────────────────────────────────────────────\n // General commands exist only while the WHOLE draft is one leading slash\n // token (`/`, `/mod`); `/skill <query>` also searches eligible skills. Esc\n // or an outside click dismisses for the CURRENT token only, so continued\n // typing reopens the menu instead of leaving it permanently suppressed.\n const slashPanelRef = useRef<HTMLDivElement>(null)\n const cardRef = useRef<HTMLDivElement>(null)\n const slashListId = useId()\n const [mentionSuggestionOpen, setMentionSuggestionOpen] = useState(false)\n const [slashActive, setSlashActive] = useState(0)\n const [slashDismissedFor, setSlashDismissedFor] = useState<string | null>(null)\n // General commands retain their textarea-only contract. Eligible skills\n // deliberately share the menu in both input modes; the rich input forwards\n // its keyboard events below. `/skill <query>` filters only skills.\n const hasSkills = Boolean(onSelectedSkillChange && eligibleSkills.length > 0)\n const commandToken = !mention && slashCommands?.length ? /^\\/(\\S*)$/.exec(text)?.[1] : undefined\n const skillToken = hasSkills ? /^\\/(\\S*)$/.exec(text)?.[1] : undefined\n const skillInvocation = hasSkills ? /^\\/skill(?:\\s+(.*))?$/i.exec(text) : null\n const skillQuery = skillInvocation ? (skillInvocation[1] ?? '') : undefined\n const slashToken = skillQuery ?? skillToken ?? commandToken\n const slashOpen = slashToken !== undefined && text !== slashDismissedFor && !mentionSuggestionOpen\n const slashItems = useMemo<CommandPaletteItem[]>(\n () => [\n ...(!mention ? slashCommands ?? [] : []).map((command) => ({\n id: `command:${command.name}`,\n group: 'Commands',\n label: `/${command.name}`,\n description: command.description,\n keywords: [command.name, command.description],\n })),\n ...(onSelectedSkillChange ? eligibleSkills : []).map((skill) => ({\n id: `skill:${skill.id}`,\n group: 'Skills',\n label: `/skill ${skill.name}`,\n description: skill.description,\n keywords: [skill.id, skill.name, skill.description],\n })),\n ],\n [slashCommands, mention, eligibleSkills, onSelectedSkillChange],\n )\n const slashFiltered = useMemo(\n () => {\n if (slashToken === undefined) return []\n const items = skillQuery !== undefined ? slashItems.filter((item) => item.group === 'Skills') : slashItems\n return filterCommandPaletteItems(items, slashToken)\n },\n [slashItems, slashToken, skillQuery],\n )\n const slashActiveIndex = slashFiltered.length === 0 ? 0 : Math.min(slashActive, slashFiltered.length - 1)\n\n useEffect(() => {\n setSlashActive(0)\n }, [slashToken])\n\n useEffect(() => {\n if (!slashOpen) return\n document\n .getElementById(`${slashListId}-${slashActiveIndex}`)\n ?.scrollIntoView?.({ block: 'nearest' })\n }, [slashOpen, slashActiveIndex, slashListId])\n\n useEffect(() => {\n if (!slashOpen) return\n function onMouseDown(e: MouseEvent) {\n const target = e.target as Node\n if (cardRef.current?.contains(target)) return\n if (slashPanelRef.current?.contains(target)) return\n setSlashDismissedFor(textRef.current)\n }\n document.addEventListener('mousedown', onMouseDown)\n return () => document.removeEventListener('mousedown', onMouseDown)\n }, [slashOpen])\n\n const pickSlash = useCallback(\n (id: string) => {\n // A pick consumes the current slash invocation: clear the box, then\n // report a typed skill or run the host's general command.\n setText('')\n setSlashDismissedFor(null)\n if (id.startsWith('skill:')) {\n const skill = eligibleSkills.find((item) => item.id === id.slice('skill:'.length))\n if (skill) onSelectedSkillChange?.(skill)\n } else if (id.startsWith('command:')) {\n slashCommands?.find((command) => command.name === id.slice('command:'.length))?.run()\n }\n },\n [slashCommands, eligibleSkills, onSelectedSkillChange, setText],\n )\n\n const handleSlashKeyDown = (e: Pick<globalThis.KeyboardEvent, 'key' | 'shiftKey' | 'preventDefault'>): boolean => {\n if (slashOpen) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n if (slashFiltered.length > 0) setSlashActive((slashActiveIndex + 1) % slashFiltered.length)\n return true\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n if (slashFiltered.length > 0)\n setSlashActive((slashActiveIndex - 1 + slashFiltered.length) % slashFiltered.length)\n return true\n }\n if ((e.key === 'Enter' && !e.shiftKey) || e.key === 'Tab') {\n const item = slashFiltered[slashActiveIndex]\n if (item) {\n e.preventDefault()\n pickSlash(item.id)\n return true\n }\n // No command matched — fall through and let Enter send the raw text.\n }\n if (e.key === 'Escape') {\n e.preventDefault()\n setSlashDismissedFor(text)\n return true\n }\n }\n return false\n }\n\n const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {\n // Respect IME composition — Enter commits the candidate, it doesn't send.\n if (e.nativeEvent.isComposing) return\n if (handleSlashKeyDown(e)) return\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n send()\n }\n }\n\n // Every route a file can arrive by ends here: apply `accept`, report what it\n // removed, and hand `onAttach` only what passed. A batch the filter left\n // untouched is forwarded as the browser's own `FileList`; one it changed is\n // rebuilt, since `onAttach` takes a `FileList` and only a `DataTransfer` can\n // produce one.\n const deliverFiles = useCallback(\n (files: File[], original: FileList) => {\n if (!onAttach || files.length === 0) return\n const { accepted, rejected } = filterAcceptedFiles(files, accept)\n if (rejected.length > 0) onRejectFiles?.(rejected)\n if (accepted.length === 0) return\n const unchanged =\n accepted.length === original.length && accepted.every((file, i) => file === original[i])\n if (unchanged) {\n onAttach(original)\n return\n }\n const transfer = new DataTransfer()\n for (const file of accepted) transfer.items.add(file)\n onAttach(transfer.files)\n },\n [onAttach, onRejectFiles, accept],\n )\n\n const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {\n // Re-filter: a picker dialog lets the user override `accept` with\n // \"All Files\", so the attribute alone does not hold the gate.\n if (e.target.files?.length) deliverFiles(Array.from(e.target.files), e.target.files)\n e.target.value = ''\n }\n\n // One paste core for both input modes. Returns true when files were the\n // payload — the caller then suppresses the default text paste even when\n // every file is refused, so a rejection never half-pastes stray text.\n // The staged names go in alongside the count: the queue is the host's and\n // can outlive this mount, so the count alone could hand the next paste a\n // name the queue already holds.\n const ingestPastedFiles = (clipboardFiles: FileList): boolean => {\n if (!onAttach || clipboardFiles.length === 0) return false\n const { files, nextIndex } = renamePastedImages(\n Array.from(clipboardFiles),\n pastedImageCount.current,\n pendingFiles.map((f) => f.name),\n )\n pastedImageCount.current = nextIndex\n deliverFiles(files, clipboardFiles)\n return true\n }\n\n const handlePaste = (e: ClipboardEvent<HTMLTextAreaElement>) => {\n const clipboardFiles = e.clipboardData?.files\n if (!clipboardFiles || clipboardFiles.length === 0) return\n if (ingestPastedFiles(clipboardFiles)) e.preventDefault()\n }\n\n const handleFolderChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files?.length) (onAttachFolder ?? onAttach)?.(e.target.files)\n e.target.value = ''\n }\n\n const handleDragEnter = useCallback((e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current++\n if (e.dataTransfer?.types.includes('Files')) setDragOver(true)\n }, [])\n\n const handleDragLeave = useCallback((e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current--\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragOver(false)\n }\n }, [])\n\n const handleDragOver = useCallback((e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'\n }, [])\n\n const handleDrop = useCallback(\n (e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current = 0\n setDragOver(false)\n const files = e.dataTransfer?.files\n if (files?.length) deliverFiles(Array.from(files), files)\n },\n [deliverFiles],\n )\n\n const folderChips = pendingFiles.filter((f) => f.kind === 'folder')\n const fileChips = pendingFiles.filter((f) => f.kind !== 'folder')\n // `above` is the only placement that takes controls OUT of the card, so it is\n // the only one matched exactly; everything else falls to inline. That keeps a\n // retired value (this prop used to accept `footer` for the same placement) or a\n // typo rendering the controls somewhere rather than nowhere — dropping them\n // silently is the one outcome with no recovery for the reader.\n const showAbove = controls != null && controlsPlacement === 'above'\n const showInline = controls != null && !showAbove\n\n // One floor for both input modes, so the mention editor and the textarea\n // cannot disagree about the empty-composer height.\n const inputMinHeight = minRows * LINE_HEIGHT + TEXTAREA_PADDING_Y\n // Keep one basic input for textarea mode and the rich editor's loading\n // state. A slow or hung lazy chunk must not leave a visible dead composer.\n // The controlled draft transfers into the rich editor when it loads.\n // Both render sites stay inside the card's `focusFieldWithin` indicator.\n const textareaInput = (\n <textarea\n ref={textareaRef}\n value={text}\n onChange={(e) => setText(e.target.value)}\n onKeyDown={handleKeyDown}\n onPaste={onAttach ? handlePaste : undefined}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n rows={minRows}\n style={{ minHeight: inputMinHeight, maxHeight }}\n aria-label=\"Message input\"\n data-composer-input\n className=\"w-full resize-none bg-transparent px-1.5 py-1 text-base leading-6 text-foreground placeholder:text-muted-foreground disabled:opacity-50\"\n />\n )\n\n return (\n <div\n className={`relative ${className ?? ''}`}\n onFocus={onFocusWithin}\n onDragEnter={onAttach ? handleDragEnter : undefined}\n onDragLeave={onAttach ? handleDragLeave : undefined}\n onDragOver={onAttach ? handleDragOver : undefined}\n onDrop={onAttach ? handleDrop : undefined}\n >\n {dragOver && (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-card\">\n <div className=\"text-center\">\n <span className=\"mx-auto mb-2 flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary\">\n <UploadGlyph className=\"h-5 w-5\" />\n </span>\n <p className=\"text-sm font-semibold text-foreground\">{dropTitle}</p>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">{dropDescription}</p>\n </div>\n </div>\n )}\n\n {showAbove && <div className=\"mb-1.5 flex flex-wrap items-center gap-1.5 px-1\">{controls}</div>}\n\n {dictateError && (\n <div\n role=\"alert\"\n data-testid=\"composer-dictate-error\"\n className=\"mb-2 flex items-start gap-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\"\n >\n <span className=\"min-w-0 flex-1\">{dictateError}</span>\n <button\n type=\"button\"\n aria-label=\"Dismiss dictation error\"\n onClick={() => setDictateError(null)}\n className=\"shrink-0 font-medium underline-offset-2 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n Dismiss\n </button>\n </div>\n )}\n\n {failedSend && (\n <div\n role=\"alert\"\n data-testid=\"composer-send-error\"\n className=\"mb-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\"\n >\n <div className=\"flex items-start gap-2\">\n <span className=\"min-w-0 flex-1\">{failedSend.message}</span>\n <button\n type=\"button\"\n aria-label=\"Dismiss send error\"\n onClick={() => setFailedSend(null)}\n className=\"shrink-0 font-medium underline-offset-2 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n Dismiss\n </button>\n </div>\n {/* The draft is only held here when it could NOT go back in the box —\n the user typed a replacement. Showing the bytes is what makes the\n message recoverable by hand even if Retry keeps failing. */}\n {!failedSend.restored && (\n <div className=\"mt-1.5\">\n <p\n data-testid=\"composer-unsent-draft\"\n className=\"max-h-20 overflow-y-auto whitespace-pre-wrap rounded-lg border border-destructive/30 bg-card px-2 py-1 text-foreground\"\n >\n {failedSend.text}\n </p>\n <button\n type=\"button\"\n aria-label=\"Retry sending the unsent message\"\n onClick={retryFailedSend}\n disabled={sendBlockedByStream || disabled}\n className=\"mt-1.5 font-medium underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n Retry\n </button>\n </div>\n )}\n </div>\n )}\n\n {selectedSkillId && (\n <div aria-label=\"Selected skill\" className=\"mb-2 flex min-w-0 flex-wrap gap-1.5\">\n <span className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs text-primary\">\n <span className=\"min-w-0 truncate\">Skill: {eligibleSkills.find((skill) => skill.id === selectedSkillId)?.name ?? selectedSkillId}</span>\n {onSelectedSkillChange && (\n <button\n type=\"button\"\n aria-label=\"Remove selected skill\"\n onClick={() => onSelectedSkillChange(null)}\n className=\"shrink-0 rounded p-0.5 text-primary/70 transition hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <CloseGlyph className=\"h-3 w-3\" />\n </button>\n )}\n </span>\n </div>\n )}\n\n {contextItems.length > 0 && (\n <div aria-label=\"Message context\" className=\"mb-2 flex min-w-0 flex-wrap gap-1.5\">\n {contextItems.map((item) => (\n <span\n key={item.id}\n className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs text-primary\"\n >\n {item.icon && (\n <span className=\"shrink-0\" aria-hidden>\n {item.icon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{item.label}</span>\n {item.onRemove && (\n <button\n type=\"button\"\n aria-label={`Remove context ${item.label}`}\n onClick={item.onRemove}\n className=\"shrink-0 rounded p-0.5 text-primary/70 transition hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <CloseGlyph className=\"h-3 w-3\" />\n </button>\n )}\n </span>\n ))}\n </div>\n )}\n\n {pendingFiles.length > 0 && (\n <div className=\"mb-2 flex flex-wrap gap-1.5\">\n {[...folderChips, ...fileChips].map((f) => {\n const isError = f.status === 'error'\n return (\n <span\n key={f.id}\n title={isError ? f.errorMessage : undefined}\n className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs ${\n isError\n ? 'border-destructive/40 text-destructive'\n : 'border-border bg-secondary text-foreground'\n } ${f.status === 'pending' ? 'opacity-60' : ''}`}\n >\n {/* A thumbnail identifies a pasted screenshot that the\n auto-generated name cannot. Folders never have one. */}\n {f.kind !== 'folder' && f.previewUrl ? (\n <img src={f.previewUrl} alt=\"\" className=\"h-8 w-8 shrink-0 rounded object-cover\" />\n ) : f.kind === 'folder' ? (\n <FolderGlyph className=\"h-3 w-3 shrink-0\" />\n ) : (\n <PaperclipGlyph className=\"h-3 w-3 shrink-0\" />\n )}\n <span className=\"max-w-[150px] truncate\">{f.name}</span>\n {f.fileCount !== undefined && <span className=\"text-muted-foreground\">({f.fileCount})</span>}\n {f.status === 'uploading' && (\n <span className=\"h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent\" />\n )}\n {isError && f.errorMessage && (\n <span className=\"max-w-[150px] truncate text-destructive/80\">{f.errorMessage}</span>\n )}\n {isError && onRetryFile && (\n <button\n type=\"button\"\n aria-label={`Retry upload ${f.name}`}\n onClick={() => onRetryFile(f.id)}\n className=\"rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <RetryGlyph className=\"h-3 w-3\" />\n </button>\n )}\n {onRemoveFile && (\n <button\n type=\"button\"\n aria-label={`Remove ${f.name}`}\n onClick={() => onRemoveFile(f.id)}\n className=\"rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <CloseGlyph className=\"h-3 w-3\" />\n </button>\n )}\n </span>\n )\n })}\n </div>\n )}\n\n {/* Two rows inside one card: the message gets the full width, and every\n affordance that acts on it — attach, the controls slot, send — sits on\n its own row beneath. A single row would make the textarea share its\n line with the buttons, which is what squeezed the input and pushed the\n controls out of the card in the first place. */}\n {/* `focusFieldWithin` makes the card a field: with brand's interaction\n tokens it carries the well fill (`--well`), field border, hover glow\n and focus ring. Under a brand without the ladder, tokens.css sets\n `--well` to the card color. */}\n <div\n ref={cardRef}\n data-testid=\"composer-card\"\n className={`flex flex-col gap-1.5 rounded-2xl border bg-[var(--well,var(--color-card))] px-3 py-2.5 ${focusFieldWithin} ${\n floating ? 'shadow-raised' : ''\n }`}\n >\n {mention ? (\n // The basic textarea stays usable through download, initialization,\n // and failure.\n <LazyLoadBoundary renderFailure={() => (\n <>\n {textareaInput}\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive\"\n >\n Mentions are unavailable. You can still send.\n </div>\n </>\n )}>\n <Suspense fallback={textareaInput}>\n <MentionEditor\n value={text}\n onChange={setText}\n onSubmit={send}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n minHeight={inputMinHeight}\n maxHeight={maxHeight}\n mention={mention}\n fallback={textareaInput}\n registerFocus={registerRichFocus}\n onComposerKeyDown={handleSlashKeyDown}\n onMentionMenuOpenChange={setMentionSuggestionOpen}\n slashListId={slashOpen ? slashListId : undefined}\n slashActiveId={slashFiltered[slashActiveIndex] ? `${slashListId}-${slashActiveIndex}` : undefined}\n onPasteFiles={onAttach ? ingestPastedFiles : undefined}\n />\n </Suspense>\n </LazyLoadBoundary>\n ) : (\n // The card is the field (`focusFieldWithin`: well fill, field border,\n // hover glow, focus ring), so tokens.css clears the transparent\n // input's own outline (`[data-composer-input]:focus-visible`).\n textareaInput\n )}\n\n {/* The row wraps rather than overlaps. The controls slot never shrinks\n below its widest control (`min-w-min`), so when the controls and the\n actions group cannot share one line the actions group moves to its\n own line, right-aligned, instead of being drawn over a squeezed\n control. With `min-w-0` here a single Plan chip was compressed to\n nothing at 390px and painted underneath a trailing model picker. */}\n <div className=\"flex flex-wrap items-end gap-2\">\n {onAttach && (\n <>\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={disabled}\n aria-label=\"Attach files\"\n title=\"Attach files\"\n className=\"shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <PaperclipGlyph className=\"h-4 w-4\" />\n </button>\n <input ref={fileInputRef} type=\"file\" multiple className=\"hidden\" accept={accept} onChange={handleFileChange} />\n </>\n )}\n {onAttachFolder && (\n <>\n <button\n type=\"button\"\n onClick={() => folderInputRef.current?.click()}\n disabled={disabled}\n aria-label=\"Attach folder\"\n title=\"Attach folder\"\n className=\"shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <FolderGlyph className=\"h-4 w-4\" />\n </button>\n {/* webkitdirectory is non-standard but widely supported for folder picks. */}\n <input\n ref={folderInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={handleFolderChange}\n {...({ webkitdirectory: '' } as Record<string, string>)}\n />\n </>\n )}\n\n {/* The controls take the row's slack and wrap onto a second line when\n a long picker set outgrows it. This slot must never establish an\n overflow box: a control owns its popover (ModelPicker, EffortPicker)\n and anchors it absolutely to itself, so a scroll/clip box here traps\n a 400px-tall list inside a ~34px row — the list renders and is never\n visible — and the scroll offset that comes with it cuts the trigger's\n own left edge. Growing a second line is the cost of controls that\n stay operable. Rendered even when empty so Send stays right-aligned. */}\n <div\n data-testid=\"composer-controls\"\n className=\"flex min-w-min flex-1 flex-wrap items-center gap-1.5\"\n >\n {showInline && controls}\n </div>\n\n {/* Trailing content is the controls slot's SIBLING, not its content:\n the slot is where a picker set is allowed to wrap, and a meter or\n a status line put inside it would be pushed onto the second line\n by the very pickers it reports on. Trailing content, dictation\n and Send travel together as one group: when the row wraps they\n move to the next line right-aligned, so Send never lands alone at\n the start of a line. The group wraps inside itself too: when a\n trailing picker and Send cannot share even their own line (a\n 320px phone), Send drops below the picker, right-aligned, instead\n of being drawn over it. */}\n <div data-testid=\"composer-actions\" className=\"ml-auto flex min-w-0 max-w-full flex-wrap items-end justify-end gap-2\">\n {trailing && (\n <div data-testid=\"composer-trailing\" className=\"flex min-w-0 flex-wrap items-center justify-end gap-1.5\">\n {trailing}\n </div>\n )}\n {/* Dictation sits beside Send: it produces input, like typing. The\n button renders only when the host takes audio AND the browser can\n record — a dead mic is worse than no mic. While recording, the\n elapsed seconds (not the pulsing dot, which reduced motion\n collapses) are the signal, and the stop control is never\n disabled: a `disabled` flip mid-capture must not strand the mic. */}\n {onDictate && dictation.supported ? (\n dictation.recording ? (\n <div className=\"flex shrink-0 items-center gap-1.5\">\n <span aria-hidden=\"true\" className=\"h-2 w-2 animate-pulse rounded-full bg-destructive\" />\n <span\n aria-hidden=\"true\"\n data-testid=\"composer-dictate-elapsed\"\n className=\"text-xs tabular-nums text-muted-foreground\"\n >\n {formatDictationElapsed(dictation.elapsedSeconds)}\n </span>\n <span role=\"status\" className=\"sr-only\">\n Recording\n </span>\n <button\n type=\"button\"\n onClick={dictation.stop}\n aria-label=\"Stop dictation\"\n title=\"Stop dictation\"\n className=\"shrink-0 rounded-lg p-2 text-destructive transition hover:bg-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <StopGlyph className=\"h-4 w-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={dictation.start}\n disabled={disabled}\n aria-label=\"Dictate message\"\n title=\"Dictate message\"\n className=\"shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MicGlyph className=\"h-4 w-4\" />\n </button>\n )\n ) : null}\n\n {isStreaming ? (\n sendVariant === 'icon' ? (\n <button\n type=\"button\"\n onClick={onCancel}\n aria-label=\"Stop response\"\n title=\"Stop\"\n className=\"inline-flex size-[var(--control-height-sm,2rem)] shrink-0 items-center justify-center rounded-full border border-border bg-transparent text-foreground transition hover:bg-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <StopGlyph className=\"h-3 w-3\" />\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={onCancel}\n aria-label=\"Stop response\"\n className=\"inline-flex h-[var(--control-height-sm,2rem)] shrink-0 items-center gap-1.5 rounded-full bg-destructive/15 px-3.5 text-sm font-medium text-destructive transition hover:bg-destructive/25 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n <StopGlyph className=\"h-3.5 w-3.5\" />\n <span>Stop</span>\n </button>\n )\n ) : sendVariant === 'icon' ? (\n <button\n type=\"button\"\n onClick={send}\n disabled={!canSend}\n aria-label={sendLabel}\n title={sendLabel}\n className={`inline-flex size-[var(--control-height-sm,2rem)] shrink-0 items-center justify-center rounded-full ${sendTone === 'primary' ? 'bg-primary text-primary-foreground' : 'bg-foreground text-background'} transition ${sendTone === 'primary' ? 'hover:bg-primary/90' : 'hover:opacity-90'} disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card`}\n >\n <ArrowUpGlyph className=\"h-4 w-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={send}\n disabled={!canSend}\n aria-label={sendLabel}\n className=\"inline-flex h-[var(--control-height-sm,2rem)] shrink-0 items-center gap-1.5 rounded-full bg-primary px-3.5 text-sm font-medium text-primary-foreground shadow-sm transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card\"\n >\n <SendGlyph className=\"h-3.5 w-3.5\" />\n <span>{sendLabel}</span>\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* The slash menu ports through PopoverSurface like every canonical\n popover: the composer docks inside horizontally scrolling rails, and\n an in-place panel there is a panel the host clips away. It anchors\n to the current input card and opens above. Focus never leaves the input —\n rows are mousedown-swallowed so a click can't blur it. */}\n <PopoverSurface\n open={slashOpen}\n id={slashListId}\n role=\"listbox\"\n triggerRef={mention ? cardRef : textareaRef}\n panelRef={slashPanelRef}\n className={`w-80 overflow-y-auto rounded-xl border border-card-edge bg-popover p-1 ${OVERLAY_SHADOW}`}\n >\n <div className=\"max-h-80 overflow-y-auto overscroll-contain\">\n {slashFiltered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">\n {skillQuery !== undefined ? 'No matching skills' : hasSkills ? 'No matching commands or skills' : 'No matching commands'}\n </div>\n )}\n {slashFiltered.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === slashActiveIndex}\n id={`${slashListId}-${index}`}\n onMouseDown={(e) => e.preventDefault()}\n onMouseMove={() => setSlashActive(index)}\n onClick={() => pickSlash(item.id)}\n className={`flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${\n index === slashActiveIndex ? 'bg-accent' : 'hover:bg-accent'\n }`}\n >\n <span className=\"shrink-0 font-medium text-foreground\">{item.label}</span>\n <span className=\"truncate text-xs text-muted-foreground\">{item.description}</span>\n </button>\n ))}\n </div>\n </PopoverSurface>\n\n {focusShortcut && (\n // A touch screen has no keyboard to press the shortcut on.\n <div className=\"mt-1.5 flex justify-end px-1 [@media(pointer:coarse)]:hidden\">\n <span className=\"text-xs text-muted-foreground\">\n <kbd className=\"rounded border border-border bg-background px-1 py-0.5 text-xs\">{IS_APPLE_PLATFORM ? 'Cmd' : 'Ctrl'}</kbd>\n <kbd className=\"ml-0.5 rounded border border-border bg-background px-1 py-0.5 text-xs\">L</kbd>\n <span className=\"ml-1\">to focus</span>\n </span>\n </div>\n )}\n </div>\n )\n}\n","/**\n * `useComposerAttachments` — the composer's staged-upload lifecycle: validate\n * selected/dropped/pasted files against the shared limits (the SAME\n * `sniffBinary`/`checkAttachmentType`/size-cap vocabulary the store-backed\n * upload route enforces server-side, `../chat-routes/attachment-validation`\n * + `../chat-routes/binary-sniff`), upload each accepted file with one POST\n * request per file (so a single failure never poisons the batch), and track\n * every file's status so a host composer can render chips and gate sending.\n *\n * Ported from gtm-agent's `src/components/composer-attachments.tsx`\n * (gtm#584/#592/#593 hardened the sniff gate and batch semantics this leans\n * on), de-gtm-ified:\n * - the hardcoded `/api/vault/upload?workspaceId=` URL becomes\n * `uploadUrl`/`buildUploadRequest` (the latter wins — it hands back both\n * the URL and a `RequestInit` override, e.g. an auth header);\n * - `sonner` toasts become `onReject` (client pre-validation, never hits the\n * network) and `onError` (a request that reached the server and failed);\n * - the `accept`-string gate comes from `./composer-file-accept`, the one\n * matcher `ChatComposer` also funnels its picker/drop/paste ingress\n * through, so both ends of the staging path admit the same files;\n * - the response is expected to be `{ files: ChatAttachmentInput[] }` (full\n * server-authoritative descriptors — size/mediaType/kind — not gtm's\n * `{path, name}`), so `references` is a verbatim pass-through with no\n * client recompute;\n * - `workspaceId`'s truthiness gate becomes `enabled` (default `true`).\n *\n * Import-free beyond React + the browser-safe `/chat-routes` validation core:\n * this module ships through `/web-react` into client bundles\n * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here\n * may reach a Node builtin, `sandbox-ui`, or an engine package.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './chat-stream'\nimport type { ComposerFile } from './chat-composer'\nimport { acceptRejectionReason, isAcceptedFileType } from './composer-file-accept'\nimport {\n ATTACHMENT_ACCEPT,\n ATTACHMENT_MAX_COUNT,\n MAX_ATTACHMENT_TOTAL_BYTES,\n MAX_BINARY_ATTACHMENT_BYTES,\n MAX_TEXT_ATTACHMENT_BYTES,\n attachmentSizeErrorMessage,\n attachmentTotalSizeErrorMessage,\n checkAttachmentType,\n sanitizeAttachmentFileName,\n} from '../chat-routes/attachment-validation'\nimport { sniffBinary } from '../chat-routes/binary-sniff'\n\nexport { ATTACHMENT_ACCEPT } from '../chat-routes/attachment-validation'\n\n/** One staged file and its upload lifecycle. `file` is retained so a failed\n * upload can be retried without re-selecting; `previewUrl` is an object URL\n * for image thumbnails and must be revoked when the entry leaves the queue.\n * `reference` is the server's authoritative descriptor once the upload\n * lands — stored verbatim, never recomputed client-side. */\ninterface StagedAttachment {\n id: string\n file: File\n name: string\n size: number\n status: 'pending' | 'uploading' | 'ready' | 'error'\n reference?: ChatAttachmentInput\n previewUrl?: string\n errorMessage?: string\n}\n\n/** Define options for configuring file upload behavior and handling in a composer component */\nexport interface UseComposerAttachmentsOptions {\n /** Simple upload target: every file POSTs here. Ignored when\n * `buildUploadRequest` is provided. */\n uploadUrl?: string\n /** Full request-building seam (auth headers, per-file routing, …) — wins\n * over `uploadUrl` when both are set. */\n buildUploadRequest?: (args: { file: File; name: string; form: FormData }) => {\n url: string\n init?: Omit<RequestInit, 'body' | 'signal'>\n }\n /** Client pre-validation rejections — a file that never reaches the\n * network (bad type, over a size cap, over count, disallowed kind). */\n onReject?: (reason: string, file?: File) => void\n /** A file that reached the upload endpoint and failed (HTTP error,\n * transport error, malformed response). */\n onError?: (reason: string) => void\n limits?: {\n maxCount?: number\n maxBinaryBytes?: number\n maxTextBytes?: number\n maxTotalBytes?: number\n }\n /** Attachment kinds accepted, checked against the sniffed content's\n * mime. Default: both (`['image', 'file']` — i.e. no restriction). */\n allowedKinds?: ChatAttachmentKind[]\n /** `<input accept>`-style gate for the file picker/drop/paste path.\n * Default {@link ATTACHMENT_ACCEPT}. */\n accept?: string\n /** When `false`, `addFiles` rejects every call via `onReject` (and\n * `blockReason` explains why) instead of staging anything — the\n * replacement for gtm's `workspaceId`-truthiness gate (e.g. no workspace\n * loaded yet). Default `true`. */\n enabled?: boolean\n}\n\n/** Provide staged file chips, ready attachments, and methods to add, retry, or drop composer files */\nexport interface UseComposerAttachmentsResult {\n /** Chip models for `ChatComposer`'s `pendingFiles` prop, one per staged\n * file — `kind` is always `'file'` (agent-app's `ComposerFile.kind`\n * discriminates file-vs-folder chips, not attachment media type). */\n composerFiles: ComposerFile[]\n /** Ready-to-send attachment descriptors — only files whose upload\n * succeeded, straight from the server's response (no recompute). Feed\n * this into `ChatTurnRequestPayload.attachments`. */\n references: ChatAttachmentInput[]\n /** Validate + stage + upload the given files, one request per file. */\n addFiles: (files: File[] | FileList) => Promise<void>\n /** Re-upload a failed entry using its retained `File`. */\n retry: (id: string) => void\n /** Drop one staged entry, aborting its upload and revoking its preview. */\n removeAttachment: (id: string) => void\n /** Forget every staged entry (call after a successful send). */\n clear: () => void\n /** True while any file is still pending or uploading. */\n hasPending: boolean\n /** True while any file failed to upload. */\n hasError: boolean\n /** Why a send is blocked, or `null` when the queue is clean. */\n blockReason: string | null\n}\n\nfunction newId(): string {\n const cryptoObject = globalThis.crypto\n if (typeof cryptoObject?.randomUUID === 'function') return cryptoObject.randomUUID()\n return `att-${Date.now()}-${Math.random().toString(36).slice(2)}`\n}\n\n/** Suffix a name (`report.pdf` → `report-2.pdf`) until it's unused. The\n * server writes to a name-derived store path, so identical names would\n * overwrite. The suffix stays inside the store-path charset (see\n * `sanitizeAttachmentFileName`). Ported byte-for-byte from gtm's\n * `dedupeName`. */\nfunction dedupeName(name: string, taken: Set<string>): string {\n if (!taken.has(name)) return name\n const dot = name.lastIndexOf('.')\n const base = dot > 0 ? name.slice(0, dot) : name\n const ext = dot > 0 ? name.slice(dot) : ''\n let n = 2\n let candidate = `${base}-${n}${ext}`\n while (taken.has(candidate)) {\n n += 1\n candidate = `${base}-${n}${ext}`\n }\n return candidate\n}\n\n/** `image/*` → `'image'`, everything else → `'file'`. Deliberately\n * reimplemented here (not imported from `../chat-store/parts`, which pulls\n * the drizzle-adjacent `/chat-store` barrel): this module must stay reachable\n * from a browser bundle with only the `/chat-routes` validation core as a\n * dependency. */\nfunction kindForMime(mime: string): ChatAttachmentKind {\n return mime.startsWith('image/') ? 'image' : 'file'\n}\n\n/** Pull a human-readable message out of the upload endpoint's error body.\n * Ported from gtm's `parseUploadError`: handles both `{ error: string }`\n * (size/count/access errors) and `{ error: { message } }` (the\n * `createAttachmentUploadRoute` envelope, `{error:{code,message,path?}}`). */\nasync function parseUploadError(res: Response): Promise<string> {\n const detail = await res.json().catch(() => null)\n if (detail && typeof detail === 'object' && 'error' in detail) {\n const error = (detail as { error: unknown }).error\n if (typeof error === 'string' && error) return error\n if (error && typeof error === 'object' && 'message' in error) {\n const message = (error as { message: unknown }).message\n if (typeof message === 'string' && message) return message\n }\n }\n return `Upload failed (${res.status})`\n}\n\n/** Shown when neither `uploadUrl` nor `buildUploadRequest` is configured\n * while `enabled` — a product wiring bug, not a user-facing rejection, so it\n * lands each affected entry in `error` (with `onError`) rather than blocking\n * `addFiles` outright via `onReject`: the files still stage and can be\n * retried once the product fixes its config, instead of silently vanishing. */\nconst NO_UPLOAD_TARGET_MESSAGE = 'No upload destination configured (pass uploadUrl or buildUploadRequest)'\n\n/**\n * Owns the composer's attachment lifecycle: validate selected/dropped/pasted\n * files against the shared limits, upload each accepted file to the\n * product's store (one request per file), and track every file's status so\n * the composer can render chips and gate sending.\n *\n * Failures surface loud — a rejected file calls `onReject` and is never\n * uploaded; a failed upload calls `onError` and leaves an error chip the user\n * can retry or remove. `references` only ever contains files whose upload the\n * server actually confirmed.\n */\nexport function useComposerAttachments(\n options: UseComposerAttachmentsOptions,\n): UseComposerAttachmentsResult {\n // Latest options, read from inside stable callbacks — avoids re-creating\n // `addFiles`/`upload` (and therefore breaking referential stability for\n // effects a host might hang off them) every time a caller passes a fresh\n // options object literal.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const [staged, setStagedState] = useState<StagedAttachment[]>([])\n // Mirror of `staged` kept in lockstep so dedupe/aggregate-cap/abort read\n // current values synchronously (setState callbacks alone can't answer\n // \"what's staged right now\" mid-validation).\n const stagedRef = useRef<StagedAttachment[]>([])\n const controllersRef = useRef<Map<string, AbortController>>(new Map())\n\n // Post-unmount calls reduce to a React no-op setState; the refs they touch\n // die with the instance.\n const setStaged = useCallback(\n (updater: StagedAttachment[] | ((prev: StagedAttachment[]) => StagedAttachment[])) => {\n const next =\n typeof updater === 'function'\n ? (updater as (prev: StagedAttachment[]) => StagedAttachment[])(stagedRef.current)\n : updater\n stagedRef.current = next\n setStagedState(next)\n },\n [],\n )\n\n const upload = useCallback(\n async (id: string, file: File, name: string) => {\n const opts = optionsRef.current\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'uploading', errorMessage: undefined } : s)),\n )\n const controller = new AbortController()\n controllersRef.current.set(id, controller)\n const form = new FormData()\n form.append('file', file, name)\n\n const request = opts.buildUploadRequest\n ? opts.buildUploadRequest({ file, name, form })\n : opts.uploadUrl\n ? { url: opts.uploadUrl }\n : null\n\n if (!request) {\n setStaged((prev) =>\n prev.map((s) =>\n s.id === id ? { ...s, status: 'error', errorMessage: NO_UPLOAD_TARGET_MESSAGE } : s,\n ),\n )\n opts.onError?.(NO_UPLOAD_TARGET_MESSAGE)\n controllersRef.current.delete(id)\n return\n }\n\n try {\n const res = await fetch(request.url, {\n method: 'POST',\n credentials: 'same-origin',\n ...request.init,\n body: form,\n signal: controller.signal,\n })\n if (!res.ok) {\n const message = await parseUploadError(res)\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n const data = (await res.json()) as { files?: ChatAttachmentInput[] }\n const uploaded = data.files?.[0]\n if (!uploaded) {\n const message = 'Upload returned no file'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'ready', reference: uploaded } : s)),\n )\n } catch (err) {\n if ((err as Error).name === 'AbortError') return // silent removal — see removeAttachment/clear\n const message =\n err instanceof Error && err.message ? err.message : 'Upload failed — check your connection'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n } finally {\n controllersRef.current.delete(id)\n }\n },\n [setStaged],\n )\n\n const addFiles = useCallback(\n async (files: File[] | FileList) => {\n const opts = optionsRef.current\n const enabled = opts.enabled ?? true\n if (!enabled) {\n opts.onReject?.('Attachments are disabled')\n return\n }\n\n const accept = opts.accept ?? ATTACHMENT_ACCEPT\n const maxCount = opts.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = opts.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\n const maxTextBytes = opts.limits?.maxTextBytes ?? MAX_TEXT_ATTACHMENT_BYTES\n const maxTotalBytes = opts.limits?.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const allowedKinds = opts.allowedKinds ?? (['image', 'file'] as ChatAttachmentKind[])\n\n const list = Array.isArray(files) ? files : Array.from(files)\n\n // Pass 1: accept-list, then count cap — accept first, and the count\n // checked against what is already staged plus what this batch has taken.\n const currentCount = stagedRef.current.length\n const countAccepted: File[] = []\n for (const file of list) {\n if (!isAcceptedFileType(file, accept)) {\n opts.onReject?.(acceptRejectionReason(file, accept), file)\n continue\n }\n if (currentCount + countAccepted.length >= maxCount) {\n opts.onReject?.(`\"${file.name}\" was not added — the ${maxCount}-file limit is already reached.`, file)\n continue\n }\n countAccepted.push(file)\n }\n\n // Pass 2: real content sniff + type gate + per-kind size cap +\n // allowed-kinds gate — the SAME checks the server enforces, so a\n // rejection never differs depending on which side classified the bytes\n // first. Nothing here ever reaches the network.\n const sizeAccepted: File[] = []\n for (const file of countAccepted) {\n const bytes = new Uint8Array(await file.arrayBuffer())\n const sniff = sniffBinary(bytes)\n const typeCheck = checkAttachmentType(file.name, sniff)\n if (!typeCheck.succeeded) {\n opts.onReject?.(typeCheck.message, file)\n continue\n }\n const limit = sniff.binary ? maxBinaryBytes : maxTextBytes\n if (file.size > limit) {\n opts.onReject?.(attachmentSizeErrorMessage(file.name, file.size, limit), file)\n continue\n }\n const mediaType = sniff.mime ?? file.type ?? ''\n const kind = kindForMime(mediaType)\n if (!allowedKinds.includes(kind)) {\n opts.onReject?.(`\"${file.name}\" is a ${kind} attachment, which isn't accepted here`, file)\n continue\n }\n sizeAccepted.push(file)\n }\n\n // Pass 3: running aggregate cap across this batch + everything already\n // staged (any status) — a partial batch can still land.\n const accepted: File[] = []\n let totalBytes = stagedRef.current.reduce((total, s) => total + s.size, 0)\n for (const file of sizeAccepted) {\n const nextTotalBytes = totalBytes + file.size\n if (nextTotalBytes > maxTotalBytes) {\n opts.onReject?.(attachmentTotalSizeErrorMessage(nextTotalBytes, maxTotalBytes), file)\n continue\n }\n accepted.push(file)\n totalBytes = nextTotalBytes\n }\n if (accepted.length === 0) return\n\n // Stage under the name the server will actually store, so the chip and\n // the message's attachment references never diverge.\n const taken = new Set(stagedRef.current.map((s) => s.name))\n const entries: StagedAttachment[] = accepted.map((file) => {\n const name = dedupeName(sanitizeAttachmentFileName(file.name), taken)\n taken.add(name)\n return {\n id: newId(),\n file,\n name,\n size: file.size,\n status: 'pending',\n previewUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n })\n setStaged((prev) => [...prev, ...entries])\n for (const entry of entries) void upload(entry.id, entry.file, entry.name)\n },\n [setStaged, upload],\n )\n\n const retry = useCallback(\n (id: string) => {\n const entry = stagedRef.current.find((s) => s.id === id)\n if (!entry) return\n void upload(entry.id, entry.file, entry.name)\n },\n [upload],\n )\n\n const removeAttachment = useCallback(\n (id: string) => {\n controllersRef.current.get(id)?.abort()\n controllersRef.current.delete(id)\n const entry = stagedRef.current.find((s) => s.id === id)\n if (entry?.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n setStaged((prev) => prev.filter((s) => s.id !== id))\n },\n [setStaged],\n )\n\n const clear = useCallback(() => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n setStaged([])\n }, [setStaged])\n\n useEffect(\n () => () => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n },\n [],\n )\n\n // `previewUrl` and `errorMessage` travel with the chip model. This hook is\n // the only place that knows either one — it mints the object URL and holds\n // the upload's failure text — so a projection that dropped them left the\n // composer rendering a red chip with no reason on it and no thumbnail for a\n // staged image.\n const composerFiles = useMemo<ComposerFile[]>(\n () =>\n staged.map((s) => ({\n id: s.id,\n name: s.name,\n size: s.size,\n kind: 'file' as const,\n status: s.status,\n previewUrl: s.previewUrl,\n errorMessage: s.errorMessage,\n })),\n [staged],\n )\n\n const references = useMemo<ChatAttachmentInput[]>(\n () =>\n staged\n .filter((s): s is StagedAttachment & { reference: ChatAttachmentInput } => s.status === 'ready' && !!s.reference)\n .map((s) => s.reference),\n [staged],\n )\n\n const hasPending = useMemo(\n () => staged.some((s) => s.status === 'pending' || s.status === 'uploading'),\n [staged],\n )\n const hasError = useMemo(() => staged.some((s) => s.status === 'error'), [staged])\n const enabled = options.enabled ?? true\n const blockReason = !enabled\n ? 'Attachments are disabled'\n : hasPending\n ? 'Attachments are still uploading'\n : hasError\n ? 'Remove failed attachments to send'\n : null\n\n return {\n composerFiles,\n references,\n addFiles,\n retry,\n removeAttachment,\n clear,\n hasPending,\n hasError,\n blockReason,\n }\n}\n","import { joinClasses } from './class-names'\n\n/**\n * The plan-mode toggle state an entry surface docks beside the composer. The\n * agent-identity pickers (model / harness / effort) speak the canonical\n * `AgentSessionControls` vocabulary — see \"UI chrome ownership (picker\n * canon)\" in AGENTS.md.\n */\nexport interface ComposerPlanModeSelection {\n enabled: boolean\n setEnabled: (next: boolean) => void\n saving?: boolean\n}\n\nexport interface ComposerModeControlsProps {\n /**\n * Plan-approval mode. Products pass it only when the selected backend can\n * propose a plan and wait for approval; omitted means nothing renders.\n */\n planMode?: ComposerPlanModeSelection\n}\n\n/** Checklist glyph, inline like every `/web-react` glyph — an icon-library\n * import here would turn that optional peer into a build-time requirement\n * for every consumer of the default surface. */\nfunction ListChecksGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m3 17 2 2 4-4M3 7l2 2 4-4M13 6h8M13 12h8M13 18h8\" />\n </svg>\n )\n}\n\n/**\n * The shared plan-mode toggle for the left side of an agent composer.\n * Plan mode is a behavioral switch, not part of the profile/backend/model/\n * thinking identity controls on the right.\n */\nexport function ComposerModeControls({ planMode }: ComposerModeControlsProps) {\n if (!planMode) return null\n\n return (\n <button\n type=\"button\"\n aria-pressed={planMode.enabled}\n disabled={planMode.saving}\n onClick={() => planMode.setEnabled(!planMode.enabled)}\n title=\"Plan mode: the agent proposes a plan you approve before it executes\"\n className={joinClasses(\n // Inset ring: this chip sits in a composer row that clips its overflow,\n // so an outward ring loses three of its four sides. Only the offset is\n // overridden — width and colour stay with the tokens.\n 'inline-flex h-[var(--control-height-sm,2rem)] items-center gap-1 rounded-full border px-2.5 text-sm transition-colors focus-visible:[outline-offset:-2px]',\n planMode.enabled\n ? 'border-primary/50 bg-primary/10 text-primary'\n : 'border-border bg-transparent text-muted-foreground hover:text-foreground',\n planMode.saving && 'opacity-60',\n )}\n >\n <ListChecksGlyph className=\"h-3.5 w-3.5\" />\n Plan\n </button>\n )\n}\n","import { useState, type ReactNode } from 'react'\nimport { ChatComposer } from './chat-composer'\nimport { ATTACHMENT_ACCEPT, useComposerAttachments } from './use-composer-attachments'\nimport type { ComposerFileRejection } from './composer-file-accept'\nimport type { UseFileMentionsResult } from './use-file-mentions'\nimport type { ChatAttachmentInput, FileMention } from '../chat-routes/wire'\nimport {\n AgentSessionControls,\n type AgentSessionControlsProps,\n} from './agent-session-controls'\nimport { ComposerModeControls, type ComposerPlanModeSelection } from './composer-mode-controls'\n\nexport interface EntryComposerProps {\n /** The one line above the input. Domain copy — always a product parameter. */\n heading?: ReactNode\n /** Optional supporting line under the heading. */\n subheading?: ReactNode\n placeholder?: string\n initialValue?: string\n sendLabel?: string\n disabled?: boolean\n /**\n * Agent identity (backend/model/effort). Pass it and the control row renders;\n * omit it and the composer ships without one. Omitting is a real choice for a\n * surface with nothing to choose — it should never be an oversight, which is\n * why this is one prop rather than a free-form slot a caller can forget.\n *\n * This is the CANONICAL picker cluster: `AgentSessionControls` from\n * `/web-react` (its model menu IS the canonical `ModelPicker`). The legacy\n * sandbox-ui adapter that used to back this prop was removed; the props\n * mapping for migrating a stored selection lives in\n * `docs/ui-picker-canon.md`.\n */\n agent?: AgentSessionControlsProps\n /**\n * Product-specific behavioral controls docked on the LEFT. A mode is an\n * on/off switch, not a value picker, so it sits apart from agent identity.\n * For the standard plan-approval control, prefer `planMode`.\n */\n modes?: ReactNode\n /**\n * Standard plan-approval mode. Pass only when the selected backend supports\n * it; omitted means the control is not rendered.\n */\n planMode?: ComposerPlanModeSelection\n /**\n * Upload endpoint for staged attachments. Omit and the attach affordance is\n * hidden — a composer with no place to put a file must not offer one.\n */\n uploadUrl?: string\n accept?: string\n onAttachmentError?: (reason: string) => void\n /** Files rejected by this composer's attachment policy. */\n onRejectFiles?: (rejections: ComposerFileRejection[]) => void\n /**\n * `@`-file mentions. The hook itself is shared (`useFileMentions`), but the\n * cold-box policy around it — whether pressing `@` is worth starting a\n * sandbox — is a per-surface product call, so the result is injected rather\n * than created here.\n */\n mentions?: UseFileMentionsResult\n mentionPopoverClassName?: string\n /** Rendered under the composer: suggestion pills, an onboarding nudge, a\n * disclaimer. All domain copy. */\n footer?: ReactNode\n /** Block submit until a persisted selection has resolved against the catalog,\n * so the first turn cannot go out under the wrong model. Defaults to true. */\n ready?: boolean\n onSubmit: (\n prompt: string,\n attachments: ChatAttachmentInput[],\n mentions: FileMention[],\n ) => void\n className?: string\n composerClassName?: string\n /** Max width of the centered column. Defaults to the fleet's 820px. */\n maxWidth?: number\n}\n\n/**\n * `EntryComposer` — the centered \"what do you want to work on?\" surface a\n * product shows before a conversation exists (a new thread, an empty session,\n * a workspace overview).\n *\n * It exists because three products each grew their own, and they drifted into\n * three different capability sets rather than three different looks: one lost\n * the attach button, one lost the model and effort pickers entirely, one\n * hand-rolled a `<textarea>` and wired pickers from three separate packages\n * into one row. The composer, the pickers, the attachment queue and the mention\n * index were all ALREADY shared — what was not shared was the assembly, so\n * every product re-derived which controls an entry surface gets, and each\n * re-derivation dropped a different one.\n *\n * Mechanism lives here: layout, the attachment queue, the mention wiring, the\n * submit gate (nothing sends while an upload is pending or failed, or before\n * the model selection has resolved). Domain stays a parameter: `heading`,\n * `placeholder`, `footer` (suggestion pills, disclaimers) and the selections\n * themselves.\n *\n * The input is `ChatComposer` — the same component a docked in-thread\n * composer renders directly; this assembly adds only the hero layout around\n * it.\n */\nexport function EntryComposer({\n heading,\n subheading,\n placeholder,\n initialValue = '',\n sendLabel,\n disabled,\n agent,\n modes,\n planMode,\n uploadUrl,\n accept = ATTACHMENT_ACCEPT,\n onAttachmentError,\n onRejectFiles,\n mentions,\n mentionPopoverClassName,\n footer,\n ready = true,\n onSubmit,\n className,\n composerClassName,\n maxWidth = 820,\n}: EntryComposerProps) {\n const [value, setValue] = useState(initialValue)\n const attachments = useComposerAttachments({\n uploadUrl,\n enabled: !!uploadUrl,\n onReject: onAttachmentError,\n onError: onAttachmentError,\n })\n\n function submit(prompt: string) {\n if (!ready || disabled) return\n const next = prompt.trim()\n // A pending or failed upload must not send: the turn would reference an\n // attachment the store does not have. Surface why rather than no-op'ing.\n if (attachments.hasPending || attachments.hasError) {\n if (attachments.blockReason) onAttachmentError?.(attachments.blockReason)\n return\n }\n if (!next && attachments.references.length === 0) return\n onSubmit(next, attachments.references, mentions?.mentions ?? [])\n setValue('')\n attachments.clear()\n mentions?.clearMentions()\n }\n\n return (\n <div\n className={\n className ??\n 'relative flex min-h-0 flex-1 flex-col items-center overflow-y-auto bg-background px-5 py-8'\n }\n >\n <div className=\"my-auto w-full shrink-0\" style={{ maxWidth }}>\n {heading ? (\n <h2 className=\"mb-4 text-center text-[1.75rem] font-medium tracking-tight text-foreground\">\n {heading}\n </h2>\n ) : null}\n {subheading ? (\n <p className=\"mx-auto mb-9 max-w-md text-center text-sm leading-relaxed text-muted-foreground\">\n {subheading}\n </p>\n ) : (\n heading ? <div className=\"mb-7\" /> : null\n )}\n <ChatComposer\n className={composerClassName ?? 'vt-composer'}\n value={value}\n onValueChange={setValue}\n onSend={(message) => submit(message)}\n placeholder={placeholder}\n sendLabel={sendLabel}\n // The circular arrow AgentComposer shipped and the agent-app canon\n // for new surfaces.\n sendVariant=\"icon\"\n disabled={disabled}\n sendDisabled={!ready}\n autoFocus\n // A hero composer autofocuses on mount, so the Cmd/Ctrl+L hint\n // would advertise a shortcut to the input the user is already in.\n focusShortcut={false}\n canSubmitAttachmentsOnly\n accept={accept}\n pendingFiles={attachments.composerFiles}\n onAttach={uploadUrl ? (files) => void attachments.addFiles(files) : undefined}\n onRejectFiles={onRejectFiles}\n onRemoveFile={attachments.removeAttachment}\n onRetryFile={attachments.retry}\n mention={\n mentions\n ? {\n ...mentions.mention,\n // Sidebar tone + hairline so the popover reads as app chrome\n // (the same treatment the picker menus get) instead of the\n // brighter overlay tone the component defaults to.\n popoverClassName:\n mentionPopoverClassName ??\n 'bg-surface-container-low border-[var(--md3-outline-variant)]',\n }\n : undefined\n }\n controls={modes ?? <ComposerModeControls planMode={planMode} />}\n trailing={\n agent ? <AgentSessionControls {...agent} /> : undefined\n }\n />\n {footer ? <div className=\"mt-7\">{footer}</div> : null}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useState","handle","lazy","useCallback","useEffect","useRef","useState","jsx","jsxs","lazy","useState","useRef","useCallback","useEffect","useCallback","useEffect","useMemo","useRef","useState","useRef","useState","useCallback","enabled","useEffect","useMemo","jsx","jsxs","useState","jsx","jsxs","useState"]}
|
|
1
|
+
{"version":3,"sources":["../src/web-react/use-dictation.ts","../src/web-react/lazy-load-boundary.tsx","../src/web-react/chat-composer.tsx","../src/web-react/use-composer-attachments.ts","../src/web-react/composer-mode-controls.tsx","../src/web-react/entry-composer.tsx"],"sourcesContent":["/**\n * `useDictation` — the capture half of composer dictation.\n *\n * Dictation splits at a clean seam: the browser owns capture (`getUserMedia` +\n * `MediaRecorder`), the host owns what the audio MEANS (transcription —\n * `sequences-react`'s Whisper provider — or a straight upload). This hook is\n * the capture half and nothing else: it asks for the mic, records, ticks whole\n * seconds while it does, and hands the assembled `Blob` to the host's\n * `onDictate`. A hook rather than composer-private code, because a host whose\n * composer is fully composed (hotkey, push-to-talk) needs the same capture\n * without re-deriving it.\n *\n * The rules the implementation exists to hold:\n *\n * - **Unsupported is a render signal, not an exception.** A browser without\n * `MediaRecorder`/`getUserMedia` gets `supported: false`, and the composer\n * renders no dead button. `start()` stays a no-op rather than throwing, so\n * a host that wired it to a hotkey cannot crash on such a browser.\n * - **The mic is released the moment recording ends.** Tracks are stopped in\n * every exit — stop, error, cancel-during-prompt, unmount. A red dot the\n * browser keeps showing after the composer says \"idle\" is the failure this\n * is written against.\n * - **A denied prompt is a message, not a crash.** `NotAllowedError` and a\n * missing device are reported through `onError` as words the composer can\n * show; the hook returns to idle.\n * - **Unmount discards.** A composer that unmounts mid-recording delivers\n * nothing: the host it would have called has moved on, and an arriving\n * transcript would land in a conversation the user left.\n * - **Duration is measured, not counted.** `durationSeconds` comes off the\n * clock at stop; the one-second ticker drives only the visible elapsed\n * display, so a throttled timer never falsifies the delivered figure.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n/** The audio a finished recording hands to the host. */\nexport interface DictationAudio {\n /** The assembled recording, typed with the MIME the recorder actually used. */\n readonly blob: Blob\n /** `blob.type`, surfaced so a host can switch on it without touching the blob. */\n readonly mimeType: string\n /** Clock-measured whole seconds between start and stop. */\n readonly durationSeconds: number\n}\n\nexport interface UseDictationOptions {\n /** The host callback: receive the recording. Transcription is the host's. */\n onDictate: (audio: DictationAudio) => void\n /** Capture failures in words (\"Microphone access was denied…\"). Optional —\n * the composer shows its own notice either way; this is for hosts that log. */\n onError?: (message: string) => void\n}\n\nexport interface DictationControls {\n /** Whether this browser can record at all. When false, render no affordance. */\n readonly supported: boolean\n readonly recording: boolean\n /** Whole seconds since the current recording started; drives the indicator. */\n readonly elapsedSeconds: number\n /** Ask for the mic and start. A no-op while a recording or a prompt is open. */\n readonly start: () => void\n /** Stop and deliver. Cancels a still-pending permission prompt instead. */\n readonly stop: () => void\n}\n\n/** Preference order: opus-in-webm first, Safari's mp4 last, UA default if none. */\nconst PREFERRED_MIME_TYPES = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'] as const\n\n/** The mime to ask the recorder for, or `undefined` to take the UA default. */\nexport function pickDictationMimeType(): string | undefined {\n if (typeof MediaRecorder === 'undefined' || typeof MediaRecorder.isTypeSupported !== 'function') {\n return undefined\n }\n for (const type of PREFERRED_MIME_TYPES) {\n if (MediaRecorder.isTypeSupported(type)) return type\n }\n return undefined\n}\n\n/** Capture support is a property of the browser, so it is read once per mount. */\nfunction detectDictationSupport(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n typeof navigator.mediaDevices?.getUserMedia === 'function' &&\n typeof MediaRecorder !== 'undefined'\n )\n}\n\n/** The failure as a sentence. The denied prompt is the common case and the one\n * whose generic name (\"NotAllowedError\") says nothing to a reader. */\nexport function dictationErrorMessage(error: unknown): string {\n if (error instanceof DOMException) {\n if (error.name === 'NotAllowedError') return 'Microphone access was denied — allow it in the browser to dictate.'\n if (error.name === 'NotFoundError') return 'No microphone found on this device.'\n }\n return 'Could not start recording.'\n}\n\n/** `0:00`, `0:09`, `1:05`, `60:00` — minutes unbounded, seconds always two digits. */\nexport function formatDictationElapsed(totalSeconds: number): string {\n const safe = Number.isFinite(totalSeconds) && totalSeconds > 0 ? Math.floor(totalSeconds) : 0\n const minutes = Math.floor(safe / 60)\n const seconds = safe % 60\n return `${minutes}:${String(seconds).padStart(2, '0')}`\n}\n\n/** One capture's mutable internals, kept in a ref: they move with recorder\n * events, not with renders. */\ninterface DictationSession {\n readonly stream: MediaStream\n readonly recorder: MediaRecorder\n readonly chunks: Blob[]\n readonly mimeType: string\n readonly startedAt: number\n /** Set when the capture must deliver nothing (unmount, recorder failure). */\n cancelled: boolean\n}\n\n/** Release the mic. Idempotent — every exit path ends here. */\nfunction releaseStream(stream: MediaStream): void {\n for (const track of stream.getTracks()) track.stop()\n}\n\nexport function useDictation({ onDictate, onError }: UseDictationOptions): DictationControls {\n const [supported] = useState(detectDictationSupport)\n const [recording, setRecording] = useState(false)\n const [elapsedSeconds, setElapsedSeconds] = useState(0)\n\n const sessionRef = useRef<DictationSession | null>(null)\n /** Cancels a start whose getUserMedia has not resolved yet. */\n const cancelPendingStartRef = useRef<(() => void) | null>(null)\n // The recorder's event handlers fire outside React's render, so they read the\n // LATEST callbacks — a re-rendered host must not have its audio delivered to\n // the props the recording started with.\n const callbacksRef = useRef({ onDictate, onError })\n callbacksRef.current = { onDictate, onError }\n\n // The visible elapsed ticker. Follows `recording`; reset on each start so a\n // reused composer never opens at the previous capture's stale count.\n useEffect(() => {\n if (!recording) return\n setElapsedSeconds(0)\n const id = setInterval(() => setElapsedSeconds((s) => s + 1), 1000)\n return () => clearInterval(id)\n }, [recording])\n\n /** End the session: release the mic, reset state. Delivery is onstop's job. */\n const teardown = useCallback((cancelled: boolean) => {\n const session = sessionRef.current\n if (session === null) return\n session.cancelled = session.cancelled || cancelled\n sessionRef.current = null\n releaseStream(session.stream)\n setRecording(false)\n }, [])\n\n const stop = useCallback(() => {\n // A stop while the permission prompt is still open cancels the start: when\n // the stream arrives it is released unused, and nothing ever records.\n cancelPendingStartRef.current?.()\n cancelPendingStartRef.current = null\n const session = sessionRef.current\n if (session === null || session.cancelled) return\n // stop() flushes the buffered chunk (dataavailable) and THEN fires stop —\n // the blob is assembled in onstop, so a stop mid-chunk loses nothing.\n if (session.recorder.state !== 'inactive') session.recorder.stop()\n }, [])\n\n const start = useCallback(() => {\n if (!supported) return\n if (sessionRef.current !== null || cancelPendingStartRef.current !== null) return\n\n let pendingCancelled = false\n cancelPendingStartRef.current = () => {\n pendingCancelled = true\n }\n\n navigator.mediaDevices.getUserMedia({ audio: true }).then(\n (stream) => {\n cancelPendingStartRef.current = null\n if (pendingCancelled) {\n releaseStream(stream)\n return\n }\n const mimeType = pickDictationMimeType()\n const recorder = new MediaRecorder(stream, mimeType === undefined ? undefined : { mimeType })\n const session: DictationSession = {\n stream,\n recorder,\n chunks: [],\n mimeType: recorder.mimeType || mimeType || '',\n startedAt: Date.now(),\n cancelled: false,\n }\n sessionRef.current = session\n\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) session.chunks.push(event.data)\n }\n\n recorder.onstop = () => {\n teardown(session.cancelled)\n if (session.cancelled) return\n const blob = new Blob(session.chunks, { type: session.mimeType })\n if (blob.size === 0) {\n // A tap on/off can produce no bytes at all. Handing the host a\n // 0-byte blob reads as a recording that happened; it did not.\n callbacksRef.current.onError?.('Nothing was recorded.')\n return\n }\n const durationSeconds = Math.max(0, Math.round((Date.now() - session.startedAt) / 1000))\n callbacksRef.current.onDictate({ blob, mimeType: session.mimeType, durationSeconds })\n }\n\n recorder.onerror = () => {\n // The capture is dead; what matters is that the mic is released and\n // the hook is not wedged — the next start builds a fresh session.\n teardown(true)\n callbacksRef.current.onError?.('Recording stopped unexpectedly.')\n }\n\n recorder.start()\n setRecording(true)\n },\n (error: unknown) => {\n cancelPendingStartRef.current = null\n if (pendingCancelled) return\n callbacksRef.current.onError?.(dictationErrorMessage(error))\n },\n )\n }, [supported, teardown])\n\n // Unmount mid-recording discards the capture: mark it cancelled so onstop\n // delivers nothing, then stop the recorder to flush its events, and release\n // the mic whether or not those events ever fire.\n useEffect(\n () => () => {\n cancelPendingStartRef.current?.()\n cancelPendingStartRef.current = null\n const session = sessionRef.current\n if (session === null) return\n session.cancelled = true\n sessionRef.current = null\n try {\n if (session.recorder.state !== 'inactive') session.recorder.stop()\n } finally {\n releaseStream(session.stream)\n }\n },\n [],\n )\n\n return { supported, recording, elapsedSeconds, start, stop }\n}\n","import { Component, createElement, lazy, useEffect, useState, type ComponentType, type ReactElement, type ReactNode } from 'react'\n\ninterface LazyLoadBoundaryProps {\n children: ReactNode\n renderFailure: (error: unknown) => ReactNode\n}\n\n/** Keep a failed lazy import inside the surface that requested it. */\nexport class LazyLoadBoundary extends Component<LazyLoadBoundaryProps, { failed: boolean; error: unknown }> {\n state = { failed: false, error: null as unknown }\n\n static getDerivedStateFromError(error: unknown) {\n return { failed: true, error }\n }\n\n render() {\n if (this.state.failed) return this.props.renderFailure(this.state.error)\n return this.props.children\n }\n}\n\nconst ROUTE_RELOAD_KEY = 'agent-app:route-chunk-reload-at'\nconst ROUTE_RELOAD_WINDOW_MS = 60_000\nconst ROUTE_RELOAD_PARAM = '__agent_app_route_reload'\nconst CHUNK_LOAD_FAILURE =\n /failed to fetch dynamically imported module|error loading dynamically imported module|importing a module script failed|loading chunk .* failed|failed to load module script/i\n\n/** A stale deployed HTML page can reference a chunk removed by the next deploy. */\nfunction isChunkLoadFailure(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false\n const candidate = error as { name?: unknown; message?: unknown }\n return candidate.name === 'ChunkLoadError' ||\n (typeof candidate.message === 'string' && CHUNK_LOAD_FAILURE.test(candidate.message))\n}\n\nfunction loadFreshDocument(): void {\n const url = new URL(window.location.href)\n url.searchParams.set(ROUTE_RELOAD_PARAM, Date.now().toString(36))\n window.location.replace(url.toString())\n}\n\nfunction RouteLoadFailure({ error, autoReloadOnChunkError }: { error: unknown; autoReloadOnChunkError: boolean }) {\n useEffect(() => {\n if (!autoReloadOnChunkError || !isChunkLoadFailure(error) || typeof window === 'undefined') return\n try {\n const lastReload = Number(window.sessionStorage.getItem(ROUTE_RELOAD_KEY) ?? 0)\n const elapsed = Date.now() - lastReload\n if (lastReload > 0 && elapsed >= 0 && elapsed < ROUTE_RELOAD_WINDOW_MS) return\n window.sessionStorage.setItem(ROUTE_RELOAD_KEY, String(Date.now()))\n loadFreshDocument()\n } catch {\n // Storage may be disabled. The visible reload action remains available.\n }\n }, [error, autoReloadOnChunkError])\n\n return (\n <div role=\"alert\" className=\"rounded-xl border border-border bg-card p-5 text-foreground\">\n <p className=\"font-medium\">This page couldn’t load.</p>\n <p className=\"mt-1 text-sm text-muted-foreground\">Reload to try again.</p>\n <button\n type=\"button\"\n className=\"mt-4 rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n onClick={loadFreshDocument}\n >\n Reload page\n </button>\n </div>\n )\n}\n\nexport interface RouteChunkBoundaryProps {\n children: ReactNode\n /** Enable only where a reload cannot discard unsaved work. Default: false. */\n autoReloadOnChunkError?: boolean\n}\n\n/** Wrap a lazy route and its Suspense fallback; recovery requests a fresh document URL. */\nexport function RouteChunkBoundary({ children, autoReloadOnChunkError = false }: RouteChunkBoundaryProps) {\n useEffect(() => {\n const url = new URL(window.location.href)\n if (!url.searchParams.has(ROUTE_RELOAD_PARAM)) return\n url.searchParams.delete(ROUTE_RELOAD_PARAM)\n window.history.replaceState(window.history.state, '', url)\n }, [])\n return (\n <LazyLoadBoundary renderFailure={(error) => <RouteLoadFailure error={error} autoReloadOnChunkError={autoReloadOnChunkError} />}>\n {children}\n </LazyLoadBoundary>\n )\n}\n\n/**\n * A lazily loaded route that renders synchronously once its module has loaded.\n *\n * `React.lazy` calls its loader only on first render, so even an already\n * downloaded chunk suspends once, and React holds that Suspense retry for its\n * ~300 ms fallback throttle. Hospitality measured 305 ms before the first data\n * read on every first visit to a lazy route, with the chunk already cached.\n * Each mount decides once which component it renders, so a route mounted\n * before its module arrived keeps its state when it re-renders later.\n */\nexport type LazyRoute<P extends object> = ((props: P) => ReactElement) & { preload: () => Promise<void> }\n\nexport function lazyRoute<P extends object>(load: () => Promise<ComponentType<P>>): LazyRoute<P> {\n let loaded: ComponentType<P> | null = null\n let pending: Promise<ComponentType<P>> | null = null\n const start = () => (pending ??= load().then(\n (component) => (loaded = component),\n (error: unknown) => { pending = null; throw error },\n ))\n const Lazy = lazy(() => start().then((component) => ({ default: component })))\n function Route(props: P) {\n const [Ready] = useState(() => loaded)\n return Ready ? createElement(Ready, props) : createElement(Lazy as unknown as ComponentType<P>, props)\n }\n return Object.assign(Route, { preload: () => start().then(() => undefined) })\n}\n\n/** Load routes while the browser is idle (at most `timeoutMs` later), so a first visit waits only on its data. */\nexport function preloadWhenIdle(routes: ReadonlyArray<{ preload: () => Promise<void> }>, timeoutMs = 3000): () => void {\n const run = () => { for (const route of routes) void route.preload().catch(() => { /* The route's boundary reports a failed load when it renders. */ }) }\n if (typeof requestIdleCallback === 'function') {\n const handle = requestIdleCallback(run, { timeout: timeoutMs })\n return () => cancelIdleCallback(handle)\n }\n const handle = setTimeout(run, Math.min(timeoutMs, 1000))\n return () => clearTimeout(handle)\n}\n","/**\n * ChatComposer — the shared message input every agent app used to hand-roll:\n * an auto-resizing textarea (Enter sends, Shift+Enter inserts a newline), an\n * opt-in attach + drag-and-drop + clipboard-paste surface with pending-file\n * chips, an opt-in `@`-mention mode (`mention`) that swaps the textarea for a\n * lazily loaded rich input with atomic mention pills, a streaming Stop/Send\n * toggle, a slot for inline controls (model picker, reasoning effort), and a\n * Cmd/Ctrl+L focus shortcut.\n *\n * Files arrive by three routes — the picker dialog, a drop, and a paste — and\n * all three funnel through `accept` (`./composer-file-accept`) before they\n * reach `onAttach`, so a type the picker will not offer cannot get in by\n * another route. What `accept` refuses goes to `onRejectFiles` with a reason;\n * without that prop a refusal is silent, which is what the native picker also\n * does. Size and count limits stay the host's job — `useComposerAttachments`\n * owns them, because they depend on what is already staged.\n *\n * A REJECTED send never destroys the draft. The input clears optimistically —\n * the composer stays editable while a turn streams precisely so the next\n * message can be typed against a live answer, and holding the sent text in the\n * box until the server confirms would put the clear on a collision course with\n * that typing. So the clear happens immediately and the draft is held until the\n * send is known to have landed: a handler that throws, rejects, or returns\n * `{ ok: false }` puts the exact bytes back with the caret where it was, names\n * the reason, and reports `onSendFailed` so the host can restore the\n * attachments it consumed. If the user has already typed a replacement, the\n * unsent text is shown in the notice with its own Retry instead of overwriting\n * what they typed — neither draft is ever destroyed.\n *\n * Styling contract matches the rest of `web-react`: Tailwind over the shared\n * design tokens (`bg-card`, `border-border`, `text-foreground`, `bg-primary`, …)\n * and inline-SVG glyphs. It defines NO `--chat-*` / `--brand-*` custom\n * properties, so it themes correctly in any shell that provides the standard\n * tokens — the input renders on-palette instead of collapsing to unstyled\n * fallbacks when a host hasn't defined a private chat-token set.\n */\n\nimport {\n lazy,\n Suspense,\n useCallback,\n useEffect,\n useMemo,\n useId,\n useRef,\n useState,\n type ChangeEvent,\n type ClipboardEvent,\n type DragEvent,\n type KeyboardEvent,\n type ReactNode,\n} from 'react'\n\nimport { focusFieldWithin } from '@tangle-network/ui/utils'\n\nimport {\n filterAcceptedFiles,\n renamePastedImages,\n type ComposerFileRejection,\n} from './composer-file-accept'\nimport { filterCommandPaletteItems, type CommandPaletteItem } from '../session-shell/index'\nimport { OVERLAY_SHADOW, POPOVER_OPTION_FOCUS, PopoverSurface } from './controls'\nimport { formatDictationElapsed, useDictation, type DictationAudio } from './use-dictation'\nimport type { ComposerMentionProp } from './use-file-mentions'\nimport { LazyLoadBoundary } from './lazy-load-boundary'\n\n/**\n * The TipTap editor is a lazy chunk: only consumers that pass `mention` pull\n * the editor stack into their bundle, and only when the mention path renders.\n * The `@tiptap/*` packages behind it are OPTIONAL peers reached only\n * through `loadMentionEditor`'s dynamic imports — a bundler replaces a missing\n * one with a runtime-throwing stub, so a consumer without them still builds.\n */\nfunction createLazyMentionEditor() {\n return lazy(() => import('./mention-editor').then((m) => m.loadMentionEditor()))\n}\n\n// ── glyphs (no icon-library dependency) ───────────────────────────────────\n\n/** The focus-shortcut hint names the platform's modifier: Cmd on Apple,\n * Ctrl everywhere else (the handler itself listens for both). SSR-safe —\n * defaults to Ctrl when there's no navigator to ask. */\nconst IS_APPLE_PLATFORM =\n typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/i.test(navigator.platform)\n\nfunction SendGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z\" />\n </svg>\n )\n}\n\nfunction StopGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden>\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n </svg>\n )\n}\n\nfunction ArrowUpGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 19V5M5 12l7-7 7 7\" />\n </svg>\n )\n}\n\nfunction PaperclipGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n </svg>\n )\n}\n\nfunction FolderGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z\" />\n <path d=\"M12 10v6m-3-3h6\" />\n </svg>\n )\n}\n\nfunction CloseGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n )\n}\n\nfunction RetryGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n )\n}\n\nfunction UploadGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12\" />\n </svg>\n )\n}\n\nfunction MicGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\" />\n <path d=\"M5 10v1a7 7 0 0 0 14 0v-1M12 18v4\" />\n </svg>\n )\n}\n\n// ── component ──────────────────────────────────────────────────────────────\n\n/** Prompt-part descriptor an uploaded file carries (the upload route's\n * `UploadedChatFile.part`), echoed back in the turn body on send. Mirrors\n * `/chat-routes`' wire shape structurally — no server import here. */\nexport interface ComposerFilePart {\n type: 'image' | 'file'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n content?: string\n}\n\nexport interface ComposerFile {\n id: string\n name: string\n size?: number\n kind: 'file' | 'folder'\n /** Number of files inside, for a folder chip. */\n fileCount?: number\n status: 'pending' | 'uploading' | 'ready' | 'error'\n /** Uploaded part descriptor; set once the upload route returns. Only\n * `status: 'ready'` files with a part travel on a parts-aware send. */\n part?: ComposerFilePart\n /** Object URL for an image thumbnail on the chip. The host owns the URL's\n * whole life — `URL.createObjectURL` when the file is staged,\n * `URL.revokeObjectURL` when it leaves — and the composer only reads it.\n * `useComposerAttachments` already does both. */\n previewUrl?: string\n /** Why this file failed, shown on the chip while `status: 'error'`. Without\n * it an error chip is red and mute, which tells the user nothing. */\n errorMessage?: string\n}\n\n/** A piece of context the agent will see beside the next message — an open\n * file, a selected record, a pinned document. Rendered as its own chip row,\n * separate from staged attachments: context is what the turn already carries,\n * an attachment is what the user is adding to it. */\nexport interface ComposerContextItem {\n id: string\n label: string\n icon?: ReactNode\n /** Omit for a chip the user cannot dismiss. */\n onRemove?: () => void\n}\n\n/** A send the host refused. `error` is shown verbatim in the composer's notice;\n * omit it for the generic copy. */\nexport interface ComposerSendRejected {\n ok: false\n error?: string\n}\n\n/**\n * What a send handler reports back. `void` — what every handler returned before\n * this existed — reads as accepted, so wiring stays unchanged; a thrown error, a\n * rejected promise, or `{ ok: false }` is the rejection that restores the draft.\n * A handler that resolves only when the whole turn finishes still reports\n * correctly: the input already cleared on dispatch, so the answer only decides\n * whether the draft comes back.\n */\nexport type ComposerSendOutcome = void | { ok: true } | ComposerSendRejected\nexport type ComposerSendResult = ComposerSendOutcome | Promise<ComposerSendOutcome>\n\n/**\n * A send handler, typed as a UNION with the legacy `=> void` signature rather\n * than as `(…) => ComposerSendResult` alone.\n *\n * TypeScript's return-type-`void` rule accepts a function returning ANYTHING\n * where a `=> void` is expected, and that rule fires only when the target's\n * return type is exactly `void` — not when it is a union that contains `void`.\n * So narrowing this prop to `ComposerSendResult` would reject handler shapes\n * that compiled against the shipped `onSend?: (message: string) => void`:\n * `onSend={(m) => rows.push(m)}` (returns `number`) and\n * `onSend={(m) => append({ role: 'user', content: m })}` (an ai-sdk append\n * returns `Promise<string | null | undefined>`) both stop compiling, on a\n * package whose pinned consumers must never need a source edit to take a minor.\n *\n * The union keeps both: a legacy handler lands on the first member, and a\n * handler that reports an outcome lands on the second. A call through it\n * resolves to `void | ComposerSendResult`, which IS `ComposerSendResult`, so\n * the composer reads the outcome exactly as before.\n */\nexport type ComposerSendHandler =\n | ((message: string) => void)\n | ((message: string) => ComposerSendResult)\n\n/** @see ComposerSendHandler — the parts-aware arity, same union for the same reason. */\nexport type ComposerSendPartsHandler =\n | ((message: string, parts: ComposerFilePart[]) => void)\n | ((message: string, parts: ComposerFilePart[]) => ComposerSendResult)\n\n/** The rejected send, handed to `onSendFailed` so the host can undo whatever it\n * cleared optimistically — most importantly the staged attachments, which the\n * composer does not own (`pendingFiles` is a prop). */\nexport interface ComposerSendFailure {\n /** The reason as the composer renders it. */\n message: string\n /** The user's exact draft, untrimmed. */\n text: string\n /** The parts the rejected send carried. */\n parts: ComposerFilePart[]\n /** Whatever the handler threw / rejected with, or the `{ ok: false }` value. */\n error: unknown\n /** True when the draft was put back in the textarea (the box was empty).\n * False means the user had typed a replacement, so the unsent text is held in\n * the notice instead. */\n restored: boolean\n}\n\n/**\n * One `/` command the composer offers. Typing `/` at position 0 opens the\n * command menu; the rest of the token filters it (the same prefix > substring\n * > token-order ranking as the command palette). Picking a command CLEARS the\n * token from the draft and calls `run` — what the command does (a route, a\n * dialog, a draft transformation) is the product's business.\n */\nexport interface SlashCommand {\n /** Command name without the leading slash: `model`, `clear`. */\n name: string\n /** One line of what it does, rendered beside the name. */\n description: string\n run: () => void\n}\n\n/** A skill the host has made eligible for this turn. The id is the value the\n * host validates and sends; the name and description are display metadata. */\nexport interface ComposerSkillOption {\n id: string\n name: string\n description: string\n}\n\nexport interface ChatComposerProps {\n /** Send the trimmed message, or an empty message when a skill is selected.\n * Attached files travel separately via\n * `onAttach` + `pendingFiles` (the host consumes and clears them on send).\n * Optional when `onSendParts` is wired.\n *\n * Report a refused send by throwing, rejecting, or returning `{ ok: false }`\n * — the composer restores the draft rather than losing it. */\n onSend?: ComposerSendHandler\n /** Parts-aware send: receives the trimmed message plus the `part`\n * descriptors of every `ready` pending file. Takes precedence over\n * `onSend`; enables file-only sends (empty text, ≥1 ready part).\n *\n * Same rejection contract as `onSend`. */\n onSendParts?: ComposerSendPartsHandler\n /** Notified when a send is rejected, after the composer has restored what it\n * owns. The host uses it to put back the `pendingFiles` it consumed. */\n onSendFailed?: (failure: ComposerSendFailure) => void\n /** Notice copy when the handler names no reason of its own. */\n sendFailureMessage?: string\n /** Stop the in-flight turn; shown in place of Send while `isStreaming`. */\n onCancel?: () => void\n isStreaming?: boolean\n /** Block input + send (e.g. while restoring). Distinct from `isStreaming`,\n * which keeps the textarea editable so the next turn can be composed. */\n disabled?: boolean\n /** Block sending while keeping the draft editable (e.g. model choices load). */\n sendDisabled?: boolean\n placeholder?: string\n\n /** Controlled value. Omit for self-managed internal state (cleared on send). */\n value?: string\n onValueChange?: (value: string) => void\n /** Initial text in uncontrolled mode; ignored when `value` is provided. */\n initialValue?: string\n\n /** One-shot external prefill: when this becomes a non-null string the\n * composer adopts it as the draft (replacing any current draft), focuses the\n * input with the caret at the end, and reports consumption via\n * `onSeedApplied` so the host can clear its seed state. */\n seed?: string | null\n onSeedApplied?: () => void\n\n /** Inline controls (e.g. `<ModelPicker/>` + `<EffortPicker/>` or\n * `<AgentSessionControls/>`). */\n controls?: ReactNode\n /**\n * Where {@link controls} sit. `inline` (default) puts them on the card's own\n * action row, beside attach and Send — the model a turn will use reads as\n * part of the input rather than as a separate widget floating above it.\n * `above` keeps them outside the card, for a host that wants the input to be\n * nothing but the input.\n */\n controlsPlacement?: 'above' | 'inline'\n\n /** Attachments are opt-in: pass `onAttach` to show the attach button, accept\n * drag-and-drop and clipboard paste onto the input, and render\n * `pendingFiles` chips. */\n onAttach?: (files: FileList) => void\n onAttachFolder?: (files: FileList) => void\n pendingFiles?: ComposerFile[]\n onRemoveFile?: (id: string) => void\n /** Pass it and a chip with `status: 'error'` gains a retry button. */\n onRetryFile?: (id: string) => void\n /**\n * File types the composer takes, in the native `<input accept>` grammar.\n * Enforced on every ingress route — the picker dialog (which the user can\n * override with \"All Files\"), drag-and-drop, and clipboard paste — so a type\n * the picker will not offer cannot arrive by another route. A non-matching\n * file goes to `onRejectFiles` and never reaches `onAttach`. Folder attach is\n * exempt: directory selection has no native accept semantics.\n */\n accept?: string\n /** Called with the files `accept` removed from a pick, drop, or paste, each\n * with a reason. Without it a refusal is silent — the same feedback the\n * native picker gives for a type it will not offer. */\n onRejectFiles?: (rejections: ComposerFileRejection[]) => void\n dropTitle?: string\n dropDescription?: string\n\n /** Context the agent will see beside the next message, as its own chip row\n * above the input. */\n contextItems?: ReadonlyArray<ComposerContextItem>\n\n /**\n * Let a staged file stand in for message text, so the send control stays live\n * while an upload is in flight instead of going dead with nothing to explain\n * it. Default false, where an empty message needs a `ready` file.\n *\n * It does NOT make an unfinished file sendable. A turn whose only content is a\n * file that is still uploading or has failed never reaches the send handler —\n * it would arrive empty and the attachment would be lost. The composer\n * refuses it and names the reason in its notice\n * ({@link attachmentsNotReadyMessage}). So the flag decides whether the\n * control is live, and the composer keeps the integrity gate rather than\n * leaving each host to re-derive it.\n */\n canSubmitAttachmentsOnly?: boolean\n /** Notice copy when a send is refused because no staged file is ready yet.\n * Defaults to wording chosen from whether a file failed or is still\n * uploading. */\n attachmentsNotReadyMessage?: string\n /**\n * Let Enter and Send keep firing while `isStreaming`, for a surface that\n * queues the next turn rather than blocking on the current one. Default\n * false. The button still flips to Stop while a turn streams, so this opens\n * the keyboard path, not a second button.\n */\n canSubmitWhileBusy?: boolean\n\n /** Focus the input on mount — for a surface whose whole job is the input\n * (an entry/hero composer), never for one docked under a transcript. */\n autoFocus?: boolean\n /** Rows the input shows before it grows. Default 2. */\n minRows?: number\n /** Pixel height the input grows to before it scrolls. Default 168. */\n maxHeight?: number\n /** Content between the controls slot and Send — a token meter, a cost, a\n * status line, a model picker. It sits outside the controls slot, so a\n * wrapping picker set cannot push it away; it travels with Send onto its\n * own line when the row is too narrow for both, and wraps inside that line\n * rather than overflowing the card. */\n trailing?: ReactNode\n /**\n * Opt-in `@`-mentions. Present ⇒ the textarea is swapped for a lazily\n * loaded TipTap rich input that renders mentions as atomic pills and\n * serializes them to `@<id>` in the value; absent ⇒ exactly the plain\n * textarea, with no TipTap in the bundle. Wire `useFileMentions().mention`\n * straight in. The six `@tiptap/*` packages (core, extension-mention, pm,\n * react, starter-kit, suggestion) are OPTIONAL peers — a consumer installs\n * them to use this prop.\n *\n * General `slashCommands` remain disabled while `mention` is set; the\n * skill picker has explicit keyboard handling in both input modes. Seed and\n * failed-send drafts still apply in mention mode, but caret placement (a\n * textarea affordance) degrades to content-only.\n */\n mention?: ComposerMentionProp\n /** `/` commands offered when the draft is exactly a leading slash token.\n * Omit (or pass []) and `/` types as ordinary text. Inert while `mention`\n * is set — see {@link mention}. */\n slashCommands?: SlashCommand[]\n /** Skills available in the current authenticated workspace and harness.\n * The host resolves eligibility; this list only drives selection UI. */\n eligibleSkills?: ReadonlyArray<ComposerSkillOption>\n /** The host-owned skill selected for the next turn. */\n selectedSkillId?: string | null\n /** Picking or removing a skill reports its typed id and display metadata.\n * The host persists selection through queued turns and rejected sends. */\n onSelectedSkillChange?: (skill: ComposerSkillOption | null) => void\n /** Dictation is opt-in: pass `onDictate` and the action row gains a mic\n * button (browsers without `MediaRecorder`/`getUserMedia` render none).\n * Click starts the capture; the button flips to a stop control with the\n * running elapsed seconds; stop hands the recorded audio blob here. The\n * composer owns capture only — turning the audio into text (e.g. the\n * Whisper provider from `sequences-react`) is the host's. */\n onDictate?: (audio: DictationAudio) => void\n /** Capture failures (a denied mic prompt, no device), after the composer has\n * shown its own dismissible notice. For hosts that log or track. */\n onDictateError?: (message: string) => void\n\n /** Cmd/Ctrl+L focuses the input and shows the hint. Default true. */\n focusShortcut?: boolean\n /** Called when focus enters the composer: the input or any of its controls.\n * Pass `useWorkspacePrewarm().reassert` so a box that suspended while the\n * tab sat open starts resuming before the member sends. */\n onFocusWithin?: () => void\n /** Float the card on a soft two-layer foreground-tinted shadow (opt-in).\n * Elevation only — radius, ring, and control layout are unchanged. */\n floating?: boolean\n /** Send button label. Default \"Send\". */\n sendLabel?: string\n /** Send control shape. `pill` (default) is the labeled button; `icon` is the\n * 34px circular inverted arrow (streaming: circular outlined stop) — the\n * grammar sandbox-ui's legacy AgentComposer used and the current agent-app\n * canon for new surfaces. */\n sendVariant?: 'pill' | 'icon'\n /** Circular icon send colour. `contrast` (default) preserves the inverted\n * foreground/background canon; `primary` opts into the host brand tokens.\n * The labeled pill already uses primary and is unchanged. */\n sendTone?: 'contrast' | 'primary'\n className?: string\n}\n\nconst DEFAULT_MAX_HEIGHT = 168\n\n/** The input's own `leading-6` line box and its `py-1` padding, in pixels. The\n * `minRows` floor is computed from them so the CSS floor and the `rows`\n * attribute cannot drift apart at a row count other than the default. */\nconst LINE_HEIGHT = 24\nconst TEXTAREA_PADDING_Y = 8\n\nconst DEFAULT_SEND_FAILURE = \"Message not sent. Your draft is still here — try again.\"\n\n/** A rejection the handler reported by value rather than by throwing. */\nfunction isRejectedOutcome(outcome: ComposerSendOutcome): outcome is ComposerSendRejected {\n return typeof outcome === 'object' && outcome !== null && outcome.ok === false\n}\n\nfunction isPromise(value: ComposerSendResult): value is Promise<ComposerSendOutcome> {\n return typeof (value as Promise<ComposerSendOutcome> | undefined)?.then === 'function'\n}\n\n/** The reason to show. A rejection's own `error` string wins; then an Error's\n * message; else the caller's copy — never an empty notice. */\nfunction sendFailureText(error: unknown, fallback: string): string {\n if (typeof error === 'object' && error !== null && 'ok' in error) {\n const named = (error as ComposerSendRejected).error\n if (typeof named === 'string' && named.trim() !== '') return named\n return fallback\n }\n if (typeof error === 'string' && error.trim() !== '') return error\n if (error instanceof Error && error.message.trim() !== '') return error.message\n return fallback\n}\n\ninterface FailedSend {\n message: string\n /** The user's exact draft, untrimmed — what a restore puts back. */\n text: string\n /** The trimmed form the handler was called with, so Retry sends the same\n * bytes the rejected attempt did. */\n trimmed: string\n parts: ComposerFilePart[]\n restored: boolean\n}\n\nexport function ChatComposer({\n onSend,\n onSendParts,\n onSendFailed,\n sendFailureMessage = DEFAULT_SEND_FAILURE,\n onCancel,\n isStreaming = false,\n disabled = false,\n sendDisabled = false,\n placeholder = 'Message the agent…',\n value,\n onValueChange,\n initialValue,\n seed,\n onSeedApplied,\n controls,\n controlsPlacement = 'inline',\n onAttach,\n onAttachFolder,\n pendingFiles = [],\n onRemoveFile,\n onRetryFile,\n accept,\n onRejectFiles,\n dropTitle = 'Drop files to add context',\n dropDescription = 'They attach to your next message.',\n contextItems = [],\n canSubmitAttachmentsOnly = false,\n attachmentsNotReadyMessage,\n canSubmitWhileBusy = false,\n autoFocus,\n minRows = 2,\n maxHeight = DEFAULT_MAX_HEIGHT,\n trailing,\n mention,\n slashCommands,\n eligibleSkills = [],\n selectedSkillId,\n onSelectedSkillChange,\n onDictate,\n onDictateError,\n\n focusShortcut = true,\n onFocusWithin,\n floating = false,\n sendLabel = 'Send',\n sendVariant = 'pill',\n sendTone = 'contrast',\n className,\n}: ChatComposerProps) {\n const isControlled = value !== undefined\n const [internal, setInternal] = useState(initialValue ?? '')\n const text = isControlled ? value : internal\n const MentionEditor = useMemo(createLazyMentionEditor, [])\n // A send outcome arrives after the render that dispatched it, so the restore\n // decision must read the LIVE draft, not the one captured in that closure.\n const textRef = useRef(text)\n textRef.current = text\n\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n // Set by the mention editor so autofocus-independent focus paths (the\n // Cmd/Ctrl+L shortcut) reach it in the rich path, where `textareaRef` stays\n // null. The editor registers `null` on unmount, so the shortcut can never\n // call into a destroyed editor.\n const richFocusRef = useRef<(() => void) | null>(null)\n // Stable identity so the mention editor's registration effect only reruns\n // when the editor instance itself changes, not on every parent render.\n const registerRichFocus = useCallback((focus: (() => void) | null) => {\n richFocusRef.current = focus\n }, [])\n const fileInputRef = useRef<HTMLInputElement>(null)\n const folderInputRef = useRef<HTMLInputElement>(null)\n const [dragOver, setDragOver] = useState(false)\n const dragDepth = useRef(0)\n // Counts every clipboard image this composer has renamed, so two pastes of\n // the same bitmap do not both arrive as `image.png`.\n const pastedImageCount = useRef(0)\n\n const setText = useCallback(\n (next: string) => {\n if (!isControlled) setInternal(next)\n onValueChange?.(next)\n },\n [isControlled, onValueChange],\n )\n\n // Dictation: capture only. The hook reports every failure in words; the\n // composer shows them in its own dismissible notice (the same shape as a\n // rejected send) AND forwards them for hosts that log.\n const [dictateError, setDictateError] = useState<string | null>(null)\n const handleDictated = useCallback(\n (audio: DictationAudio) => {\n setDictateError(null)\n onDictate?.(audio)\n },\n [onDictate],\n )\n const handleDictateError = useCallback(\n (message: string) => {\n setDictateError(message)\n onDictateError?.(message)\n },\n [onDictateError],\n )\n const dictation = useDictation({ onDictate: handleDictated, onError: handleDictateError })\n\n // Keep the textarea height in sync with the content for BOTH typed and\n // external (controlled) value changes — one effect covers both paths. It also\n // reruns when the bounds move: `rows` is what `scrollHeight` resolves the\n // measurement against, so a changed `minRows` that did not re-measure would\n // strand the previous inline height.\n useEffect(() => {\n const el = textareaRef.current\n if (!el) return\n el.style.height = 'auto'\n el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n }, [text, maxHeight, minRows])\n\n // Adopt a one-shot seed. Applies only when the `seed` PROP transitions to a\n // new string (host sets it → consumed here → host clears it via\n // onSeedApplied), so an unstable callback identity re-running this effect\n // can never re-apply a still-set seed over the user's typing. Like\n // `initialValue`, the seed is honored ONLY in uncontrolled mode — a\n // controlled host drives its own `value` (which would shadow `setText`), so\n // it seeds by updating that state itself.\n const prevSeedRef = useRef<string | null>(null)\n const pendingCaretRef = useRef<string | null>(null)\n useEffect(() => {\n const prev = prevSeedRef.current\n prevSeedRef.current = seed ?? null\n if (seed == null || seed === prev || isControlled) return\n setText(seed)\n onSeedApplied?.()\n const el = textareaRef.current\n if (el && el.value === seed) {\n // The DOM already shows the seed — setText was a no-op (the user had\n // typed the exact string), so no re-render is coming and the [text]\n // effect below won't fire. Position the caret now instead of leaving a\n // stranded pendingCaretRef.\n el.focus()\n el.setSelectionRange(seed.length, seed.length)\n } else {\n // Defer caret positioning until the seeded value renders (see below).\n pendingCaretRef.current = seed\n }\n }, [seed, setText, onSeedApplied, isControlled])\n\n // Focus + caret-to-end AFTER the seeded value has rendered into the DOM —\n // setSelectionRange in the applying effect would run against the pre-render\n // value and clamp the caret to the old text's length.\n useEffect(() => {\n if (pendingCaretRef.current == null || pendingCaretRef.current !== text)\n return\n pendingCaretRef.current = null\n const el = textareaRef.current\n if (!el) return\n el.focus()\n el.setSelectionRange(text.length, text.length)\n }, [text])\n\n // A restored draft gets the caret back where the user left it — same\n // post-render rule as the seed above, but to the recorded offsets rather than\n // to the end, so a failed send returns the user to the word they were on.\n const restoreCaretRef = useRef<{ text: string; start: number; end: number } | null>(null)\n useEffect(() => {\n const pending = restoreCaretRef.current\n if (!pending || pending.text !== text) return\n restoreCaretRef.current = null\n const el = textareaRef.current\n if (!el) return\n el.focus()\n const start = Math.min(pending.start, text.length)\n const end = Math.min(pending.end, text.length)\n el.setSelectionRange(start, end)\n }, [text])\n\n // Cmd/Ctrl+L focuses the composer from anywhere — the shortcut the hint\n // advertises. Scoped to when the shortcut is enabled and not disabled.\n // Depends on mention PRESENCE, not identity: hosts build the prop object\n // inline per render, and only the mode switch changes which input to focus.\n const mentionEnabled = mention != null\n useEffect(() => {\n if (!focusShortcut || disabled) return\n function onKeyDown(e: globalThis.KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'l') {\n if (mentionEnabled) {\n const focus = richFocusRef.current\n e.preventDefault()\n if (focus) focus()\n else textareaRef.current?.focus()\n } else {\n e.preventDefault()\n textareaRef.current?.focus()\n }\n }\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n }, [focusShortcut, disabled, mentionEnabled])\n\n // A ready file counts as sendable content even without a `part`: store-backed\n // attachments (`useComposerAttachments`) carry no prompt part — their\n // references ride the turn body's `attachments` field — but a file-only\n // message must still be sendable. `canSubmitAttachmentsOnly` widens that to a\n // file of ANY status, so an in-flight upload leaves the control live and the\n // host's handler decides what to do about it.\n const sendableFiles = canSubmitAttachmentsOnly\n ? pendingFiles\n : pendingFiles.filter((f) => f.status === 'ready')\n const hasSendable = text.trim().length > 0 || sendableFiles.length > 0 || Boolean(selectedSkillId)\n // Streaming blocks a send unless the host queues turns. The button still\n // shows Stop while streaming, so `canSubmitWhileBusy` opens Enter, not a\n // second visible control.\n const sendBlockedByStream = isStreaming && !canSubmitWhileBusy\n const canSend = hasSendable && !sendBlockedByStream && !disabled && !sendDisabled\n\n const [failedSend, setFailedSend] = useState<FailedSend | null>(null)\n\n // The draft comes back only when the box is still empty. If the user typed a\n // replacement while the send was in flight, overwriting it would trade one\n // lost message for another — the unsent text is held in the notice instead,\n // where Retry can send it without touching what they typed.\n const failSend = useCallback(\n (error: unknown, draft: string, trimmed: string, parts: ComposerFilePart[], caret: { start: number; end: number }) => {\n const message = sendFailureText(error, sendFailureMessage)\n const restored = textRef.current === ''\n if (restored) {\n const el = textareaRef.current\n setText(draft)\n if (el && el.value === draft) {\n // A handler that rejected SYNCHRONOUSLY did so inside the same event\n // as the clear, so React collapses clear+restore into no state change\n // at all — no re-render is coming and the effect below will never\n // fire. Place the caret now rather than stranding the pending ref.\n el.focus()\n el.setSelectionRange(Math.min(caret.start, draft.length), Math.min(caret.end, draft.length))\n } else {\n restoreCaretRef.current = { text: draft, start: caret.start, end: caret.end }\n }\n }\n setFailedSend({ message, text: draft, trimmed, parts, restored })\n onSendFailed?.({ message, text: draft, parts, error, restored })\n },\n [onSendFailed, sendFailureMessage, setText],\n )\n\n // Hand the message to the host and watch the outcome. The input has already\n // been cleared by the caller — this only decides whether it comes back.\n const dispatchSend = useCallback(\n (draft: string, trimmed: string, parts: ComposerFilePart[], caret: { start: number; end: number }) => {\n let outcome: ComposerSendResult\n try {\n outcome = onSendParts ? onSendParts(trimmed, parts) : onSend?.(trimmed)\n } catch (error) {\n failSend(error, draft, trimmed, parts, caret)\n return\n }\n if (isPromise(outcome)) {\n void outcome.then(\n (settled) => {\n if (isRejectedOutcome(settled)) failSend(settled, draft, trimmed, parts, caret)\n },\n (error: unknown) => failSend(error, draft, trimmed, parts, caret),\n )\n return\n }\n if (isRejectedOutcome(outcome)) failSend(outcome, draft, trimmed, parts, caret)\n },\n [onSend, onSendParts, failSend],\n )\n\n const send = useCallback(() => {\n const trimmed = text.trim()\n if (sendBlockedByStream || disabled || sendDisabled) return\n const readyFiles = pendingFiles.filter((f) => f.status === 'ready')\n const sendable = canSubmitAttachmentsOnly ? pendingFiles : readyFiles\n if (!trimmed && sendable.length === 0 && !selectedSkillId) return\n // `canSubmitAttachmentsOnly` keeps the control live while a file is staged,\n // but a turn carrying no text and no file the host can deliver must not go\n // out: it would arrive empty and the attachment would be lost. Refuse it\n // here and say why, rather than dispatching and trusting every host to\n // re-derive the same check.\n if (!trimmed && readyFiles.length === 0 && pendingFiles.length > 0) {\n const message =\n attachmentsNotReadyMessage ??\n (pendingFiles.some((f) => f.status === 'error')\n ? 'Retry or remove the failed attachment before sending.'\n : 'Wait for the attachment to finish uploading.')\n setFailedSend({ message, text: '', trimmed: '', parts: [], restored: true })\n return\n }\n // Only a parts-aware send carries parts; `onSend`'s files travel through the\n // host's own `pendingFiles`, so its failure payload names none. Parts come\n // from READY files whatever `canSubmitAttachmentsOnly` says — an unfinished\n // upload has no part to send.\n const parts = onSendParts\n ? readyFiles.filter((f) => f.part).map((f) => f.part as ComposerFilePart)\n : []\n const el = textareaRef.current\n const caret = { start: el?.selectionStart ?? text.length, end: el?.selectionEnd ?? text.length }\n setFailedSend(null)\n setText('')\n textRef.current = ''\n dispatchSend(text, trimmed, parts, caret)\n }, [\n text,\n sendBlockedByStream,\n disabled,\n sendDisabled,\n canSubmitAttachmentsOnly,\n attachmentsNotReadyMessage,\n onSendParts,\n pendingFiles,\n selectedSkillId,\n setText,\n dispatchSend,\n ])\n\n // Re-send the message the notice is holding. Reached only when the draft was\n // NOT restored (the restored path leaves the text in the box, where Send is\n // the affordance), so it never competes with the primary control.\n const retryFailedSend = useCallback(() => {\n const failure = failedSend\n if (!failure || sendBlockedByStream || disabled || sendDisabled) return\n setFailedSend(null)\n const caret = { start: failure.text.length, end: failure.text.length }\n dispatchSend(failure.text, failure.trimmed, failure.parts, caret)\n }, [failedSend, sendBlockedByStream, disabled, sendDisabled, dispatchSend])\n\n // ── '/' commands ─────────────────────────────────────────────────────────\n // General commands exist only while the WHOLE draft is one leading slash\n // token (`/`, `/mod`); `/skill <query>` also searches eligible skills. Esc\n // or an outside click dismisses for the CURRENT token only, so continued\n // typing reopens the menu instead of leaving it permanently suppressed.\n const slashPanelRef = useRef<HTMLDivElement>(null)\n const cardRef = useRef<HTMLDivElement>(null)\n const slashListId = useId()\n const [mentionSuggestionOpen, setMentionSuggestionOpen] = useState(false)\n const [slashActive, setSlashActive] = useState(0)\n const [slashDismissedFor, setSlashDismissedFor] = useState<string | null>(null)\n // General commands retain their textarea-only contract. Eligible skills\n // deliberately share the menu in both input modes; the rich input forwards\n // its keyboard events below. `/skill <query>` filters only skills.\n const hasSkills = Boolean(onSelectedSkillChange && eligibleSkills.length > 0)\n const commandToken = !mention && slashCommands?.length ? /^\\/(\\S*)$/.exec(text)?.[1] : undefined\n const skillToken = hasSkills ? /^\\/(\\S*)$/.exec(text)?.[1] : undefined\n const skillInvocation = hasSkills ? /^\\/skill(?:\\s+(.*))?$/i.exec(text) : null\n const skillQuery = skillInvocation ? (skillInvocation[1] ?? '') : undefined\n const slashToken = skillQuery ?? skillToken ?? commandToken\n const slashOpen = slashToken !== undefined && text !== slashDismissedFor && !mentionSuggestionOpen\n const slashItems = useMemo<CommandPaletteItem[]>(\n () => [\n ...(!mention ? slashCommands ?? [] : []).map((command) => ({\n id: `command:${command.name}`,\n group: 'Commands',\n label: `/${command.name}`,\n description: command.description,\n keywords: [command.name, command.description],\n })),\n ...(onSelectedSkillChange ? eligibleSkills : []).map((skill) => ({\n id: `skill:${skill.id}`,\n group: 'Skills',\n label: `/skill ${skill.name}`,\n description: skill.description,\n keywords: [skill.id, skill.name, skill.description],\n })),\n ],\n [slashCommands, mention, eligibleSkills, onSelectedSkillChange],\n )\n const slashFiltered = useMemo(\n () => {\n if (slashToken === undefined) return []\n const items = skillQuery !== undefined ? slashItems.filter((item) => item.group === 'Skills') : slashItems\n return filterCommandPaletteItems(items, slashToken)\n },\n [slashItems, slashToken, skillQuery],\n )\n const slashActiveIndex = slashFiltered.length === 0 ? 0 : Math.min(slashActive, slashFiltered.length - 1)\n\n useEffect(() => {\n setSlashActive(0)\n }, [slashToken])\n\n useEffect(() => {\n if (!slashOpen) return\n document\n .getElementById(`${slashListId}-${slashActiveIndex}`)\n ?.scrollIntoView?.({ block: 'nearest' })\n }, [slashOpen, slashActiveIndex, slashListId])\n\n useEffect(() => {\n if (!slashOpen) return\n function onMouseDown(e: MouseEvent) {\n const target = e.target as Node\n if (cardRef.current?.contains(target)) return\n if (slashPanelRef.current?.contains(target)) return\n setSlashDismissedFor(textRef.current)\n }\n document.addEventListener('mousedown', onMouseDown)\n return () => document.removeEventListener('mousedown', onMouseDown)\n }, [slashOpen])\n\n const pickSlash = useCallback(\n (id: string) => {\n // A pick consumes the current slash invocation: clear the box, then\n // report a typed skill or run the host's general command.\n setText('')\n setSlashDismissedFor(null)\n if (id.startsWith('skill:')) {\n const skill = eligibleSkills.find((item) => item.id === id.slice('skill:'.length))\n if (skill) onSelectedSkillChange?.(skill)\n } else if (id.startsWith('command:')) {\n slashCommands?.find((command) => command.name === id.slice('command:'.length))?.run()\n }\n },\n [slashCommands, eligibleSkills, onSelectedSkillChange, setText],\n )\n\n const handleSlashKeyDown = (e: Pick<globalThis.KeyboardEvent, 'key' | 'shiftKey' | 'preventDefault'>): boolean => {\n if (slashOpen) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n if (slashFiltered.length > 0) setSlashActive((slashActiveIndex + 1) % slashFiltered.length)\n return true\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n if (slashFiltered.length > 0)\n setSlashActive((slashActiveIndex - 1 + slashFiltered.length) % slashFiltered.length)\n return true\n }\n if ((e.key === 'Enter' && !e.shiftKey) || e.key === 'Tab') {\n const item = slashFiltered[slashActiveIndex]\n if (item) {\n e.preventDefault()\n pickSlash(item.id)\n return true\n }\n // No command matched — fall through and let Enter send the raw text.\n }\n if (e.key === 'Escape') {\n e.preventDefault()\n setSlashDismissedFor(text)\n return true\n }\n }\n return false\n }\n\n const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {\n // Respect IME composition — Enter commits the candidate, it doesn't send.\n if (e.nativeEvent.isComposing) return\n if (handleSlashKeyDown(e)) return\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n send()\n }\n }\n\n // Every route a file can arrive by ends here: apply `accept`, report what it\n // removed, and hand `onAttach` only what passed. A batch the filter left\n // untouched is forwarded as the browser's own `FileList`; one it changed is\n // rebuilt, since `onAttach` takes a `FileList` and only a `DataTransfer` can\n // produce one.\n const deliverFiles = useCallback(\n (files: File[], original: FileList) => {\n if (!onAttach || files.length === 0) return\n const { accepted, rejected } = filterAcceptedFiles(files, accept)\n if (rejected.length > 0) onRejectFiles?.(rejected)\n if (accepted.length === 0) return\n const unchanged =\n accepted.length === original.length && accepted.every((file, i) => file === original[i])\n if (unchanged) {\n onAttach(original)\n return\n }\n const transfer = new DataTransfer()\n for (const file of accepted) transfer.items.add(file)\n onAttach(transfer.files)\n },\n [onAttach, onRejectFiles, accept],\n )\n\n const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {\n // Re-filter: a picker dialog lets the user override `accept` with\n // \"All Files\", so the attribute alone does not hold the gate.\n if (e.target.files?.length) deliverFiles(Array.from(e.target.files), e.target.files)\n e.target.value = ''\n }\n\n // One paste core for both input modes. Returns true when files were the\n // payload — the caller then suppresses the default text paste even when\n // every file is refused, so a rejection never half-pastes stray text.\n // The staged names go in alongside the count: the queue is the host's and\n // can outlive this mount, so the count alone could hand the next paste a\n // name the queue already holds.\n const ingestPastedFiles = (clipboardFiles: FileList): boolean => {\n if (!onAttach || clipboardFiles.length === 0) return false\n const { files, nextIndex } = renamePastedImages(\n Array.from(clipboardFiles),\n pastedImageCount.current,\n pendingFiles.map((f) => f.name),\n )\n pastedImageCount.current = nextIndex\n deliverFiles(files, clipboardFiles)\n return true\n }\n\n const handlePaste = (e: ClipboardEvent<HTMLTextAreaElement>) => {\n const clipboardFiles = e.clipboardData?.files\n if (!clipboardFiles || clipboardFiles.length === 0) return\n if (ingestPastedFiles(clipboardFiles)) e.preventDefault()\n }\n\n const handleFolderChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files?.length) (onAttachFolder ?? onAttach)?.(e.target.files)\n e.target.value = ''\n }\n\n const handleDragEnter = useCallback((e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current++\n if (e.dataTransfer?.types.includes('Files')) setDragOver(true)\n }, [])\n\n const handleDragLeave = useCallback((e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current--\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragOver(false)\n }\n }, [])\n\n const handleDragOver = useCallback((e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'\n }, [])\n\n const handleDrop = useCallback(\n (e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current = 0\n setDragOver(false)\n const files = e.dataTransfer?.files\n if (files?.length) deliverFiles(Array.from(files), files)\n },\n [deliverFiles],\n )\n\n const folderChips = pendingFiles.filter((f) => f.kind === 'folder')\n const fileChips = pendingFiles.filter((f) => f.kind !== 'folder')\n // `above` is the only placement that takes controls OUT of the card, so it is\n // the only one matched exactly; everything else falls to inline. That keeps a\n // retired value (this prop used to accept `footer` for the same placement) or a\n // typo rendering the controls somewhere rather than nowhere — dropping them\n // silently is the one outcome with no recovery for the reader.\n const showAbove = controls != null && controlsPlacement === 'above'\n const showInline = controls != null && !showAbove\n\n // One floor for both input modes, so the mention editor and the textarea\n // cannot disagree about the empty-composer height.\n const inputMinHeight = minRows * LINE_HEIGHT + TEXTAREA_PADDING_Y\n // Keep one basic input for textarea mode and the rich editor's loading\n // state. A slow or hung lazy chunk must not leave a visible dead composer.\n // The controlled draft transfers into the rich editor when it loads.\n // Both render sites stay inside the card's `focusFieldWithin` indicator.\n const textareaInput = (\n <textarea\n ref={textareaRef}\n value={text}\n onChange={(e) => setText(e.target.value)}\n onKeyDown={handleKeyDown}\n onPaste={onAttach ? handlePaste : undefined}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n rows={minRows}\n style={{ minHeight: inputMinHeight, maxHeight }}\n aria-label=\"Message input\"\n data-composer-input\n className=\"w-full resize-none bg-transparent px-1.5 py-1 text-base leading-6 text-foreground placeholder:text-muted-foreground disabled:opacity-50\"\n />\n )\n\n return (\n <div\n className={`relative ${className ?? ''}`}\n onFocus={onFocusWithin}\n onDragEnter={onAttach ? handleDragEnter : undefined}\n onDragLeave={onAttach ? handleDragLeave : undefined}\n onDragOver={onAttach ? handleDragOver : undefined}\n onDrop={onAttach ? handleDrop : undefined}\n >\n {dragOver && (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-card\">\n <div className=\"text-center\">\n <span className=\"mx-auto mb-2 flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary\">\n <UploadGlyph className=\"h-5 w-5\" />\n </span>\n <p className=\"text-sm font-semibold text-foreground\">{dropTitle}</p>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">{dropDescription}</p>\n </div>\n </div>\n )}\n\n {showAbove && <div className=\"mb-1.5 flex flex-wrap items-center gap-1.5 px-1\">{controls}</div>}\n\n {dictateError && (\n <div\n role=\"alert\"\n data-testid=\"composer-dictate-error\"\n className=\"mb-2 flex items-start gap-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\"\n >\n <span className=\"min-w-0 flex-1\">{dictateError}</span>\n <button\n type=\"button\"\n aria-label=\"Dismiss dictation error\"\n onClick={() => setDictateError(null)}\n className=\"shrink-0 font-medium underline-offset-2 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n Dismiss\n </button>\n </div>\n )}\n\n {failedSend && (\n <div\n role=\"alert\"\n data-testid=\"composer-send-error\"\n className=\"mb-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\"\n >\n <div className=\"flex items-start gap-2\">\n <span className=\"min-w-0 flex-1\">{failedSend.message}</span>\n <button\n type=\"button\"\n aria-label=\"Dismiss send error\"\n onClick={() => setFailedSend(null)}\n className=\"shrink-0 font-medium underline-offset-2 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n Dismiss\n </button>\n </div>\n {/* The draft is only held here when it could NOT go back in the box —\n the user typed a replacement. Showing the bytes is what makes the\n message recoverable by hand even if Retry keeps failing. */}\n {!failedSend.restored && (\n <div className=\"mt-1.5\">\n <p\n data-testid=\"composer-unsent-draft\"\n className=\"max-h-20 overflow-y-auto whitespace-pre-wrap rounded-lg border border-destructive/30 bg-card px-2 py-1 text-foreground\"\n >\n {failedSend.text}\n </p>\n <button\n type=\"button\"\n aria-label=\"Retry sending the unsent message\"\n onClick={retryFailedSend}\n disabled={sendBlockedByStream || disabled}\n className=\"mt-1.5 font-medium underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n Retry\n </button>\n </div>\n )}\n </div>\n )}\n\n {selectedSkillId && (\n <div aria-label=\"Selected skill\" className=\"mb-2 flex min-w-0 flex-wrap gap-1.5\">\n <span className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs text-primary\">\n <span className=\"min-w-0 truncate\">Skill: {eligibleSkills.find((skill) => skill.id === selectedSkillId)?.name ?? selectedSkillId}</span>\n {onSelectedSkillChange && (\n <button\n type=\"button\"\n aria-label=\"Remove selected skill\"\n onClick={() => onSelectedSkillChange(null)}\n className=\"shrink-0 rounded p-0.5 text-primary/70 transition hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <CloseGlyph className=\"h-3 w-3\" />\n </button>\n )}\n </span>\n </div>\n )}\n\n {contextItems.length > 0 && (\n <div aria-label=\"Message context\" className=\"mb-2 flex min-w-0 flex-wrap gap-1.5\">\n {contextItems.map((item) => (\n <span\n key={item.id}\n className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs text-primary\"\n >\n {item.icon && (\n <span className=\"shrink-0\" aria-hidden>\n {item.icon}\n </span>\n )}\n <span className=\"min-w-0 truncate\">{item.label}</span>\n {item.onRemove && (\n <button\n type=\"button\"\n aria-label={`Remove context ${item.label}`}\n onClick={item.onRemove}\n className=\"shrink-0 rounded p-0.5 text-primary/70 transition hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <CloseGlyph className=\"h-3 w-3\" />\n </button>\n )}\n </span>\n ))}\n </div>\n )}\n\n {pendingFiles.length > 0 && (\n <div className=\"mb-2 flex flex-wrap gap-1.5\">\n {[...folderChips, ...fileChips].map((f) => {\n const isError = f.status === 'error'\n return (\n <span\n key={f.id}\n title={isError ? f.errorMessage : undefined}\n className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs ${\n isError\n ? 'border-destructive/40 text-destructive'\n : 'border-border bg-secondary text-foreground'\n } ${f.status === 'pending' ? 'opacity-60' : ''}`}\n >\n {/* A thumbnail identifies a pasted screenshot that the\n auto-generated name cannot. Folders never have one. */}\n {f.kind !== 'folder' && f.previewUrl ? (\n <img src={f.previewUrl} alt=\"\" className=\"h-8 w-8 shrink-0 rounded object-cover\" />\n ) : f.kind === 'folder' ? (\n <FolderGlyph className=\"h-3 w-3 shrink-0\" />\n ) : (\n <PaperclipGlyph className=\"h-3 w-3 shrink-0\" />\n )}\n <span className=\"max-w-[150px] truncate\">{f.name}</span>\n {f.fileCount !== undefined && <span className=\"text-muted-foreground\">({f.fileCount})</span>}\n {f.status === 'uploading' && (\n <span className=\"h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent\" />\n )}\n {isError && f.errorMessage && (\n <span className=\"max-w-[150px] truncate text-destructive/80\">{f.errorMessage}</span>\n )}\n {isError && onRetryFile && (\n <button\n type=\"button\"\n aria-label={`Retry upload ${f.name}`}\n onClick={() => onRetryFile(f.id)}\n className=\"rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <RetryGlyph className=\"h-3 w-3\" />\n </button>\n )}\n {onRemoveFile && (\n <button\n type=\"button\"\n aria-label={`Remove ${f.name}`}\n onClick={() => onRemoveFile(f.id)}\n className=\"rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <CloseGlyph className=\"h-3 w-3\" />\n </button>\n )}\n </span>\n )\n })}\n </div>\n )}\n\n {/* Two rows inside one card: the message gets the full width, and every\n affordance that acts on it — attach, the controls slot, send — sits on\n its own row beneath. A single row would make the textarea share its\n line with the buttons, which is what squeezed the input and pushed the\n controls out of the card in the first place. */}\n {/* `focusFieldWithin` makes the card a field: with brand's interaction\n tokens it carries the well fill (`--well`), field border, hover glow\n and focus ring. Under a brand without the ladder, tokens.css sets\n `--well` to the card color. */}\n <div\n ref={cardRef}\n data-testid=\"composer-card\"\n className={`flex flex-col gap-1.5 rounded-2xl border bg-[var(--well,var(--color-card))] px-3 py-2.5 ${focusFieldWithin} ${\n floating ? 'shadow-raised' : ''\n }`}\n >\n {mention ? (\n // The basic textarea stays usable through download, initialization,\n // and failure.\n <LazyLoadBoundary renderFailure={() => (\n <>\n {textareaInput}\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive\"\n >\n Mentions are unavailable. You can still send.\n </div>\n </>\n )}>\n <Suspense fallback={textareaInput}>\n <MentionEditor\n value={text}\n onChange={setText}\n onSubmit={send}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n minHeight={inputMinHeight}\n maxHeight={maxHeight}\n mention={mention}\n fallback={textareaInput}\n registerFocus={registerRichFocus}\n onComposerKeyDown={handleSlashKeyDown}\n onMentionMenuOpenChange={setMentionSuggestionOpen}\n slashListId={slashOpen ? slashListId : undefined}\n slashActiveId={slashFiltered[slashActiveIndex] ? `${slashListId}-${slashActiveIndex}` : undefined}\n onPasteFiles={onAttach ? ingestPastedFiles : undefined}\n />\n </Suspense>\n </LazyLoadBoundary>\n ) : (\n // The card is the field (`focusFieldWithin`: well fill, field border,\n // hover glow, focus ring), so tokens.css clears the transparent\n // input's own outline (`[data-composer-input]:focus-visible`).\n textareaInput\n )}\n\n {/* The row wraps rather than overlaps. The controls slot never shrinks\n below its widest control (`min-w-min`), so when the controls and the\n actions group cannot share one line the actions group moves to its\n own line, right-aligned, instead of being drawn over a squeezed\n control. With `min-w-0` here a single Plan chip was compressed to\n nothing at 390px and painted underneath a trailing model picker. */}\n <div className=\"flex flex-wrap items-end gap-2\">\n {onAttach && (\n <>\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={disabled}\n aria-label=\"Attach files\"\n title=\"Attach files\"\n className=\"shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <PaperclipGlyph className=\"h-4 w-4\" />\n </button>\n <input ref={fileInputRef} type=\"file\" multiple className=\"hidden\" accept={accept} onChange={handleFileChange} />\n </>\n )}\n {onAttachFolder && (\n <>\n <button\n type=\"button\"\n onClick={() => folderInputRef.current?.click()}\n disabled={disabled}\n aria-label=\"Attach folder\"\n title=\"Attach folder\"\n className=\"shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <FolderGlyph className=\"h-4 w-4\" />\n </button>\n {/* webkitdirectory is non-standard but widely supported for folder picks. */}\n <input\n ref={folderInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={handleFolderChange}\n {...({ webkitdirectory: '' } as Record<string, string>)}\n />\n </>\n )}\n\n {/* The controls take the row's slack and wrap onto a second line when\n a long picker set outgrows it. This slot must never establish an\n overflow box: a control owns its popover (ModelPicker, EffortPicker)\n and anchors it absolutely to itself, so a scroll/clip box here traps\n a 400px-tall list inside a ~34px row — the list renders and is never\n visible — and the scroll offset that comes with it cuts the trigger's\n own left edge. Growing a second line is the cost of controls that\n stay operable. Rendered even when empty so Send stays right-aligned. */}\n <div\n data-testid=\"composer-controls\"\n className=\"flex min-w-min flex-1 flex-wrap items-center gap-1.5\"\n >\n {showInline && controls}\n </div>\n\n {/* Trailing content is the controls slot's SIBLING, not its content:\n the slot is where a picker set is allowed to wrap, and a meter or\n a status line put inside it would be pushed onto the second line\n by the very pickers it reports on. Trailing content, dictation\n and Send travel together as one group: when the row wraps they\n move to the next line right-aligned, so Send never lands alone at\n the start of a line. The group wraps inside itself too: when a\n trailing picker and Send cannot share even their own line (a\n 320px phone), Send drops below the picker, right-aligned, instead\n of being drawn over it. */}\n <div data-testid=\"composer-actions\" className=\"ml-auto flex min-w-0 max-w-full flex-wrap items-end justify-end gap-2\">\n {trailing && (\n <div data-testid=\"composer-trailing\" className=\"flex min-w-0 flex-wrap items-center justify-end gap-1.5\">\n {trailing}\n </div>\n )}\n {/* Dictation sits beside Send: it produces input, like typing. The\n button renders only when the host takes audio AND the browser can\n record — a dead mic is worse than no mic. While recording, the\n elapsed seconds (not the pulsing dot, which reduced motion\n collapses) are the signal, and the stop control is never\n disabled: a `disabled` flip mid-capture must not strand the mic. */}\n {onDictate && dictation.supported ? (\n dictation.recording ? (\n <div className=\"flex shrink-0 items-center gap-1.5\">\n <span aria-hidden=\"true\" className=\"h-2 w-2 animate-pulse rounded-full bg-destructive\" />\n <span\n aria-hidden=\"true\"\n data-testid=\"composer-dictate-elapsed\"\n className=\"text-xs tabular-nums text-muted-foreground\"\n >\n {formatDictationElapsed(dictation.elapsedSeconds)}\n </span>\n <span role=\"status\" className=\"sr-only\">\n Recording\n </span>\n <button\n type=\"button\"\n onClick={dictation.stop}\n aria-label=\"Stop dictation\"\n title=\"Stop dictation\"\n className=\"shrink-0 rounded-lg p-2 text-destructive transition hover:bg-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <StopGlyph className=\"h-4 w-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={dictation.start}\n disabled={disabled}\n aria-label=\"Dictate message\"\n title=\"Dictate message\"\n className=\"shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MicGlyph className=\"h-4 w-4\" />\n </button>\n )\n ) : null}\n\n {isStreaming ? (\n sendVariant === 'icon' ? (\n <button\n type=\"button\"\n onClick={onCancel}\n aria-label=\"Stop response\"\n title=\"Stop\"\n className=\"inline-flex size-[var(--control-height-sm,2rem)] shrink-0 items-center justify-center rounded-full border border-border bg-transparent text-foreground transition hover:bg-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <StopGlyph className=\"h-3 w-3\" />\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={onCancel}\n aria-label=\"Stop response\"\n className=\"inline-flex h-[var(--control-height-sm,2rem)] shrink-0 items-center gap-1.5 rounded-full bg-destructive/15 px-3.5 text-sm font-medium text-destructive transition hover:bg-destructive/25 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50\"\n >\n <StopGlyph className=\"h-3.5 w-3.5\" />\n <span>Stop</span>\n </button>\n )\n ) : sendVariant === 'icon' ? (\n <button\n type=\"button\"\n onClick={send}\n disabled={!canSend}\n aria-label={sendLabel}\n title={sendLabel}\n className={`inline-flex size-[var(--control-height-sm,2rem)] shrink-0 items-center justify-center rounded-full ${sendTone === 'primary' ? 'bg-primary text-primary-foreground' : 'bg-foreground text-background'} transition ${sendTone === 'primary' ? 'hover:bg-primary/90' : 'hover:opacity-90'} disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card`}\n >\n <ArrowUpGlyph className=\"h-4 w-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={send}\n disabled={!canSend}\n aria-label={sendLabel}\n className=\"inline-flex h-[var(--control-height-sm,2rem)] shrink-0 items-center gap-1.5 rounded-full bg-primary px-3.5 text-sm font-medium text-primary-foreground shadow-sm transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card\"\n >\n <SendGlyph className=\"h-3.5 w-3.5\" />\n <span>{sendLabel}</span>\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* The slash menu ports through PopoverSurface like every canonical\n popover: the composer docks inside horizontally scrolling rails, and\n an in-place panel there is a panel the host clips away. It anchors\n to the current input card and opens above. Focus never leaves the input —\n rows are mousedown-swallowed so a click can't blur it. */}\n <PopoverSurface\n open={slashOpen}\n id={slashListId}\n role=\"listbox\"\n triggerRef={mention ? cardRef : textareaRef}\n panelRef={slashPanelRef}\n className={`w-80 overflow-y-auto rounded-xl border border-card-edge bg-popover p-1 ${OVERLAY_SHADOW}`}\n >\n <div className=\"max-h-80 overflow-y-auto overscroll-contain\">\n {slashFiltered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">\n {skillQuery !== undefined ? 'No matching skills' : hasSkills ? 'No matching commands or skills' : 'No matching commands'}\n </div>\n )}\n {slashFiltered.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === slashActiveIndex}\n id={`${slashListId}-${index}`}\n onMouseDown={(e) => e.preventDefault()}\n onMouseMove={() => setSlashActive(index)}\n onClick={() => pickSlash(item.id)}\n className={`flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${\n index === slashActiveIndex ? 'bg-accent' : 'hover:bg-accent'\n }`}\n >\n <span className=\"shrink-0 font-medium text-foreground\">{item.label}</span>\n <span className=\"truncate text-xs text-muted-foreground\">{item.description}</span>\n </button>\n ))}\n </div>\n </PopoverSurface>\n\n {focusShortcut && (\n // A touch screen has no keyboard to press the shortcut on.\n <div className=\"mt-1.5 flex justify-end px-1 [@media(pointer:coarse)]:hidden\">\n <span className=\"text-xs text-muted-foreground\">\n <kbd className=\"rounded border border-border bg-background px-1 py-0.5 text-xs\">{IS_APPLE_PLATFORM ? 'Cmd' : 'Ctrl'}</kbd>\n <kbd className=\"ml-0.5 rounded border border-border bg-background px-1 py-0.5 text-xs\">L</kbd>\n <span className=\"ml-1\">to focus</span>\n </span>\n </div>\n )}\n </div>\n )\n}\n","/**\n * `useComposerAttachments` — the composer's staged-upload lifecycle: validate\n * selected/dropped/pasted files against the shared limits (the SAME\n * `sniffBinary`/`checkAttachmentType`/size-cap vocabulary the store-backed\n * upload route enforces server-side, `../chat-routes/attachment-validation`\n * + `../chat-routes/binary-sniff`), upload each accepted file with one POST\n * request per file (so a single failure never poisons the batch), and track\n * every file's status so a host composer can render chips and gate sending.\n *\n * Ported from gtm-agent's `src/components/composer-attachments.tsx`\n * (gtm#584/#592/#593 hardened the sniff gate and batch semantics this leans\n * on), de-gtm-ified:\n * - the hardcoded `/api/vault/upload?workspaceId=` URL becomes\n * `uploadUrl`/`buildUploadRequest` (the latter wins — it hands back both\n * the URL and a `RequestInit` override, e.g. an auth header);\n * - `sonner` toasts become `onReject` (client pre-validation, never hits the\n * network) and `onError` (a request that reached the server and failed);\n * - the `accept`-string gate comes from `./composer-file-accept`, the one\n * matcher `ChatComposer` also funnels its picker/drop/paste ingress\n * through, so both ends of the staging path admit the same files;\n * - the response is expected to be `{ files: ChatAttachmentInput[] }` (full\n * server-authoritative descriptors — size/mediaType/kind — not gtm's\n * `{path, name}`), so `references` is a verbatim pass-through with no\n * client recompute;\n * - `workspaceId`'s truthiness gate becomes `enabled` (default `true`).\n *\n * Import-free beyond React + the browser-safe `/chat-routes` validation core:\n * this module ships through `/web-react` into client bundles\n * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here\n * may reach a Node builtin, `sandbox-ui`, or an engine package.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './chat-stream'\nimport type { ComposerFile } from './chat-composer'\nimport { acceptRejectionReason, isAcceptedFileType } from './composer-file-accept'\nimport {\n ALLOWED_ATTACHMENT_SNIFFED_MIMES,\n ATTACHMENT_ACCEPT,\n ATTACHMENT_MAX_COUNT,\n MAX_ATTACHMENT_TOTAL_BYTES,\n MAX_BINARY_ATTACHMENT_BYTES,\n MAX_TEXT_ATTACHMENT_BYTES,\n attachmentSizeErrorMessage,\n attachmentTotalSizeErrorMessage,\n checkAttachmentType,\n sanitizeAttachmentFileName,\n} from '../chat-routes/attachment-validation'\nimport { sniffBinary } from '../chat-routes/binary-sniff'\n\n// The gate's defaults, so a host widening its composer gate starts from the\n// route's own values instead of a copy.\nexport {\n ALLOWED_ATTACHMENT_SNIFFED_MIMES,\n ATTACHMENT_ACCEPT,\n OOXML_SNIFFED_MIMES,\n} from '../chat-routes/attachment-validation'\n\n/** One staged file and its upload lifecycle. `file` is retained so a failed\n * upload can be retried without re-selecting; `previewUrl` is an object URL\n * for image thumbnails and must be revoked when the entry leaves the queue.\n * `reference` is the server's authoritative descriptor once the upload\n * lands — stored verbatim, never recomputed client-side. */\ninterface StagedAttachment {\n id: string\n file: File\n name: string\n size: number\n status: 'pending' | 'uploading' | 'ready' | 'error'\n reference?: ChatAttachmentInput\n previewUrl?: string\n errorMessage?: string\n}\n\n/** Define options for configuring file upload behavior and handling in a composer component */\nexport interface UseComposerAttachmentsOptions {\n /** Simple upload target: every file POSTs here. Ignored when\n * `buildUploadRequest` is provided. */\n uploadUrl?: string\n /** Full request-building seam (auth headers, per-file routing, …) — wins\n * over `uploadUrl` when both are set. */\n buildUploadRequest?: (args: { file: File; name: string; form: FormData }) => {\n url: string\n init?: Omit<RequestInit, 'body' | 'signal'>\n }\n /** Client pre-validation rejections — a file that never reaches the\n * network (bad type, over a size cap, over count, disallowed kind). */\n onReject?: (reason: string, file?: File) => void\n /** A file that reached the upload endpoint and failed (HTTP error,\n * transport error, malformed response). */\n onError?: (reason: string) => void\n limits?: {\n maxCount?: number\n maxBinaryBytes?: number\n /** Binary ceilings keyed by the content-sniffed mime, as the upload\n * route's `limits.maxBytesBySniffedMime`. */\n maxBytesBySniffedMime?: ReadonlyMap<string, number>\n maxTextBytes?: number\n maxTotalBytes?: number\n }\n /** Sniffed-mime allowlist, as the upload route's `allowedSniffedMimes`.\n * Default {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}. */\n allowedSniffedMimes?: ReadonlySet<string>\n /** How each file travels. `'form'` (default) posts multipart form data.\n * `'raw'` posts the file itself as the body, named by a `filename`\n * query parameter with its size in `X-Upload-Length`, which\n * `createAttachmentUploadRoute` streams to the store without holding the\n * file whole. */\n uploadBody?: 'form' | 'raw'\n /** Attachment kinds accepted, checked against the sniffed content's\n * mime. Default: both (`['image', 'file']` — i.e. no restriction). */\n allowedKinds?: ChatAttachmentKind[]\n /** `<input accept>`-style gate for the file picker/drop/paste path.\n * Default {@link ATTACHMENT_ACCEPT}. */\n accept?: string\n /** When `false`, `addFiles` rejects every call via `onReject` (and\n * `blockReason` explains why) instead of staging anything — the\n * replacement for gtm's `workspaceId`-truthiness gate (e.g. no workspace\n * loaded yet). Default `true`. */\n enabled?: boolean\n}\n\n/** Provide staged file chips, ready attachments, and methods to add, retry, or drop composer files */\nexport interface UseComposerAttachmentsResult {\n /** Chip models for `ChatComposer`'s `pendingFiles` prop, one per staged\n * file — `kind` is always `'file'` (agent-app's `ComposerFile.kind`\n * discriminates file-vs-folder chips, not attachment media type). */\n composerFiles: ComposerFile[]\n /** Ready-to-send attachment descriptors — only files whose upload\n * succeeded, straight from the server's response (no recompute). Feed\n * this into `ChatTurnRequestPayload.attachments`. */\n references: ChatAttachmentInput[]\n /** Validate + stage + upload the given files, one request per file. */\n addFiles: (files: File[] | FileList) => Promise<void>\n /** Re-upload a failed entry using its retained `File`. */\n retry: (id: string) => void\n /** Drop one staged entry, aborting its upload and revoking its preview. */\n removeAttachment: (id: string) => void\n /** Forget every staged entry (call after a successful send). */\n clear: () => void\n /** True while any file is still pending or uploading. */\n hasPending: boolean\n /** True while any file failed to upload. */\n hasError: boolean\n /** Why a send is blocked, or `null` when the queue is clean. */\n blockReason: string | null\n}\n\nfunction newId(): string {\n const cryptoObject = globalThis.crypto\n if (typeof cryptoObject?.randomUUID === 'function') return cryptoObject.randomUUID()\n return `att-${Date.now()}-${Math.random().toString(36).slice(2)}`\n}\n\n/** Suffix a name (`report.pdf` → `report-2.pdf`) until it's unused. The\n * server writes to a name-derived store path, so identical names would\n * overwrite. The suffix stays inside the store-path charset (see\n * `sanitizeAttachmentFileName`). Ported byte-for-byte from gtm's\n * `dedupeName`. */\nfunction dedupeName(name: string, taken: Set<string>): string {\n if (!taken.has(name)) return name\n const dot = name.lastIndexOf('.')\n const base = dot > 0 ? name.slice(0, dot) : name\n const ext = dot > 0 ? name.slice(dot) : ''\n let n = 2\n let candidate = `${base}-${n}${ext}`\n while (taken.has(candidate)) {\n n += 1\n candidate = `${base}-${n}${ext}`\n }\n return candidate\n}\n\n/** `image/*` → `'image'`, everything else → `'file'`. Deliberately\n * reimplemented here (not imported from `../chat-store/parts`, which pulls\n * the drizzle-adjacent `/chat-store` barrel): this module must stay reachable\n * from a browser bundle with only the `/chat-routes` validation core as a\n * dependency. */\nfunction kindForMime(mime: string): ChatAttachmentKind {\n return mime.startsWith('image/') ? 'image' : 'file'\n}\n\n/** Pull a human-readable message out of the upload endpoint's error body.\n * Ported from gtm's `parseUploadError`: handles both `{ error: string }`\n * (size/count/access errors) and `{ error: { message } }` (the\n * `createAttachmentUploadRoute` envelope, `{error:{code,message,path?}}`). */\nasync function parseUploadError(res: Response): Promise<string> {\n const detail = await res.json().catch(() => null)\n if (detail && typeof detail === 'object' && 'error' in detail) {\n const error = (detail as { error: unknown }).error\n if (typeof error === 'string' && error) return error\n if (error && typeof error === 'object' && 'message' in error) {\n const message = (error as { message: unknown }).message\n if (typeof message === 'string' && message) return message\n }\n }\n return `Upload failed (${res.status})`\n}\n\n/** Shown when neither `uploadUrl` nor `buildUploadRequest` is configured\n * while `enabled` — a product wiring bug, not a user-facing rejection, so it\n * lands each affected entry in `error` (with `onError`) rather than blocking\n * `addFiles` outright via `onReject`: the files still stage and can be\n * retried once the product fixes its config, instead of silently vanishing. */\nconst NO_UPLOAD_TARGET_MESSAGE = 'No upload destination configured (pass uploadUrl or buildUploadRequest)'\n\n/** The upload URL with the raw upload's `filename` query parameter set. */\nfunction withFilename(url: string, name: string): string {\n const [base, hash] = url.split('#', 2) as [string, string | undefined]\n const separator = base.includes('?') ? '&' : '?'\n return `${base}${separator}filename=${encodeURIComponent(name)}${hash === undefined ? '' : `#${hash}`}`\n}\n\n/** A raw upload's headers: the host's own, plus the declared size the route\n * needs before it reads the body. */\nfunction rawUploadHeaders(init: HeadersInit | undefined, size: number): Headers {\n const headers = new Headers(init)\n headers.set('Content-Type', 'application/octet-stream')\n headers.set('X-Upload-Length', String(size))\n return headers\n}\n\n/**\n * Owns the composer's attachment lifecycle: validate selected/dropped/pasted\n * files against the shared limits, upload each accepted file to the\n * product's store (one request per file), and track every file's status so\n * the composer can render chips and gate sending.\n *\n * Failures surface loud — a rejected file calls `onReject` and is never\n * uploaded; a failed upload calls `onError` and leaves an error chip the user\n * can retry or remove. `references` only ever contains files whose upload the\n * server actually confirmed.\n */\nexport function useComposerAttachments(\n options: UseComposerAttachmentsOptions,\n): UseComposerAttachmentsResult {\n // Latest options, read from inside stable callbacks — avoids re-creating\n // `addFiles`/`upload` (and therefore breaking referential stability for\n // effects a host might hang off them) every time a caller passes a fresh\n // options object literal.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const [staged, setStagedState] = useState<StagedAttachment[]>([])\n // Mirror of `staged` kept in lockstep so dedupe/aggregate-cap/abort read\n // current values synchronously (setState callbacks alone can't answer\n // \"what's staged right now\" mid-validation).\n const stagedRef = useRef<StagedAttachment[]>([])\n const controllersRef = useRef<Map<string, AbortController>>(new Map())\n\n // Post-unmount calls reduce to a React no-op setState; the refs they touch\n // die with the instance.\n const setStaged = useCallback(\n (updater: StagedAttachment[] | ((prev: StagedAttachment[]) => StagedAttachment[])) => {\n const next =\n typeof updater === 'function'\n ? (updater as (prev: StagedAttachment[]) => StagedAttachment[])(stagedRef.current)\n : updater\n stagedRef.current = next\n setStagedState(next)\n },\n [],\n )\n\n const upload = useCallback(\n async (id: string, file: File, name: string) => {\n const opts = optionsRef.current\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'uploading', errorMessage: undefined } : s)),\n )\n const controller = new AbortController()\n controllersRef.current.set(id, controller)\n const form = new FormData()\n form.append('file', file, name)\n\n const request = opts.buildUploadRequest\n ? opts.buildUploadRequest({ file, name, form })\n : opts.uploadUrl\n ? { url: opts.uploadUrl }\n : null\n\n if (!request) {\n setStaged((prev) =>\n prev.map((s) =>\n s.id === id ? { ...s, status: 'error', errorMessage: NO_UPLOAD_TARGET_MESSAGE } : s,\n ),\n )\n opts.onError?.(NO_UPLOAD_TARGET_MESSAGE)\n controllersRef.current.delete(id)\n return\n }\n\n try {\n const res = opts.uploadBody === 'raw'\n ? await fetch(withFilename(request.url, name), {\n method: 'POST',\n credentials: 'same-origin',\n ...request.init,\n headers: rawUploadHeaders(request.init?.headers, file.size),\n body: file,\n signal: controller.signal,\n })\n : await fetch(request.url, {\n method: 'POST',\n credentials: 'same-origin',\n ...request.init,\n body: form,\n signal: controller.signal,\n })\n if (!res.ok) {\n const message = await parseUploadError(res)\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n const data = (await res.json()) as { files?: ChatAttachmentInput[] }\n const uploaded = data.files?.[0]\n if (!uploaded) {\n const message = 'Upload returned no file'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'ready', reference: uploaded } : s)),\n )\n } catch (err) {\n if ((err as Error).name === 'AbortError') return // silent removal — see removeAttachment/clear\n const message =\n err instanceof Error && err.message ? err.message : 'Upload failed — check your connection'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n } finally {\n controllersRef.current.delete(id)\n }\n },\n [setStaged],\n )\n\n const addFiles = useCallback(\n async (files: File[] | FileList) => {\n const opts = optionsRef.current\n const enabled = opts.enabled ?? true\n if (!enabled) {\n opts.onReject?.('Attachments are disabled')\n return\n }\n\n const accept = opts.accept ?? ATTACHMENT_ACCEPT\n const maxCount = opts.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = opts.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\n const maxTextBytes = opts.limits?.maxTextBytes ?? MAX_TEXT_ATTACHMENT_BYTES\n const maxTotalBytes = opts.limits?.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const maxBytesBySniffedMime = opts.limits?.maxBytesBySniffedMime\n const allowedSniffedMimes = opts.allowedSniffedMimes ?? ALLOWED_ATTACHMENT_SNIFFED_MIMES\n const allowedKinds = opts.allowedKinds ?? (['image', 'file'] as ChatAttachmentKind[])\n\n const list = Array.isArray(files) ? files : Array.from(files)\n\n // Pass 1: accept-list, then count cap — accept first, and the count\n // checked against what is already staged plus what this batch has taken.\n const currentCount = stagedRef.current.length\n const countAccepted: File[] = []\n for (const file of list) {\n if (!isAcceptedFileType(file, accept)) {\n opts.onReject?.(acceptRejectionReason(file, accept), file)\n continue\n }\n if (currentCount + countAccepted.length >= maxCount) {\n opts.onReject?.(`\"${file.name}\" was not added — the ${maxCount}-file limit is already reached.`, file)\n continue\n }\n countAccepted.push(file)\n }\n\n // Pass 2: real content sniff + type gate + per-kind size cap +\n // allowed-kinds gate — the SAME checks the server enforces, so a\n // rejection never differs depending on which side classified the bytes\n // first. Nothing here ever reaches the network.\n const sizeAccepted: File[] = []\n for (const file of countAccepted) {\n const bytes = new Uint8Array(await file.arrayBuffer())\n const sniff = sniffBinary(bytes)\n const typeCheck = checkAttachmentType(file.name, sniff, allowedSniffedMimes)\n if (!typeCheck.succeeded) {\n opts.onReject?.(typeCheck.message, file)\n continue\n }\n const limit = !sniff.binary\n ? maxTextBytes\n : (sniff.mime ? maxBytesBySniffedMime?.get(sniff.mime) : undefined) ?? maxBinaryBytes\n if (file.size > limit) {\n opts.onReject?.(attachmentSizeErrorMessage(file.name, file.size, limit), file)\n continue\n }\n const mediaType = sniff.mime ?? file.type ?? ''\n const kind = kindForMime(mediaType)\n if (!allowedKinds.includes(kind)) {\n opts.onReject?.(`\"${file.name}\" is a ${kind} attachment, which isn't accepted here`, file)\n continue\n }\n sizeAccepted.push(file)\n }\n\n // Pass 3: running aggregate cap across this batch + everything already\n // staged (any status) — a partial batch can still land.\n const accepted: File[] = []\n let totalBytes = stagedRef.current.reduce((total, s) => total + s.size, 0)\n for (const file of sizeAccepted) {\n const nextTotalBytes = totalBytes + file.size\n if (nextTotalBytes > maxTotalBytes) {\n opts.onReject?.(attachmentTotalSizeErrorMessage(nextTotalBytes, maxTotalBytes), file)\n continue\n }\n accepted.push(file)\n totalBytes = nextTotalBytes\n }\n if (accepted.length === 0) return\n\n // Stage under the name the server will actually store, so the chip and\n // the message's attachment references never diverge.\n const taken = new Set(stagedRef.current.map((s) => s.name))\n const entries: StagedAttachment[] = accepted.map((file) => {\n const name = dedupeName(sanitizeAttachmentFileName(file.name), taken)\n taken.add(name)\n return {\n id: newId(),\n file,\n name,\n size: file.size,\n status: 'pending',\n previewUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n })\n setStaged((prev) => [...prev, ...entries])\n for (const entry of entries) void upload(entry.id, entry.file, entry.name)\n },\n [setStaged, upload],\n )\n\n const retry = useCallback(\n (id: string) => {\n const entry = stagedRef.current.find((s) => s.id === id)\n if (!entry) return\n void upload(entry.id, entry.file, entry.name)\n },\n [upload],\n )\n\n const removeAttachment = useCallback(\n (id: string) => {\n controllersRef.current.get(id)?.abort()\n controllersRef.current.delete(id)\n const entry = stagedRef.current.find((s) => s.id === id)\n if (entry?.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n setStaged((prev) => prev.filter((s) => s.id !== id))\n },\n [setStaged],\n )\n\n const clear = useCallback(() => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n setStaged([])\n }, [setStaged])\n\n useEffect(\n () => () => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n },\n [],\n )\n\n // `previewUrl` and `errorMessage` travel with the chip model. This hook is\n // the only place that knows either one — it mints the object URL and holds\n // the upload's failure text — so a projection that dropped them left the\n // composer rendering a red chip with no reason on it and no thumbnail for a\n // staged image.\n const composerFiles = useMemo<ComposerFile[]>(\n () =>\n staged.map((s) => ({\n id: s.id,\n name: s.name,\n size: s.size,\n kind: 'file' as const,\n status: s.status,\n previewUrl: s.previewUrl,\n errorMessage: s.errorMessage,\n })),\n [staged],\n )\n\n const references = useMemo<ChatAttachmentInput[]>(\n () =>\n staged\n .filter((s): s is StagedAttachment & { reference: ChatAttachmentInput } => s.status === 'ready' && !!s.reference)\n .map((s) => s.reference),\n [staged],\n )\n\n const hasPending = useMemo(\n () => staged.some((s) => s.status === 'pending' || s.status === 'uploading'),\n [staged],\n )\n const hasError = useMemo(() => staged.some((s) => s.status === 'error'), [staged])\n const enabled = options.enabled ?? true\n const blockReason = !enabled\n ? 'Attachments are disabled'\n : hasPending\n ? 'Attachments are still uploading'\n : hasError\n ? 'Remove failed attachments to send'\n : null\n\n return {\n composerFiles,\n references,\n addFiles,\n retry,\n removeAttachment,\n clear,\n hasPending,\n hasError,\n blockReason,\n }\n}\n","import { joinClasses } from './class-names'\n\n/**\n * The plan-mode toggle state an entry surface docks beside the composer. The\n * agent-identity pickers (model / harness / effort) speak the canonical\n * `AgentSessionControls` vocabulary — see \"UI chrome ownership (picker\n * canon)\" in AGENTS.md.\n */\nexport interface ComposerPlanModeSelection {\n enabled: boolean\n setEnabled: (next: boolean) => void\n saving?: boolean\n}\n\nexport interface ComposerModeControlsProps {\n /**\n * Plan-approval mode. Products pass it only when the selected backend can\n * propose a plan and wait for approval; omitted means nothing renders.\n */\n planMode?: ComposerPlanModeSelection\n}\n\n/** Checklist glyph, inline like every `/web-react` glyph — an icon-library\n * import here would turn that optional peer into a build-time requirement\n * for every consumer of the default surface. */\nfunction ListChecksGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m3 17 2 2 4-4M3 7l2 2 4-4M13 6h8M13 12h8M13 18h8\" />\n </svg>\n )\n}\n\n/**\n * The shared plan-mode toggle for the left side of an agent composer.\n * Plan mode is a behavioral switch, not part of the profile/backend/model/\n * thinking identity controls on the right.\n */\nexport function ComposerModeControls({ planMode }: ComposerModeControlsProps) {\n if (!planMode) return null\n\n return (\n <button\n type=\"button\"\n aria-pressed={planMode.enabled}\n disabled={planMode.saving}\n onClick={() => planMode.setEnabled(!planMode.enabled)}\n title=\"Plan mode: the agent proposes a plan you approve before it executes\"\n className={joinClasses(\n // Inset ring: this chip sits in a composer row that clips its overflow,\n // so an outward ring loses three of its four sides. Only the offset is\n // overridden — width and colour stay with the tokens.\n 'inline-flex h-[var(--control-height-sm,2rem)] items-center gap-1 rounded-full border px-2.5 text-sm transition-colors focus-visible:[outline-offset:-2px]',\n planMode.enabled\n ? 'border-primary/50 bg-primary/10 text-primary'\n : 'border-border bg-transparent text-muted-foreground hover:text-foreground',\n planMode.saving && 'opacity-60',\n )}\n >\n <ListChecksGlyph className=\"h-3.5 w-3.5\" />\n Plan\n </button>\n )\n}\n","import { useState, type ReactNode } from 'react'\nimport { ChatComposer } from './chat-composer'\nimport { ATTACHMENT_ACCEPT, useComposerAttachments } from './use-composer-attachments'\nimport type { ComposerFileRejection } from './composer-file-accept'\nimport type { UseFileMentionsResult } from './use-file-mentions'\nimport type { ChatAttachmentInput, FileMention } from '../chat-routes/wire'\nimport {\n AgentSessionControls,\n type AgentSessionControlsProps,\n} from './agent-session-controls'\nimport { ComposerModeControls, type ComposerPlanModeSelection } from './composer-mode-controls'\n\nexport interface EntryComposerProps {\n /** The one line above the input. Domain copy — always a product parameter. */\n heading?: ReactNode\n /** Optional supporting line under the heading. */\n subheading?: ReactNode\n placeholder?: string\n initialValue?: string\n sendLabel?: string\n disabled?: boolean\n /**\n * Agent identity (backend/model/effort). Pass it and the control row renders;\n * omit it and the composer ships without one. Omitting is a real choice for a\n * surface with nothing to choose — it should never be an oversight, which is\n * why this is one prop rather than a free-form slot a caller can forget.\n *\n * This is the CANONICAL picker cluster: `AgentSessionControls` from\n * `/web-react` (its model menu IS the canonical `ModelPicker`). The legacy\n * sandbox-ui adapter that used to back this prop was removed; the props\n * mapping for migrating a stored selection lives in\n * `docs/ui-picker-canon.md`.\n */\n agent?: AgentSessionControlsProps\n /**\n * Product-specific behavioral controls docked on the LEFT. A mode is an\n * on/off switch, not a value picker, so it sits apart from agent identity.\n * For the standard plan-approval control, prefer `planMode`.\n */\n modes?: ReactNode\n /**\n * Standard plan-approval mode. Pass only when the selected backend supports\n * it; omitted means the control is not rendered.\n */\n planMode?: ComposerPlanModeSelection\n /**\n * Upload endpoint for staged attachments. Omit and the attach affordance is\n * hidden — a composer with no place to put a file must not offer one.\n */\n uploadUrl?: string\n accept?: string\n onAttachmentError?: (reason: string) => void\n /** Files rejected by this composer's attachment policy. */\n onRejectFiles?: (rejections: ComposerFileRejection[]) => void\n /**\n * `@`-file mentions. The hook itself is shared (`useFileMentions`), but the\n * cold-box policy around it — whether pressing `@` is worth starting a\n * sandbox — is a per-surface product call, so the result is injected rather\n * than created here.\n */\n mentions?: UseFileMentionsResult\n mentionPopoverClassName?: string\n /** Rendered under the composer: suggestion pills, an onboarding nudge, a\n * disclaimer. All domain copy. */\n footer?: ReactNode\n /** Block submit until a persisted selection has resolved against the catalog,\n * so the first turn cannot go out under the wrong model. Defaults to true. */\n ready?: boolean\n onSubmit: (\n prompt: string,\n attachments: ChatAttachmentInput[],\n mentions: FileMention[],\n ) => void\n className?: string\n composerClassName?: string\n /** Max width of the centered column. Defaults to the fleet's 820px. */\n maxWidth?: number\n}\n\n/**\n * `EntryComposer` — the centered \"what do you want to work on?\" surface a\n * product shows before a conversation exists (a new thread, an empty session,\n * a workspace overview).\n *\n * It exists because three products each grew their own, and they drifted into\n * three different capability sets rather than three different looks: one lost\n * the attach button, one lost the model and effort pickers entirely, one\n * hand-rolled a `<textarea>` and wired pickers from three separate packages\n * into one row. The composer, the pickers, the attachment queue and the mention\n * index were all ALREADY shared — what was not shared was the assembly, so\n * every product re-derived which controls an entry surface gets, and each\n * re-derivation dropped a different one.\n *\n * Mechanism lives here: layout, the attachment queue, the mention wiring, the\n * submit gate (nothing sends while an upload is pending or failed, or before\n * the model selection has resolved). Domain stays a parameter: `heading`,\n * `placeholder`, `footer` (suggestion pills, disclaimers) and the selections\n * themselves.\n *\n * The input is `ChatComposer` — the same component a docked in-thread\n * composer renders directly; this assembly adds only the hero layout around\n * it.\n */\nexport function EntryComposer({\n heading,\n subheading,\n placeholder,\n initialValue = '',\n sendLabel,\n disabled,\n agent,\n modes,\n planMode,\n uploadUrl,\n accept = ATTACHMENT_ACCEPT,\n onAttachmentError,\n onRejectFiles,\n mentions,\n mentionPopoverClassName,\n footer,\n ready = true,\n onSubmit,\n className,\n composerClassName,\n maxWidth = 820,\n}: EntryComposerProps) {\n const [value, setValue] = useState(initialValue)\n const attachments = useComposerAttachments({\n uploadUrl,\n enabled: !!uploadUrl,\n onReject: onAttachmentError,\n onError: onAttachmentError,\n })\n\n function submit(prompt: string) {\n if (!ready || disabled) return\n const next = prompt.trim()\n // A pending or failed upload must not send: the turn would reference an\n // attachment the store does not have. Surface why rather than no-op'ing.\n if (attachments.hasPending || attachments.hasError) {\n if (attachments.blockReason) onAttachmentError?.(attachments.blockReason)\n return\n }\n if (!next && attachments.references.length === 0) return\n onSubmit(next, attachments.references, mentions?.mentions ?? [])\n setValue('')\n attachments.clear()\n mentions?.clearMentions()\n }\n\n return (\n <div\n className={\n className ??\n 'relative flex min-h-0 flex-1 flex-col items-center overflow-y-auto bg-background px-5 py-8'\n }\n >\n <div className=\"my-auto w-full shrink-0\" style={{ maxWidth }}>\n {heading ? (\n <h2 className=\"mb-4 text-center text-[1.75rem] font-medium tracking-tight text-foreground\">\n {heading}\n </h2>\n ) : null}\n {subheading ? (\n <p className=\"mx-auto mb-9 max-w-md text-center text-sm leading-relaxed text-muted-foreground\">\n {subheading}\n </p>\n ) : (\n heading ? <div className=\"mb-7\" /> : null\n )}\n <ChatComposer\n className={composerClassName ?? 'vt-composer'}\n value={value}\n onValueChange={setValue}\n onSend={(message) => submit(message)}\n placeholder={placeholder}\n sendLabel={sendLabel}\n // The circular arrow AgentComposer shipped and the agent-app canon\n // for new surfaces.\n sendVariant=\"icon\"\n disabled={disabled}\n sendDisabled={!ready}\n autoFocus\n // A hero composer autofocuses on mount, so the Cmd/Ctrl+L hint\n // would advertise a shortcut to the input the user is already in.\n focusShortcut={false}\n canSubmitAttachmentsOnly\n accept={accept}\n pendingFiles={attachments.composerFiles}\n onAttach={uploadUrl ? (files) => void attachments.addFiles(files) : undefined}\n onRejectFiles={onRejectFiles}\n onRemoveFile={attachments.removeAttachment}\n onRetryFile={attachments.retry}\n mention={\n mentions\n ? {\n ...mentions.mention,\n // Sidebar tone + hairline so the popover reads as app chrome\n // (the same treatment the picker menus get) instead of the\n // brighter overlay tone the component defaults to.\n popoverClassName:\n mentionPopoverClassName ??\n 'bg-surface-container-low border-[var(--md3-outline-variant)]',\n }\n : undefined\n }\n controls={modes ?? <ComposerModeControls planMode={planMode} />}\n trailing={\n agent ? <AgentSessionControls {...agent} /> : undefined\n }\n />\n {footer ? <div className=\"mt-7\">{footer}</div> : null}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useState","handle","lazy","useCallback","useEffect","useRef","useState","jsx","jsxs","lazy","useState","useRef","useCallback","useEffect","useCallback","useEffect","useMemo","useRef","useState","useRef","useState","useCallback","enabled","useEffect","useMemo","jsx","jsxs","useState","jsx","jsxs","useState"]}
|