feat(workspace): the console tab bar is the session view's only header

The status/name/back-arrow row is gone; Tools sits at the tab bar's right
end and the pipeline/feature pickers show only with a template.
LaneConsolePane is reduced to the terminal (no title, no Active runs, no
New run): RunHistory.tsx, api.run.history and the page's active-sessions
poll are removed.
This commit is contained in:
2026-10-03 12:13:11 +07:00
parent 4df944c6aa
commit b7efba8747
17 changed files with 177 additions and 1941 deletions
@@ -69,7 +69,6 @@ vi.mock("../../lib/api", async (importOriginal) => {
api: {
run: {
list: r({ items: [] }),
history: r({ items: [] }),
binary: r({ found: true, path: "/usr/bin/claude" }),
cwds: r({ items: [HOME] }),
files: r({ items: [] }),
+5 -10
View File
@@ -173,13 +173,6 @@ vi.mock("../../lib/api", async (importOriginal) => {
recordCall("GET", "/api/run/list");
return { runs: [], items: [] };
}),
history: vi.fn().mockImplementation(async (limit?: number, opts?: { laneId?: number }) => {
recordCall(
"GET",
`/api/run/history?limit=${limit}${opts?.laneId ? `&laneId=${opts.laneId}` : ""}`
);
return { items: [] };
}),
binary: vi.fn().mockImplementation(async () => {
recordCall("GET", "/api/run/binary");
return { found: true, path: "/usr/bin/claude" };
@@ -612,7 +605,7 @@ describe("Workspace — home and session view", () => {
expect(screen.getByTestId("lane-detail").parentElement).toHaveClass("hidden");
});
it("opens the session view for ?run=, and the back button returns home", async () => {
it("opens the session view for ?run=, headed by the tab bar alone", async () => {
render(
<MemoryRouter initialEntries={["/workspace?lane=1&run=agenthub-lane-1"]}>
<Workspace />
@@ -622,8 +615,10 @@ describe("Workspace — home and session view", () => {
await settle();
expect(screen.queryByTestId("workspace-home")).toBeNull();
expect(screen.getByTestId("lane-detail").parentElement).not.toHaveClass("hidden");
await userEvent.setup().click(screen.getByTestId("workspace-back-home"));
expect(await screen.findByTestId("workspace-home")).toBeInTheDocument();
// No header row (status, back arrow, name) and no pane header: the tab bar is it.
expect(screen.queryByTestId("workspace-back-home")).toBeNull();
expect(screen.queryByText(/^Run /)).toBeNull();
expect(screen.getByTestId("console-tab-new")).toBeInTheDocument();
});
it("opens an ended session's replay for ?session=, hiding (not unmounting) the console", async () => {
@@ -5404,112 +5404,33 @@ exports[`screen snapshots > Run 1`] = `
data-testid="lane-detail"
>
<div
class="flex min-h-0 flex-1 flex-col gap-2 "
class="flex min-h-0 flex-1 flex-col gap-2"
>
<div
class="contents"
>
<div
class="contents"
class="flex min-h-0 flex-1 flex-col"
>
<div
class="flex min-h-0 flex-1 flex-col gap-5"
data-testid="console-body"
class="flex min-h-0 flex-1 flex-col border border-border bg-surface-0 p-2 rounded-lg"
>
<header
class="flex items-center gap-2.5"
<div
class="contents"
>
<div
class="w-7 h-7 rounded-lg bg-accent/15 flex items-center justify-center flex-shrink-0"
>
<svg
class="lucide lucide-play w-3.5 h-3.5 text-accent"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="6 3 20 12 6 21 6 3"
/>
</svg>
</div>
<div
class="flex min-w-0 flex-1 items-center gap-2"
class="flex min-h-0 flex-1 flex-col gap-3"
data-testid="console-body"
>
<div
class="flex shrink-0 items-center gap-2"
class="flex flex-1 flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border p-6 text-center text-sm text-fg-muted"
data-testid="pane-no-run"
>
<h1
class="text-base font-semibold text-fg-primary"
>
Run Claude
</h1>
<span
class="flex items-center gap-1.5 text-[11px] text-status-success bg-status-success/10 border border-status-success/20 px-2 py-0.5 rounded-full"
>
<span
class="w-1.5 h-1.5 rounded-full bg-status-success animate-pulse-dot"
/>
Live
</span>
<p>
No session in this pane. Start one from the workspace's prompt box.
</p>
</div>
<p
class="hidden min-w-0 truncate text-[11px] text-fg-muted sm:block"
>
Streamed output, multi-turn, same hooks and analytics as your terminal.
</p>
</div>
<button
class="inline-flex items-center gap-2 rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed border-border bg-surface-2 text-fg-secondary hover:bg-surface-3"
disabled=""
>
<svg
class="lucide lucide-list-ordered w-3.5 h-3.5"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 12h11"
/>
<path
d="M10 18h11"
/>
<path
d="M10 6h11"
/>
<path
d="M4 10h2"
/>
<path
d="M4 6h1v4"
/>
<path
d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"
/>
</svg>
Active runs
</button>
</header>
<div
class="flex flex-1 flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border p-6 text-center text-sm text-fg-muted"
data-testid="pane-no-run"
>
<p>
No session in this pane. Start one from the workspace's prompt box.
</p>
</div>
</div>
</div>
@@ -407,7 +407,6 @@ vi.mock("../../lib/api", async (importOriginal) => {
},
run: {
list: r({ runs: [], items: [] }),
history: r({ items: [] }),
binary: r({ found: true, path: "/usr/bin/claude" }),
cwds: r({ items: [] }),
files: r({ items: [] }),