From acb959c05953904fe1f3f6f729decf25416d669b Mon Sep 17 00:00:00 2001 From: Dhruv Sharma Date: Mon, 6 Jul 2026 03:25:11 +0530 Subject: [PATCH] fix(frontend): address onboarding copy review + hide Board header on welcome Per review on #2432: welcome body now names the kanban board explicitly, step copy tightened to the reviewer's wording, the project empty state says "No worker sessions yet" (an orchestrator session may already exist), and the "Board" subhead is hidden on the first-launch welcome since it described a board that isn't rendered there. Co-Authored-By: Claude Fable 5 --- .../integration/board-empty-states.test.tsx | 16 +++++++++------- .../src/renderer/components/BoardEmptyState.tsx | 12 ++++++------ .../src/renderer/components/SessionsBoard.tsx | 15 ++++++++++----- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/frontend/src/renderer/__tests__/integration/board-empty-states.test.tsx b/frontend/src/renderer/__tests__/integration/board-empty-states.test.tsx index ac1d9a570..1403a7474 100644 --- a/frontend/src/renderer/__tests__/integration/board-empty-states.test.tsx +++ b/frontend/src/renderer/__tests__/integration/board-empty-states.test.tsx @@ -118,6 +118,8 @@ describe("global board first launch", () => { expect(screen.getByText("Describe a task")).toBeInTheDocument(); expect(screen.getByText("Ready to merge")).toBeInTheDocument(); expect(columnCount()).toBe(0); + // The welcome carries its own orientation — no dangling "Board" header. + expect(screen.queryByText("Board")).not.toBeInTheDocument(); }); it("opens the native folder picker from the welcome CTA", async () => { @@ -154,7 +156,7 @@ describe("project board with no sessions", () => { respondWith([project], []); renderBoard(); - expect(await screen.findByText("No sessions yet")).toBeInTheDocument(); + expect(await screen.findByText("No worker sessions yet")).toBeInTheDocument(); // Board header + empty state each offer the pair; the orchestrator is primary in both. expect(screen.getAllByRole("button", { name: "Spawn Orchestrator" }).length).toBeGreaterThan(0); expect(screen.getAllByRole("button", { name: "New task" }).length).toBeGreaterThan(0); @@ -167,7 +169,7 @@ describe("project board with no sessions", () => { spawnOrchestratorMock.mockRejectedValue(new Error("branch is already checked out in another worktree")); renderBoard(); - await screen.findByText("No sessions yet"); + await screen.findByText("No worker sessions yet"); const [spawnButton] = screen.getAllByRole("button", { name: "Spawn Orchestrator" }); await userEvent.click(spawnButton); @@ -229,7 +231,7 @@ describe("project board with no sessions", () => { , ); - await screen.findByText("No sessions yet"); + await screen.findByText("No worker sessions yet"); await waitFor(() => expect(useUiStore.getState().orchestratorStartupErrors["proj-1"]).toBeUndefined()); expect(screen.queryByText(/Project added, but orchestrator did not start/)).not.toBeInTheDocument(); }); @@ -244,7 +246,7 @@ describe("project board with no sessions", () => { ); renderBoard(); - await screen.findByText("No sessions yet"); + await screen.findByText("No worker sessions yet"); await waitFor(() => expect(useUiStore.getState().orchestratorStartupErrors["proj-1"]).toBeUndefined()); expect(screen.queryByText(/Project added, but orchestrator did not start/)).not.toBeInTheDocument(); }); @@ -255,7 +257,7 @@ describe("project board with no sessions", () => { spawnOrchestratorMock.mockRejectedValue(new Error("branch is already checked out in another worktree")); const { rerender } = renderBoard(); - await screen.findByText("No sessions yet"); + await screen.findByText("No worker sessions yet"); const [spawnButton] = screen.getAllByRole("button", { name: "Spawn Orchestrator" }); await userEvent.click(spawnButton); await screen.findByText(/branch is already checked out/); @@ -267,7 +269,7 @@ describe("project board with no sessions", () => { , ); - await screen.findByText("No sessions yet"); + await screen.findByText("No worker sessions yet"); expect(screen.queryByText(/branch is already checked out/)).not.toBeInTheDocument(); }); @@ -276,7 +278,7 @@ describe("project board with no sessions", () => { renderBoard(); expect(await screen.findByText("fix the bug")).toBeInTheDocument(); - expect(screen.queryByText("No sessions yet")).not.toBeInTheDocument(); + expect(screen.queryByText("No worker sessions yet")).not.toBeInTheDocument(); expect(columnCount()).toBe(4); }); }); diff --git a/frontend/src/renderer/components/BoardEmptyState.tsx b/frontend/src/renderer/components/BoardEmptyState.tsx index adf4a9263..e339844af 100644 --- a/frontend/src/renderer/components/BoardEmptyState.tsx +++ b/frontend/src/renderer/components/BoardEmptyState.tsx @@ -29,19 +29,19 @@ export function BoardWelcome() { Welcome to Agent Orchestrator

- Point it at a git repository and delegate. Agents do the work on isolated branches; you review and merge the - PRs from this board. + Add a git repository, describe the work, and AO coordinates agent sessions on isolated branches. This kanban + board tracks each session from work through review to merge.

    - Pick a local git repository and choose which agents run it. + Choose a local git repository and select the agents AO should use. - The orchestrator plans the work and spawns worker sessions, each on its own branch. + Tell the orchestrator what you want done; it creates worker sessions on isolated branches. - Sessions move across this board as they progress: + Follow each session from work to review to merge readiness: {FLOW_LEGEND.map((zone, index) => ( @@ -123,7 +123,7 @@ export function ProjectBoardEmpty({ return (
    -

    No sessions yet

    +

    No worker sessions yet

    Describe a task and the orchestrator plans it, spawns worker sessions, and tracks them here from work to merge. diff --git a/frontend/src/renderer/components/SessionsBoard.tsx b/frontend/src/renderer/components/SessionsBoard.tsx index 451666da7..cb6148f36 100644 --- a/frontend/src/renderer/components/SessionsBoard.tsx +++ b/frontend/src/renderer/components/SessionsBoard.tsx @@ -225,11 +225,16 @@ export function SessionsBoard({ projectId }: SessionsBoardProps) { return (

    - + {/* The first-launch welcome carries its own orientation; a "Board" + header above it would describe a board that isn't rendered + (review feedback on #2432). */} + {!showWelcome && ( + + )}
    {projectId && health.state !== "ok" ? (