Files
ThothII/frontend/src/shell/CentralStatus.tsx
T
marcopanandClaude Opus 4.8 d6b4729ddf feat(shell): yellow active session, growing composer, phase title, refined activity log, arrow panel toggle
Fix six GUI defects, verified live against the real stack:

- NavSessions: mark the in-progress (active) session in yellow (warning
  token) — both its status dot and its row background.
- SteerInput: the composer is now an auto-growing textarea that wraps and
  grows vertically (caps at 160px, then scrolls); Enter sends, Shift+Enter
  inserts a newline.
- WorkflowBar: show a synthetic title of the current phase under the 8 dots
  from static EN/IT strings (no LLM); English is displayed to match the chrome.
- CentralStatus: reformat the 5-line system-message tail as a structured,
  monospace list with per-line markers and an emphasized last line.
- AppShell + CentralStatus: move the left Model-activity panel toggle off the
  working spinner (now a pure status indicator) onto a dedicated arrow button
  (→ opens, ← closes).
- gate: rename the phase-confirm button "Conferma e prosegui" → "Salva e
  procedi" (builders.js + its L1 test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 14:32:01 +02:00

113 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useReducer, useState } from "react";
import { useSessionStore } from "../store/sessionStore";
import { WorkingSpinner } from "./WorkingSpinner";
/** Last few non-blank lines of a string — a compact "what the model is doing now"
* tail (mirrors ModelActivityPanel's tail-line logic, at a smaller size). Returned
* as an array so each line renders as its own formatted log row. */
function tailLines(s: string, n = 5): string[] {
return s
.split("\n")
.map((l) => l.trimEnd())
.filter((l) => l.trim() !== "")
.slice(-n);
}
/** The minimal central view: the user's last input/choice, a live "working" indicator
* (elapsed time + a short tail of the model stream) while the model is busy, and the
* gate's curated messages for the current step. The full verbose stream lives in the
* left Model-activity panel. */
export function CentralStatus({ working }: { working: boolean }) {
const lastUserEntry = useSessionStore((s) => s.lastUserEntry);
const stepMessages = useSessionStore((s) => s.stepMessages);
const transcript = useSessionStore((s) => s.transcript);
const [startedAt, setStartedAt] = useState<number | null>(null);
const [, tick] = useReducer((x: number) => x + 1, 0);
// Start the per-step timer when work begins; tick once a second so the elapsed
// display updates; clear it when work stops.
useEffect(() => {
if (!working) {
setStartedAt(null);
return;
}
setStartedAt((prev) => prev ?? Date.now());
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [working]);
if (!working && !lastUserEntry && stepMessages.length === 0) return null;
const elapsed = startedAt ? Math.max(0, Math.floor((Date.now() - startedAt) / 1000)) : 0;
const tailRows = tailLines(transcript.at(-1)?.text ?? "");
return (
<div className="flex flex-col gap-3">
{lastUserEntry && (
<div className="rounded-lg border border-border/70 bg-card px-3 py-2">
<span className="text-[0.65rem] font-semibold uppercase tracking-wide text-muted-foreground">
{lastUserEntry.kind === "input" ? "You asked" : "You chose"}
</span>
<p className="mt-0.5 text-sm text-foreground">{lastUserEntry.text}</p>
</div>
)}
{working && (
<div className="flex flex-col gap-1.5">
<div className="flex min-w-0 items-center gap-2 text-sm text-muted-foreground">
{/* Pure status indicator — opening the activity panel now lives on the
dedicated arrow toggle in the header, not on this spinner. */}
<span className="grid size-5 shrink-0 place-items-center text-primary">
<WorkingSpinner spinning className="size-3.5" />
</span>
<span className="shrink-0 tabular-nums">{elapsed}s</span>
<span className="sr-only">ThothII is working</span>
</div>
{tailRows.length > 0 && (
<ol className="ml-7 flex flex-col gap-0.5 overflow-hidden rounded-md border border-border/50 bg-muted/40 px-3 py-2">
{tailRows.map((line, i) => {
const isLast = i === tailRows.length - 1;
return (
<li
key={i}
className={[
"flex items-baseline gap-2 font-mono text-[0.72rem] leading-relaxed",
isLast ? "text-foreground/80" : "text-muted-foreground/55",
].join(" ")}
>
<span
aria-hidden
className={[
"shrink-0 select-none",
isLast ? "text-primary" : "text-muted-foreground/35",
].join(" ")}
>
›
</span>
<span className="min-w-0 flex-1 truncate">{line}</span>
</li>
);
})}
</ol>
)}
</div>
)}
{stepMessages.map((m, i) => (
<p
key={i}
className={[
"text-sm",
m.level === "error" ? "text-destructive"
: m.level === "warning" ? "text-amber-600"
: "text-foreground/90",
].join(" ")}
>
{m.text}
</p>
))}
</div>
);
}