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 (
);