diff --git a/frontend/src/renderer/components/TerminalPane.test.tsx b/frontend/src/renderer/components/TerminalPane.test.tsx new file mode 100644 index 0000000000..5be8fb7c2a --- /dev/null +++ b/frontend/src/renderer/components/TerminalPane.test.tsx @@ -0,0 +1,96 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import type { WorkspaceSession } from "../types/workspace"; +import { TerminalPane } from "./TerminalPane"; + +vi.mock("./XtermTerminal", () => ({ + XtermTerminal: () =>
, +})); + +vi.mock("../hooks/useTerminalSession", () => ({ + useTerminalSession: () => ({ + attach: vi.fn(), + state: "idle", + error: undefined, + }), +})); + +const worker = { + id: "sess-1", + workspaceId: "proj-1", + workspaceName: "my-app", + title: "do the thing", + provider: "claude-code", + kind: "worker", + branch: "ao/sess-1", + status: "working", + updatedAt: "2026-06-10T00:00:00Z", + prs: [], +} satisfies WorkspaceSession; + +const orchestrator = { + ...worker, + id: "sess-orch", + title: "orchestrate", + kind: "orchestrator", +} satisfies WorkspaceSession; + +function renderPane(session?: WorkspaceSession) { + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + const previousAO = window.ao; + window.ao = {} as typeof window.ao; + const result = render( + + + , + ); + return { + ...result, + restore: () => { + window.ao = previousAO; + }, + }; +} + +describe("TerminalPane empty states", () => { + it("shows a no-selection message when no session is selected", () => { + const view = renderPane(); + try { + expect(screen.getByText("Agent Orchestrator")).toBeInTheDocument(); + expect(screen.getByText("No session selected. Pick a worker to attach its terminal.")).toBeInTheDocument(); + } finally { + view.restore(); + } + }); + + it("shows a startup message when a selected session has no terminal handle yet", () => { + const view = renderPane(worker); + try { + expect(screen.getByText("Starting session")).toBeInTheDocument(); + expect( + screen.getByText( + "Preparing the worker terminal. This can take a moment while AO creates the worktree and starts the agent.", + ), + ).toBeInTheDocument(); + expect(screen.queryByText("No session selected. Pick a worker to attach its terminal.")).not.toBeInTheDocument(); + } finally { + view.restore(); + } + }); + + it("shows orchestrator-specific startup copy for a pending orchestrator terminal", () => { + const view = renderPane(orchestrator); + try { + expect(screen.getByText("Starting session")).toBeInTheDocument(); + expect( + screen.getByText( + "Preparing the orchestrator terminal. This can take a moment while AO creates the worktree and starts the agent.", + ), + ).toBeInTheDocument(); + expect(screen.queryByText(/worker terminal/i)).not.toBeInTheDocument(); + } finally { + view.restore(); + } + }); +}); diff --git a/frontend/src/renderer/components/TerminalPane.tsx b/frontend/src/renderer/components/TerminalPane.tsx index 79367e1a38..126dc858b0 100644 --- a/frontend/src/renderer/components/TerminalPane.tsx +++ b/frontend/src/renderer/components/TerminalPane.tsx @@ -207,6 +207,12 @@ function AttachedTerminal({ session, theme, daemonReady, terminalTarget, fontSiz const banner = bannerText(state, error); const showEmptyState = !handleId; const showExitedState = state === "exited"; + const emptyStateTitle = session ? "Starting session" : "Agent Orchestrator"; + const emptyStateMessage = session + ? session.kind === "orchestrator" + ? "Preparing the orchestrator terminal. This can take a moment while AO creates the worktree and starts the agent." + : "Preparing the worker terminal. This can take a moment while AO creates the worktree and starts the agent." + : "No session selected. Pick a worker to attach its terminal."; return (
@@ -231,10 +237,8 @@ function AttachedTerminal({ session, theme, daemonReady, terminalTarget, fontSiz {showEmptyState && (
-
Agent Orchestrator
-
- No session selected. Pick a worker to attach its terminal. -
+
{emptyStateTitle}
+
{emptyStateMessage}
)}