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:
2026-07-17 22:56:12 +02:00
co-authored by Claude Opus 4.8
parent 6274bf2da0
commit f979ada5e7
8 changed files with 92 additions and 17 deletions
+4 -4
View File
@@ -109,11 +109,11 @@ test("A, B, and admin requests preserve owner isolation through session route mu
const adminHeaders = { ...aliceHeaders, "x-thoth-principal-subject": "admin", "x-thoth-is-admin": "1" };
expect((await app.inject({ method: "GET", url: "/sessions", headers: aliceHeaders })).json())
.toEqual([{ id: "a", author: "alice" }]);
.toEqual([{ id: "a", author: "alice", active: false }]);
expect((await app.inject({ method: "GET", url: "/sessions", headers: bobHeaders })).json())
.toEqual([{ id: "b", author: "bob" }]);
.toEqual([{ id: "b", author: "bob", active: false }]);
expect((await app.inject({ method: "GET", url: "/sessions?scope=all", headers: adminHeaders })).json())
.toEqual([{ id: "a", author: "alice" }, { id: "b", author: "bob" }]);
.toEqual([{ id: "a", author: "alice", active: false }, { id: "b", author: "bob", active: false }]);
expect((await app.inject({ method: "POST", url: "/sessions/a/close", headers: bobHeaders })).statusCode)
.toBe(404);
@@ -174,7 +174,7 @@ test("POST /sessions usa i settings (workspace/provider/model/thinking) e crea+a
expect(sessionNewArg.thinking).toBe("high");
expect(sessionNewArg.question).toBe("q");
const list = await app.inject({ method: "GET", url: "/sessions" });
expect(list.json()).toEqual([{ id: "s1" }]);
expect(list.json()).toEqual([{ id: "s1", active: expect.any(Boolean) }]);
unlinkSync(modelKey);
});