feat(dashboard): hoist Active Agents panel above main list, show next heartbeat

Move ActiveAgentsPanel between AgentMetricsBar and the main agent
collection so live work is visible without scrolling past the whole
agent directory. Add a "Next heartbeat in Xs" line to each card
footer (or "Heartbeat overdue" when negative), driven by
runtimeConfig.heartbeatIntervalMs with the dashboard default fallback.
Give cards a pointer cursor + hover/focus styling so the existing
click-to-select behavior is discoverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-29 14:26:58 -07:00
parent 5aad943562
commit e946ce9a89
4 changed files with 62 additions and 9 deletions

View File

@@ -249,7 +249,7 @@ describe("AgentsView", () => {
});
});
it("renders metrics above the main collection and active panel after it", async () => {
it("renders metrics, then active panel, then the main collection", async () => {
const { container } = render(<AgentsView addToast={mockAddToast} />);
await waitFor(() => {
@@ -262,10 +262,11 @@ describe("AgentsView", () => {
const metrics = container.querySelector(".agent-metrics-bar");
const activePanel = container.querySelector(".active-agents-panel");
expect(list && metrics && activePanel).toBeTruthy();
// Metrics bar sits above the agent list (top-of-view stats placement).
expect(metrics!.compareDocumentPosition(list!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
// Active agents panel comes after the main agent collection.
expect(list!.compareDocumentPosition(activePanel!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
// Metrics bar sits at the very top of the view content.
expect(metrics!.compareDocumentPosition(activePanel!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
// Active agents panel sits between metrics and the main agent list,
// so live work is visible without scrolling past the full directory.
expect(activePanel!.compareDocumentPosition(list!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});
it("fetches agents only once on mount (regression: no duplicate initial load path)", async () => {