fix(ui): reset session view on stop and exit
This commit is contained in:
@@ -1213,6 +1213,38 @@ test("closing and reopening Model activity preserves the complete activity log",
|
||||
expect(useSessionStore.getState().activityLog).toEqual(beforeClose);
|
||||
});
|
||||
|
||||
test.each(["stop", "session_exit"])("%s restores the empty landing with model activity closed", async (exit) => {
|
||||
const close = vi.fn(() => new HttpResponse(null, { status: 204 }));
|
||||
server.use(
|
||||
http.post("/api/sessions/:id/resume", () => resumeResult("s1")),
|
||||
http.post("/api/sessions/:id/close", close),
|
||||
);
|
||||
wrap(adminUser);
|
||||
await userEvent.click(await screen.findByText("Attiva uno"));
|
||||
await userEvent.click(await screen.findByRole("button", { name: "Resume" }));
|
||||
await userEvent.click(screen.getByRole("button", { name: "Show model activity" }));
|
||||
act(() => useSessionStore.getState().setLastUserEntry({ kind: "input", text: "Previous question" }));
|
||||
expect(screen.getByLabelText("Model activity timeline")).toHaveTextContent("Previous question");
|
||||
const source = FakeEventSource.instances.at(-1)!;
|
||||
|
||||
if (exit === "stop") {
|
||||
await userEvent.click(screen.getByRole("button", { name: /stop and save session/i }));
|
||||
await userEvent.click(screen.getAllByRole("button", { name: "Stop & save" })[0]);
|
||||
} else {
|
||||
act(() => source.emit({ type: "system_event", event: "session_exit" }));
|
||||
}
|
||||
|
||||
expect(await screen.findByText(/type your question/i)).toBeInTheDocument();
|
||||
expect(screen.queryByRole("heading", { name: "Model activity" })).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("app-shell")).toHaveAttribute("data-activity-layout", "closed");
|
||||
expect(screen.queryByRole("complementary", { name: "Session summary" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("complementary", { name: "Session navigation" })).toBeVisible();
|
||||
expect(screen.getByRole("textbox", { name: /new question/i })).toHaveValue("");
|
||||
expect(useSessionStore.getState().activityLog).toEqual([]);
|
||||
expect(source.closed).toBe(true);
|
||||
expect(close).toHaveBeenCalledTimes(exit === "stop" ? 1 : 0);
|
||||
});
|
||||
|
||||
test("a new question after finalization restores the landing and administration navigation", async () => {
|
||||
useSessionStore.getState().resetSession();
|
||||
let finalized = false;
|
||||
|
||||
@@ -694,6 +694,20 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
authGeneration,
|
||||
);
|
||||
|
||||
function resetSessionView() {
|
||||
invalidateResumeIntent();
|
||||
newSessionOperationRef.current = null;
|
||||
resetSession();
|
||||
selectActiveSession(null);
|
||||
// Panel visibility belongs to the shell, outside the session store.
|
||||
// Clear it too so leaving a session restores the initial landing layout.
|
||||
setPanelSession(null);
|
||||
setShowActivity(false);
|
||||
setCreatingSession(false);
|
||||
setAwaitingQuestion(false);
|
||||
setStopConfirm(false);
|
||||
}
|
||||
|
||||
// A backend "session_exit" system event (e.g. the replay server emitting it
|
||||
// when the reviewer picks "Esci") asks us to leave the live session view and
|
||||
// return to the landing state. We deliberately do NOT also POST /close here —
|
||||
@@ -705,10 +719,7 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
if (ev === "session_exit") {
|
||||
// Never let a streamed event terminate the managed Pi child. Only the
|
||||
// explicit “Stop & save” action is allowed to call /close.
|
||||
invalidateResumeIntent();
|
||||
resetSession();
|
||||
selectActiveSession(null);
|
||||
setAwaitingQuestion(false);
|
||||
resetSessionView();
|
||||
}
|
||||
// The final workflow turn ends with the session already finalized on disk:
|
||||
// refetch now instead of waiting for the 10s poll, so the completed state
|
||||
@@ -725,15 +736,8 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
}
|
||||
if (!canLeaveDatabaseManagement()) return;
|
||||
navigate({ surface: "core" }, true);
|
||||
invalidateResumeIntent();
|
||||
newSessionOperationRef.current = null;
|
||||
resetSession();
|
||||
// Restore the landing layout, including the right-hand administration rail.
|
||||
setPanelSession(null);
|
||||
setShowActivity(false);
|
||||
resetSessionView();
|
||||
setAwaitingQuestion(true);
|
||||
setCreatingSession(false);
|
||||
selectActiveSession(null);
|
||||
// Best effort only: session creation keeps the authoritative readiness gate.
|
||||
// Composer focus is deliberately independent of this network request.
|
||||
void prewarmRuntime().catch(() => undefined);
|
||||
@@ -781,9 +785,7 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
} finally {
|
||||
if (!isAuthOperationCurrent(guard, { sessionId: id, disposalEpoch: operationEpochRef.current })
|
||||
|| activeSessionIdRef.current !== id) return;
|
||||
resetSession();
|
||||
selectActiveSession(null);
|
||||
setAwaitingQuestion(false);
|
||||
resetSessionView();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user