"use client"; import { CheckIcon, CornerDownLeftIcon, SquareIcon, XIcon } from "lucide-react"; import { cn } from "@/lib/utils"; /** * The chat composer — content row ONLY (the persistent rounded shell lives in * the chat view's overlay, so the shell can later morph chat → question * without remounting). * * Corner-radius math (concentric with the shell): shell rounded-2xl (16px) − * row padding p-1.5 (6px) = button rounded-[10px] = 25% of the 40px button. */ /** What one Enter press does, in precedence order (the plan-verdict * behavior is user-decided 2026-09-19, docs/probe-teach-split.md §4): * stop a busy stream; ACCEPT when the plan verdict pends — typed text * rides the acceptance as the note (the green button's exact semantics; * an empty box accepts too, so Enter can never strand the verdict); * otherwise send the message. Pure so the static-render harness can pin * the decision (key events can't be simulated in renderToStaticMarkup). */ export function enterAction(state: { busy: boolean; verdictPending: boolean; canSend: boolean }): "stop" | "accept" | "send" | "none" { if (state.busy) return "stop"; if (state.verdictPending) return "accept"; if (state.canSend) return "send"; return "none"; } export function Composer({ busy, value, onChange, onSend, onStop, canSend, autoFocus, verdict, onEscape, }: { busy: boolean; value: string; onChange: (v: string) => void; onSend: () => void; onStop: () => void; canSend: boolean; autoFocus?: boolean; /** The plan verdict gate: while the presented plan awaits the learner's * word, the send square becomes two stacked buttons — green accept, * red decline. Typed text rides the verdict as his note. */ verdict?: { onAccept: () => void; onDecline: () => void }; /** Escape while a dictation runs stops it (STT wave, 2026-09-17). No-op * when absent — Esc otherwise means nothing in the composer. */ onEscape?: () => void; }) { return (
{ e.preventDefault(); const action = enterAction({ busy, verdictPending: Boolean(verdict), canSend }); if (action === "stop") onStop(); else if (action === "accept") verdict?.onAccept(); else if (action === "send") onSend(); }} >