+ {/* THE NEXT CHIP (owner round 16): the practice pause's
+ mouse path — hovering at the composer band's top-right,
+ the pick-pill chrome register. One click = the move-on
+ signal; gone the moment a problem is active again. */}
+ {nextReady && (
+
+ )}
+
+
}
onSubmitAnswer={(output) => {
if (!pending) return;
diff --git a/components/composer.tsx b/components/composer.tsx
index ad4cb01..3fb4558 100644
--- a/components/composer.tsx
+++ b/components/composer.tsx
@@ -13,17 +13,26 @@ import { Hint } from "@/components/ui/hint";
* row padding p-1.5 (6px) = button rounded-[10px] = 25% of the 40px button.
*/
/** What one Enter press does, in precedence order: stop a busy stream,
- * otherwise send the message. (The plan-verdict accept branch died
- * 2026-09-26 with the verdict itself.) Pure so the static-render harness
+ * otherwise send the message, otherwise — practice sessions only — fire
+ * the next-problem signal while the between-problems pause is live
+ * (owner round 16: Enter on an EMPTY composer is the go-ahead; typing
+ * anything makes Enter a send again). Pure so the static-render harness
* can pin the decision (key events can't be simulated in
* renderToStaticMarkup). */
-export function enterAction(state: { busy: boolean; canSend: boolean }): "stop" | "send" | "none" {
+export function enterAction(state: {
+ busy: boolean;
+ canSend: boolean;
+ /** The between-problems pause is live: practice session, nothing
+ * active, no turn/compaction in flight (the caller's fold). */
+ nextProblem?: boolean;
+}): "stop" | "send" | "next" | "none" {
// Composer steering (owner arc 2026-10-03): while a turn runs, typed
// text makes Enter a STEER (the queue-and-redirect send); Enter with
// an empty composer stays the stop shortcut. The button itself keeps
// the square — Stop stays one click away at all times.
if (state.busy) return state.canSend ? "send" : "stop";
if (state.canSend) return "send";
+ if (state.nextProblem) return "next";
return "none";
}
@@ -38,6 +47,8 @@ export function Composer({
inputRef,
onEscape,
placeholder = "Ask, answer, or steer the lesson…",
+ nextProblem = false,
+ onNext,
}: {
busy: boolean;
value: string;
@@ -56,16 +67,27 @@ export function Composer({
* active sessions read "Ask, answer, or steer the lesson…"; the fresh
* new-session page asks the graph view's opening question instead. */
placeholder?: string;
+ /** The practice pause is live (owner round 16): Enter on the empty
+ * composer fires the next-problem signal instead of doing nothing —
+ * the keyboard-first move-on. */
+ nextProblem?: boolean;
+ /** The signal's dispatcher (send({type:"next"})) — the chip and the
+ * empty-Enter share it. */
+ onNext?: () => void;
}) {
+ const act = () => {
+ const action = enterAction({ busy, canSend, nextProblem });
+ if (action === "stop") onStop();
+ else if (action === "send") onSend();
+ else if (action === "next") onNext?.();
+ };
return (