feat(FN-2932): improve active agent visibility and grouped tool-call UX

- Preserve and emphasize active agent card highlights across hover and modal states, with dedicated CSS updates and regression tests
- Expand dashboard and TUI activity surfaces for grouped tool-call and live agent status/log visibility, including Quick Chat and chat view refinements
- Add route and runtime updates for agent/settings flows plus self-healing coverage for merge recovery behavior
- Include accompanying changesets and test stabilizations for mission activity assertions and active highlight hooks

Fusion-Task-Id: FN-2932
This commit is contained in:
Fusion
2026-04-29 13:28:54 -07:00
committed by gsxdsm
parent 48208db9b0
commit 23e2c2514e
5 changed files with 91 additions and 30 deletions

View File

@@ -81,6 +81,11 @@
background: var(--card-hover);
}
.agent-list-modal .agent-card[data-state="active"]:hover,
.agent-list-modal .agent-card[data-state="running"]:hover {
background: color-mix(in srgb, var(--state-active-border) 20%, var(--card-hover));
}
.agent-list-modal .agent-card:focus-within {
box-shadow: var(--focus-ring);
}
@@ -150,7 +155,10 @@
.agent-list-modal .agent-board-card[data-state="idle"] { border-top-color: var(--state-idle-border); }
.agent-list-modal .agent-board-card[data-state="active"],
.agent-list-modal .agent-board-card[data-state="running"] { border-top-color: var(--state-active-border); }
.agent-list-modal .agent-board-card[data-state="running"] {
background: var(--state-active-bg);
border-top-color: var(--state-active-border);
}
.agent-list-modal .agent-board-card[data-state="paused"] { border-top-color: var(--state-paused-border); }
.agent-list-modal .agent-board-card[data-state="error"],
.agent-list-modal .agent-board-card[data-state="terminated"] { border-top-color: var(--state-error-border); }
@@ -209,7 +217,10 @@
.agent-list-modal .agent-card[data-state="idle"] { border-left-color: var(--state-idle-border); }
.agent-list-modal .agent-card[data-state="active"],
.agent-list-modal .agent-card[data-state="running"] { border-left-color: var(--state-active-border); }
.agent-list-modal .agent-card[data-state="running"] {
background: var(--state-active-bg);
border-left-color: var(--state-active-border);
}
.agent-list-modal .agent-card[data-state="paused"] { border-left-color: var(--state-paused-border); }
.agent-list-modal .agent-card[data-state="error"],
.agent-list-modal .agent-card[data-state="terminated"] { border-left-color: var(--state-error-border); }

View File

@@ -391,6 +391,11 @@
border-color: var(--text-muted);
}
.agent-board-card--active:hover,
.agent-board-card--running:hover {
background: color-mix(in srgb, var(--state-active-border) 20%, var(--card-hover));
}
.agent-board-header {
display: flex;
align-items: center;
@@ -676,6 +681,11 @@
border-left-color: var(--state-active-border);
background: var(--state-active-bg);
}
.agent-card--active:hover,
.agent-card--running:hover {
background: color-mix(in srgb, var(--state-active-border) 20%, var(--card-hover));
}
.agent-card--paused { border-left-color: var(--state-paused-border); }
.agent-card--error,
.agent-card--terminated { border-left-color: var(--state-error-border); }

View File

@@ -201,6 +201,33 @@ describe("AgentListModal", () => {
});
});
it("marks active cards with data-state in list and board views", async () => {
render(
<AgentListModal
isOpen={true}
onClose={mockOnClose}
addToast={mockAddToast}
projectId={TEST_PROJECT_ID}
/>
);
const activeName = await screen.findByText("Test Agent 2");
const activeListCard = activeName.closest(".agent-card");
expect(activeListCard?.getAttribute("data-state")).toBe("active");
fireEvent.click(screen.getByTitle("Board view"));
await waitFor(() => {
expect(document.querySelector(".agent-board")).toBeTruthy();
});
const activeBoardCard = Array.from(document.querySelectorAll(".agent-board-card")).find((card) =>
card.textContent?.includes("Test Agent 2"),
);
expect(activeBoardCard?.getAttribute("data-state")).toBe("active");
localStorage.removeItem(AGENT_VIEW_KEY);
});
it("shows terminated agents when explicitly filtered", async () => {
render(
<AgentListModal

View File

@@ -342,40 +342,53 @@ describe("AgentsView", () => {
});
});
it.each(["active", "running"] as const)("applies active highlight state classes across views for %s agents", async (state) => {
const highlightAgent: Agent = {
id: `agent-highlight-${state}`,
name: `Highlight ${state}`,
role: "executor",
state,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
metadata: {},
};
describe("active agent card highlight", () => {
it.each(["active", "running"] as const)("applies active highlight state classes across views for %s agents", async (state) => {
const highlightAgent: Agent = {
id: `agent-highlight-${state}`,
name: `Highlight ${state}`,
role: "executor",
state,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
metadata: {},
};
mockFetchAgents.mockResolvedValue([highlightAgent]);
mockFetchAgentStats.mockResolvedValue({ total: 1, byState: { [state]: 1 }, byRole: { executor: 1 } });
mockFetchOrgTree.mockResolvedValue([{ agent: highlightAgent, children: [] }]);
mockFetchAgents.mockResolvedValue([highlightAgent]);
mockFetchAgentStats.mockResolvedValue({ total: 1, byState: { [state]: 1 }, byRole: { executor: 1 } });
mockFetchOrgTree.mockResolvedValue([{ agent: highlightAgent, children: [] }]);
render(<AgentsView addToast={mockAddToast} />);
render(<AgentsView addToast={mockAddToast} />);
await waitFor(() => {
expect(document.querySelector(`.agent-card--${state}`)).toBeTruthy();
await waitFor(() => {
expect(document.querySelector(`.agent-card--${state}`)).toBeTruthy();
});
fireEvent.click(screen.getByRole("button", { name: "Board view" }));
await waitFor(() => {
expect(document.querySelector(`.agent-board-card--${state}`)).toBeTruthy();
});
fireEvent.click(screen.getByRole("button", { name: "Tree view" }));
await waitFor(() => {
expect(document.querySelector(`.agent-tree__node--${state}`)).toBeTruthy();
});
fireEvent.click(screen.getByRole("button", { name: "Org Chart view" }));
await waitFor(() => {
expect(document.querySelector(`.org-chart-node-card--${state}`)).toBeTruthy();
});
});
fireEvent.click(screen.getByRole("button", { name: "Board view" }));
await waitFor(() => {
expect(document.querySelector(`.agent-board-card--${state}`)).toBeTruthy();
});
it("keeps paused agents out of active highlight classes", async () => {
render(<AgentsView addToast={mockAddToast} />);
fireEvent.click(screen.getByRole("button", { name: "Tree view" }));
await waitFor(() => {
expect(document.querySelector(`.agent-tree__node--${state}`)).toBeTruthy();
});
const pausedAgentCard = await screen.findByText("Test Agent 3");
const pausedCard = pausedAgentCard.closest(".agent-card");
fireEvent.click(screen.getByRole("button", { name: "Org Chart view" }));
await waitFor(() => {
expect(document.querySelector(`.org-chart-node-card--${state}`)).toBeTruthy();
expect(pausedCard).toBeTruthy();
expect(pausedCard?.classList.contains("agent-card--paused")).toBe(true);
expect(pausedCard?.classList.contains("agent-card--active")).toBe(false);
});
});

View File

@@ -950,7 +950,7 @@ describe("MissionManager", () => {
fireEvent.click(screen.getByTestId("mission-activity-load-more"));
await screen.findByText("Mission event 65", undefined, { timeout: 10_000 });
await screen.findByText("Mission event 51", undefined, { timeout: 10_000 });
await waitFor(() => {
expect(screen.queryByTestId("mission-activity-load-more")).toBeNull();