"use client"; import { useCallback, useRef, useState } from "react"; import { ChevronRightIcon } from "lucide-react"; import { MessageResponse } from "@/components/ai-elements/message"; import { joinDictation, useDictationTarget } from "@/components/dictation"; import { cn } from "@/lib/utils"; /** * The practice dock (practice sessions, owner design 2026-10-04): an * ACTIVE problem replaces the chat composer — the persistent bottom blob * morphs into the problem. The agent's statement above; the learner's * half below — "Your thinking", a labeled composer-twin field (the * question dock's discipline: Enter makes a newline, it is for dumping) * with TWO submit buttons: * * - **Nudge** — submit the thinking WITHOUT a committed answer: working * trace, hint request (the ladder answers, one rung per request). * - **Commit** — the thinking contains an arrived answer. * * ⌘/Ctrl+Enter commits (the universal submit chord). One problem may * carry many submissions; each starts a turn. * * Collapse (scroll-up → slim bar) is owned by the DockOverlay; this * component is content only, and the expanded form stays MOUNTED while * collapsed (hidden) so a half-written trace survives scrolling. * * The commitment (modelSolution, decisionPoints, …) rides the tool call's * arguments and is NOT rendered here — only the statement is the * learner's. Esc dismisses the problem (the layered-Esc chain; the * confirm dialog then the silent route) — claimed by the page, not here. */ export interface PracticeDockProps { /** The active problem's tool-call id (submissions target it). */ problemCallId: string; /** The pose's arguments — only the statement is the learner's (the * mode badge would edge toward announcing the target; dropped). */ input: { statement: string }; /** Collapse state, owned by the DockOverlay. */ expanded: boolean; onExpand: () => void; /** (commit, text) — POST {type:"thinking"}; commit=false is the nudge. */ onSubmit: (commit: boolean, text: string) => void; /** Escape in the thinking field stops dictation (threaded from the page). */ onEscape?: () => void; } export function PracticeDock({ problemCallId, input, expanded, onExpand, onSubmit, onEscape }: PracticeDockProps) { const [thinking, setThinking] = useState(""); // Ref MIRRORS the field ("nothing gets lost", the question-dock // discipline): a submit resuming after `await finishDictation()` reads // the tail, never a stale closure. const thinkingRef = useRef(""); useDictationTarget( useCallback( (t: string) => { const v = joinDictation(thinkingRef.current, t); thinkingRef.current = v; setThinking(v); }, [], ), true, ); const submit = (commit: boolean) => { const text = thinkingRef.current.trim(); if (text.length === 0) return; thinkingRef.current = ""; setThinking(""); onSubmit(commit, text); }; /* ── the collapsed bar ── */ if (!expanded) { return ( ); } /* ── the expanded form ── */ return (