feat(frontend): open a live session straight to its pending gate
Opening an in-progress session that has a live Pi runtime now reconnects to its pending gate instead of the empty landing screen that read as "stopped". Cold/completed sessions keep the read-only documents panel with its explicit Resume, so a mere click never spawns a runtime. Backend GET /sessions now reports a per-session `active` flag (live runtime bound) to drive this. Also: - "New session" now closes any open session detail panel (left box). - The model-activity separator can be dragged to a full 50/50 split (was capped at 576px); central-min still guards narrow viewports. Test fixes uncovered along the way: - Node 25 ships an experimental global localStorage that shadows jsdom's and lacks clear(), failing every jsdom test at setup; install a spec-compliant in-memory Storage (feature-detected, inert on CI/LTS). - Fix 4 pre-existing session-mgmt tests that used an ambiguous getByText for a session shown in both nav and header; target the nav item by test id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -181,6 +181,37 @@ test("opening the panel shows the session documents", async () => {
|
||||
expect(await screen.findByText("Domanda originale")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("clicking a session with a live runtime reconnects to its gate instead of the panel", async () => {
|
||||
let resumed: string | null = null;
|
||||
server.use(
|
||||
http.get("http://localhost:8787/sessions", () =>
|
||||
HttpResponse.json([{ ...LIST[0], active: true }])),
|
||||
http.post("http://localhost:8787/sessions/:id/resume", ({ params }) => {
|
||||
resumed = params.id as string;
|
||||
return resumeResult(resumed, true); // warm runtime → alreadyActive
|
||||
}),
|
||||
http.get("http://localhost:8787/sessions/:id", () =>
|
||||
HttpResponse.json({ id: "s1", status: "open", phase: 1 })),
|
||||
);
|
||||
wrap();
|
||||
await userEvent.click(await screen.findByText("Attiva uno"));
|
||||
await waitFor(() => expect(resumed).toBe("s1"));
|
||||
await waitFor(() => expect(FakeEventSource.instances.at(-1)?.url).toContain("/sessions/s1/events"));
|
||||
// Live view, not the read-only documents panel.
|
||||
expect(screen.queryByText("Domanda originale")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("New session closes an open session detail panel", async () => {
|
||||
server.use(http.post("http://localhost:8787/runtime/prewarm", () =>
|
||||
HttpResponse.json({ status: "warming" }, { status: 202 })));
|
||||
wrap();
|
||||
await userEvent.click(await screen.findByText("Attiva uno")); // cold session → panel opens
|
||||
await screen.findByText("Domanda originale");
|
||||
await userEvent.click(screen.getByRole("button", { name: /^new session$/i }));
|
||||
await waitFor(() => expect(screen.queryByText("Domanda originale")).not.toBeInTheDocument());
|
||||
expect(screen.getByText(/type your question/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Archive accordion expands to reveal archived sessions", async () => {
|
||||
wrap();
|
||||
// archived hidden until the Archive accordion is expanded
|
||||
@@ -246,7 +277,7 @@ test("an already-active same-session Resume preserves its EventSource and store"
|
||||
act(() => first.emitNamed("info", { type: "info", text: "Live state" }, "4"));
|
||||
const before = useSessionStore.getState().activityLog.map((entry) => ({ ...entry }));
|
||||
|
||||
await userEvent.click(screen.getByText("Attiva uno"));
|
||||
await userEvent.click(screen.getByTestId("session-item-s1"));
|
||||
await screen.findByText("Domanda originale");
|
||||
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
|
||||
|
||||
@@ -278,7 +309,7 @@ test("concurrent same-id Resume invocations share one cold request and replaceme
|
||||
const oldSource = FakeEventSource.instances[0];
|
||||
act(() => oldSource.emitNamed("info", { type: "info", text: "Old generation" }, "900"));
|
||||
|
||||
await userEvent.click(screen.getByText("Attiva uno"));
|
||||
await userEvent.click(screen.getByTestId("session-item-s1"));
|
||||
const resumeButton = await screen.findByRole("button", { name: /resume/i });
|
||||
await userEvent.click(resumeButton);
|
||||
await coldStarted.promise;
|
||||
@@ -343,7 +374,7 @@ test("a committed Resume releases same-id single-flight before its manifest sett
|
||||
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
|
||||
await waitFor(() => expect(useSessionStore.getState().currentPhase).toBe("F3"));
|
||||
|
||||
await userEvent.click(screen.getByText("Attiva uno"));
|
||||
await userEvent.click(screen.getByTestId("session-item-s1"));
|
||||
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
|
||||
await waitFor(() => expect(s1ResumeCalls).toBe(2));
|
||||
await waitFor(() => expect(useSessionStore.getState().currentPhase).toBe("F4"));
|
||||
@@ -380,7 +411,7 @@ test("cold same-session Resume keeps the old stream until success then receives
|
||||
return resumeResult("s1");
|
||||
}));
|
||||
|
||||
await userEvent.click(screen.getByText("Attiva uno"));
|
||||
await userEvent.click(screen.getByTestId("session-item-s1"));
|
||||
await screen.findByText("Domanda originale");
|
||||
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
|
||||
await resumeStarted;
|
||||
@@ -446,7 +477,7 @@ test("a failed same-session Resume preserves its source, activity, and document
|
||||
|
||||
server.use(http.post("http://localhost:8787/sessions/:id/resume", () =>
|
||||
new HttpResponse(null, { status: 409 })));
|
||||
await userEvent.click(screen.getByText("Attiva uno"));
|
||||
await userEvent.click(screen.getByTestId("session-item-s1"));
|
||||
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
|
||||
await waitFor(() => expect(screen.getByText("Domanda originale")).toBeInTheDocument());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user