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>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// frontend/src/shell/SteerInput.tsx
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { CornerDownLeft } from "lucide-react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { postSteer, createSession } from "../api/sessions";
|
||||
@@ -18,7 +18,7 @@ interface Props {
|
||||
/** Interrupt the running session, persisting its state. */
|
||||
onStop?: () => void;
|
||||
/** Lets the parent focus the composer (e.g. on "New session"). */
|
||||
inputRef?: React.Ref<HTMLInputElement>;
|
||||
inputRef?: React.Ref<HTMLTextAreaElement>;
|
||||
}
|
||||
|
||||
export function SteerInput({ sessionId, onSessionCreated, onStop, inputRef }: Props) {
|
||||
@@ -26,6 +26,23 @@ export function SteerInput({ sessionId, onSessionCreated, onStop, inputRef }: Pr
|
||||
const [busy, setBusy] = useState(false);
|
||||
const setLastUserEntry = useSessionStore((s) => s.setLastUserEntry);
|
||||
const setPhase = useSessionStore((s) => s.setPhase);
|
||||
const taRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
// Merge our own ref (for autosizing) with the forwarded one (parent focus).
|
||||
function attachRef(el: HTMLTextAreaElement | null) {
|
||||
taRef.current = el;
|
||||
if (typeof inputRef === "function") inputRef(el);
|
||||
else if (inputRef) (inputRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = el;
|
||||
}
|
||||
|
||||
// Grow the textarea to fit its content (up to a cap, then it scrolls), and shrink
|
||||
// back when the text is cleared. Runs after every value change so height tracks text.
|
||||
useEffect(() => {
|
||||
const el = taRef.current;
|
||||
if (!el) return;
|
||||
el.style.height = "auto";
|
||||
el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
|
||||
}, [text]);
|
||||
|
||||
async function submit() {
|
||||
const trimmed = text.trim();
|
||||
@@ -46,15 +63,16 @@ export function SteerInput({ sessionId, onSessionCreated, onStop, inputRef }: Pr
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||
if (e.key === "Enter") {
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
||||
// Enter sends; Shift+Enter inserts a newline (the textarea grows to fit it).
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-end gap-2">
|
||||
{sessionId && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -66,15 +84,15 @@ export function SteerInput({ sessionId, onSessionCreated, onStop, inputRef }: Pr
|
||||
<span className="block size-2.5 rounded-full bg-primary" />
|
||||
</button>
|
||||
)}
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
<textarea
|
||||
ref={attachRef}
|
||||
rows={1}
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={sessionId ? "Free steering (!)…" : "Ask a question to start a new session…"}
|
||||
aria-label={sessionId ? "Steering" : "New question"}
|
||||
className="flex-1 bg-transparent px-1 text-sm outline-none placeholder:text-muted-foreground"
|
||||
className="max-h-40 flex-1 resize-none bg-transparent px-1 py-1 text-sm leading-relaxed outline-none placeholder:text-muted-foreground"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user