diff --git a/frontend/src/renderer/components/ShellTopbar.test.tsx b/frontend/src/renderer/components/ShellTopbar.test.tsx index 3b2c9d4190..27b9733aef 100644 --- a/frontend/src/renderer/components/ShellTopbar.test.tsx +++ b/frontend/src/renderer/components/ShellTopbar.test.tsx @@ -5,7 +5,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { WorkspaceSession } from "../types/workspace"; import { TopbarKillButton } from "./ShellTopbar"; -const { postMock } = vi.hoisted(() => ({ +const { onKilledMock, postMock } = vi.hoisted(() => ({ + onKilledMock: vi.fn(), postMock: vi.fn(), })); @@ -35,7 +36,20 @@ const worker: WorkspaceSession = { prs: [], }; -function renderKill(session: WorkspaceSession = worker) { +const orchestrator: WorkspaceSession = { + id: "orch-1", + workspaceId: "proj-1", + workspaceName: "my-app", + title: "orchestrator", + provider: "claude-code", + kind: "orchestrator", + branch: "main", + status: "working", + updatedAt: "2026-06-10T00:00:00Z", + prs: [], +}; + +function renderKill(session: WorkspaceSession = worker, orchestratorId?: string) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, @@ -44,13 +58,14 @@ function renderKill(session: WorkspaceSession = worker) { }); render( - + , ); return queryClient; } beforeEach(() => { + onKilledMock.mockReset(); postMock.mockReset(); postMock.mockResolvedValue({ data: { ok: true, sessionId: "sess-1" }, error: undefined }); }); @@ -89,4 +104,26 @@ describe("TopbarKillButton", () => { expect(await screen.findByText("session not found")).toBeInTheDocument(); }); + + it("navigates back to the project orchestrator after a successful kill", async () => { + renderKill(worker, orchestrator.id); + + await userEvent.click(screen.getByRole("button", { name: "Kill session" })); + await userEvent.click(screen.getByRole("button", { name: "Confirm kill" })); + + await waitFor(() => { + expect(onKilledMock).toHaveBeenCalledWith("proj-1", "orch-1"); + }); + }); + + it("falls back to the project board when no orchestrator is available", async () => { + renderKill(); + + await userEvent.click(screen.getByRole("button", { name: "Kill session" })); + await userEvent.click(screen.getByRole("button", { name: "Confirm kill" })); + + await waitFor(() => { + expect(onKilledMock).toHaveBeenCalledWith("proj-1", undefined); + }); + }); }); diff --git a/frontend/src/renderer/components/ShellTopbar.tsx b/frontend/src/renderer/components/ShellTopbar.tsx index 892fcabbd5..d1e4310e15 100644 --- a/frontend/src/renderer/components/ShellTopbar.tsx +++ b/frontend/src/renderer/components/ShellTopbar.tsx @@ -195,7 +195,22 @@ export function ShellTopbar() { ) : null} {/* Kill control sits beside the orchestrator link for active workers — moved here from the inspector's Summary "Danger zone". */} - {!isOrchestrator && session && sessionIsActive(session) ? : null} + {!isOrchestrator && session && sessionIsActive(session) ? ( + { + if (orchestratorId) { + void navigate({ + to: "/projects/$projectId/sessions/$sessionId", + params: { projectId: workspaceId, sessionId: orchestratorId }, + }); + return; + } + void navigate({ to: "/projects/$projectId", params: { projectId: workspaceId } }); + }} + /> + ) : null} {!isOrchestrator && (