diff --git a/apps/web/src/components/chat/RightPanelTabStrip.browser.tsx b/apps/web/src/components/chat/RightPanelTabStrip.browser.tsx index 27c435c1..80bf335d 100644 --- a/apps/web/src/components/chat/RightPanelTabStrip.browser.tsx +++ b/apps/web/src/components/chat/RightPanelTabStrip.browser.tsx @@ -1,6 +1,7 @@ import "../../index.css"; import { afterEach, describe, expect, it, vi } from "vite-plus/test"; +import { page } from "vite-plus/test/browser"; import { render } from "vitest-browser-react"; import { RightPanelTabStrip } from "./RightPanelTabStrip"; @@ -163,3 +164,41 @@ describe("RightPanelTabStrip drag reorder", () => { } }); }); + +describe("RightPanelTabStrip + menu", () => { + afterEach(() => { + document.body.innerHTML = ""; + }); + + it("draws a live tab's row with the live node instead of its icon", async () => { + const mounted = await render( +
+ +
, + ); + + try { + await page.getByRole("button", { name: "Open panel" }).click(); + + const agents = page.getByRole("menuitem", { name: "Agents" }); + await expect.element(agents).toHaveAttribute("data-right-panel-menu-tab-live", "true"); + expect(agents.element().querySelector(".thread-halo")).not.toBeNull(); + expect(agents.element().querySelector("svg")).toBeNull(); + + const diff = page.getByRole("menuitem", { name: "Diff" }); + await expect.element(diff).not.toHaveAttribute("data-right-panel-menu-tab-live"); + expect(diff.element().querySelector(".thread-halo")).toBeNull(); + expect(diff.element().querySelector("svg")).not.toBeNull(); + } finally { + await mounted.unmount(); + } + }); +}); diff --git a/apps/web/src/components/chat/RightPanelTabStrip.tsx b/apps/web/src/components/chat/RightPanelTabStrip.tsx index 5cb4143b..0fa520ed 100644 --- a/apps/web/src/components/chat/RightPanelTabStrip.tsx +++ b/apps/web/src/components/chat/RightPanelTabStrip.tsx @@ -9,7 +9,8 @@ * because there is a fixed set of them and each can only be open once. A * surface already in the strip stays listed and dimmed: choosing it focuses the * tab it already has. Empty surfaces stay clickable too, but use the same quiet - * treatment as their rows in the panel launcher. + * treatment as their rows in the panel launcher. A surface with live work (agents + * still running) swaps its icon for the same pulsing node its tab would show. * * The row shares its width with the Windows window-controls overlay, which * reserves ~150px of it, so the strip measures itself and drops every label @@ -853,6 +854,7 @@ export const RightPanelTabStrip = memo(function RightPanelTabStrip({ const surface = RIGHT_PANEL_SURFACES[tab]; const alreadyOpen = openTabs.includes(tab); const empty = surfaceStates?.[tab]?.empty ?? false; + const live = liveTabs?.includes(tab) ?? false; const MenuIcon = RIGHT_PANEL_TAB_ICONS[tab]; return ( onSelectTab(tab)} > - );