From feda972a08ee6b74554995cf9d0191d40b46030d Mon Sep 17 00:00:00 2001 From: mptyl Date: Mon, 29 Jun 2026 21:47:50 +0200 Subject: [PATCH] feat(frontend): minimal central area; WIP icon above composer toggles model-activity panel Co-Authored-By: Claude Sonnet 4.6 --- .../src/shell/AppShell.session-mgmt.test.tsx | 17 +++++++++ frontend/src/shell/AppShell.tsx | 37 +++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/frontend/src/shell/AppShell.session-mgmt.test.tsx b/frontend/src/shell/AppShell.session-mgmt.test.tsx index e2d1541f..81f3a8f6 100644 --- a/frontend/src/shell/AppShell.session-mgmt.test.tsx +++ b/frontend/src/shell/AppShell.session-mgmt.test.tsx @@ -78,6 +78,23 @@ test("a failed resume keeps the panel open and does not activate the session", a await waitFor(() => expect(screen.getByText("Domanda originale")).toBeInTheDocument()); }); +import { act } from "@testing-library/react"; +import { useSessionStore } from "../store/sessionStore"; + +test("WIP icon toggles the Model activity panel and shows the streamed text", async () => { + wrap(); + // Activate a session by opening + resuming is heavy; instead drive the store directly. + // The WIP icon only renders with an active session, so simulate one via Resume. + server.use(http.post("http://localhost:8787/sessions/:id/resume", () => new HttpResponse(null, { status: 204 }))); + await userEvent.click(await screen.findByText("Attiva uno")); // opens docs panel + await userEvent.click(await screen.findByRole("button", { name: /resume/i })); // active session + act(() => { useSessionStore.getState().applyEvent({ type: "text_delta", text: "Looking at dim_patient." }); }); + // Model activity hidden until the WIP icon is clicked + expect(screen.queryByText(/Looking at dim_patient/)).not.toBeInTheDocument(); + await userEvent.click(screen.getByRole("button", { name: /model activity/i })); + expect(await screen.findByText(/Looking at dim_patient/)).toBeInTheDocument(); +}); + test("renaming a group reassigns its members via setSessionGroup", async () => { const groupSets: Array<{ id: string; group: string }> = []; server.use( diff --git a/frontend/src/shell/AppShell.tsx b/frontend/src/shell/AppShell.tsx index 0d41130d..eadbf652 100644 --- a/frontend/src/shell/AppShell.tsx +++ b/frontend/src/shell/AppShell.tsx @@ -1,7 +1,8 @@ import { useSessionStream } from "../stream/useSessionStream"; import { useSessionStore } from "../store/sessionStore"; import { WidgetHost } from "./WidgetHost"; -import { Transcript } from "./Transcript"; +import { CentralStatus } from "./CentralStatus"; +import { ModelActivityPanel } from "./ModelActivityPanel"; import { NavSessions } from "./NavSessions"; import { SessionMenu } from "./SessionMenu"; import { SessionDocumentsPanel } from "./SessionDocumentsPanel"; @@ -36,6 +37,7 @@ export function AppShell() { const queryClient = useQueryClient(); const [panelSession, setPanelSession] = useState(null); + const [showActivity, setShowActivity] = useState(false); const [activeOpen, setActiveOpen] = useState(true); const [archiveOpen, setArchiveOpen] = useState(false); const [renameTarget, setRenameTarget] = useState(null); @@ -53,7 +55,14 @@ export function AppShell() { function openPanel(id: string) { const s = sessions.find((x) => x.id === id); - if (s) setPanelSession(s); + if (s) { setPanelSession(s); setShowActivity(false); } + } + function toggleActivity() { + setShowActivity((v) => { + const next = !v; + if (next) setPanelSession(null); + return next; + }); } async function doResume(id: string) { try { @@ -151,6 +160,7 @@ export function AppShell() { onResume={doResume} /> )} + {showActivity && setShowActivity(false)} />} {/* Conversation column */}
@@ -166,7 +176,7 @@ export function AppShell() {
{activeSessionId ? ( <> - + ) : ( @@ -178,6 +188,20 @@ export function AppShell() { {/* Sticky composer: the box stays pinned while the conversation scrolls above. */}
+ {activeSessionId && ( +
+ +
+ )}
- {working && ( - - )}

ThothII

@@ -347,14 +368,14 @@ export function AppShell() { } /** Brand-tinted spinner shown while the assistant/harness is working. */ -function WorkingSpinner({ className }: { className?: string }) { +function WorkingSpinner({ className, spinning = true }: { className?: string; spinning?: boolean }) { return (