diff --git a/frontend/src/components/AgentPanel.test.tsx b/frontend/src/components/AgentPanel.test.tsx
index f9c79dd..da49af1 100644
--- a/frontend/src/components/AgentPanel.test.tsx
+++ b/frontend/src/components/AgentPanel.test.tsx
@@ -374,3 +374,74 @@ describe("AgentPanel fade-out", () => {
});
});
});
+
+describe("RosterBadge availability state", () => {
+ beforeAll(() => {
+ Element.prototype.scrollIntoView = vi.fn();
+ });
+
+ beforeEach(() => {
+ mockedAgents.getAgentConfig.mockResolvedValue(ROSTER);
+ mockedAgents.listAgents.mockResolvedValue([]);
+ });
+
+ it("shows a green dot for an idle agent", async () => {
+ render();
+
+ const dot = await screen.findByTestId("roster-dot-coder-1");
+ // JSDOM normalizes #3fb950 to rgb(63, 185, 80)
+ expect(dot.style.background).toBe("rgb(63, 185, 80)");
+ expect(dot.style.animation).toBe("");
+ });
+
+ it("shows green badge styling for an idle agent", async () => {
+ render();
+
+ const badge = await screen.findByTestId("roster-badge-coder-1");
+ // JSDOM normalizes #3fb95015 to rgba(63, 185, 80, 0.082) and #3fb950 to rgb(63, 185, 80)
+ expect(badge.style.background).toBe("rgba(63, 185, 80, 0.082)");
+ expect(badge.style.color).toBe("rgb(63, 185, 80)");
+ });
+
+ it("shows a blue pulsing dot for an active agent", async () => {
+ const agentList: AgentInfo[] = [
+ {
+ story_id: "81_active",
+ agent_name: "coder-1",
+ status: "running",
+ session_id: null,
+ worktree_path: null,
+ base_branch: null,
+ },
+ ];
+ mockedAgents.listAgents.mockResolvedValue(agentList);
+
+ render();
+
+ const dot = await screen.findByTestId("roster-dot-coder-1");
+ // JSDOM normalizes #58a6ff to rgb(88, 166, 255)
+ expect(dot.style.background).toBe("rgb(88, 166, 255)");
+ expect(dot.style.animation).toBe("pulse 1.5s infinite");
+ });
+
+ it("shows blue badge styling for an active agent", async () => {
+ const agentList: AgentInfo[] = [
+ {
+ story_id: "81_active",
+ agent_name: "coder-1",
+ status: "running",
+ session_id: null,
+ worktree_path: null,
+ base_branch: null,
+ },
+ ];
+ mockedAgents.listAgents.mockResolvedValue(agentList);
+
+ render();
+
+ const badge = await screen.findByTestId("roster-badge-coder-1");
+ // JSDOM normalizes #58a6ff18 to rgba(88, 166, 255, 0.094) and #58a6ff to rgb(88, 166, 255)
+ expect(badge.style.background).toBe("rgba(88, 166, 255, 0.094)");
+ expect(badge.style.color).toBe("rgb(88, 166, 255)");
+ });
+});
diff --git a/frontend/src/components/AgentPanel.tsx b/frontend/src/components/AgentPanel.tsx
index 154edaa..0cd44ae 100644
--- a/frontend/src/components/AgentPanel.tsx
+++ b/frontend/src/components/AgentPanel.tsx
@@ -105,19 +105,20 @@ function RosterBadge({
padding: "2px 8px",
borderRadius: "6px",
fontSize: "0.7em",
- background: isActive ? "#58a6ff18" : "#ffffff08",
- color: isActive ? "#58a6ff" : "#888",
- border: isActive ? "1px solid #58a6ff44" : "1px solid #333",
+ background: isActive ? "#58a6ff18" : "#3fb95015",
+ color: isActive ? "#58a6ff" : "#3fb950",
+ border: isActive ? "1px solid #58a6ff44" : "1px solid #3fb95040",
transition: "background 0.3s, color 0.3s, border-color 0.3s",
}}
title={
isActive
? `Working on #${storyNumber ?? activeStoryId}`
- : `${agent.role || agent.name} — idle`
+ : `${agent.role || agent.name} — available`
}
>
{isActive && (
)}
-
+
{agent.name}
{agent.model && (
-
+
{agent.model}
)}
@@ -153,7 +157,7 @@ function RosterBadge({
)}
{!isActive && (
- idle
+ available
)}
);