FN-8553: prevent agent card badge overlap

Keep agent identities and status badges readable across card widths.

- Wrap desktop and split-sidebar card headers without permitting text overflow.
- Stack identity and badge regions on narrow mobile cards.
- Cover long identities and populated badge layouts with regression tests.

Files changed:
 packages/dashboard/app/components/AgentsView.css   | 38 +++++++++++++++++++++
 .../app/components/__tests__/AgentsView.test.tsx   | 39 ++++++++++++++++++++++
 .../__tests__/agents-view-mobile.test.tsx          | 12 +++++++
 3 files changed, 89 insertions(+)

Fusion-Task-Id: FN-8553

Fusion-Task-Lineage: b0015f41-44e6-4cc9-8ec9-d9c0c61f7f18

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-23 12:40:10 -07:00
parent 0d355f371c
commit a69e66b54b
3 changed files with 89 additions and 0 deletions

View File

@@ -608,10 +608,16 @@ FN-6774 removes the saturated left-edge status stripe from split-sidebar agent c
box-shadow: inset 0 0 0 calc(var(--space-xs) / 4) var(--todo);
}
/*
FNXC:AgentsView 2026-07-23-12:26:
Agent-card identity and every visible status badge must remain in separate layout allocations at desktop, split-sidebar, and mobile widths. Let both regions shrink and wrap, while identity text breaks inside its own allocation, so long names or populated health/role/skill badges never paint over one another.
*/
.agent-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: var(--space-sm);
margin-bottom: var(--space-md);
}
@@ -619,6 +625,8 @@ FN-6774 removes the saturated left-edge status stripe from split-sidebar agent c
display: flex;
align-items: center;
gap: var(--space-md);
flex: 1 1 auto;
min-width: 0;
}
.agent-info--clickable {
@@ -680,6 +688,7 @@ FN-6774 removes the saturated left-edge status stripe from split-sidebar agent c
.agent-meta {
display: flex;
flex-direction: column;
min-width: 0;
}
.agent-name {
@@ -692,14 +701,29 @@ FN-6774 removes the saturated left-edge status stripe from split-sidebar agent c
font-family: var(--font-mono);
}
.agent-name,
.agent-id {
min-width: 0;
max-width: 100%;
overflow-wrap: anywhere;
}
.agent-badges {
display: flex;
flex: 0 1 auto;
gap: var(--space-sm);
flex-wrap: wrap;
min-width: 0;
max-width: 100%;
justify-content: flex-end;
}
.agent-badges .badge {
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
}
.agent-approval-badge {
display: inline-flex;
align-items: center;
@@ -712,6 +736,7 @@ FN-6774 removes the saturated left-edge status stripe from split-sidebar agent c
max-width: min(100%, calc(var(--space-2xl) * 6));
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.agent-card-chevron {
@@ -1582,4 +1607,17 @@ AgentsView uses the measured SVG overlay as the single connector system so paren
overscroll-behavior: contain;
padding: var(--space-md) var(--space-md) calc(var(--space-md) + env(safe-area-inset-bottom, 0px) + var(--standalone-bottom-gap));
}
.agents-view .agent-card-header {
flex-direction: column;
align-items: stretch;
}
.agents-view .agent-info {
min-width: 0;
}
.agents-view .agent-badges {
justify-content: flex-start;
}
}

View File

@@ -910,6 +910,45 @@ describe("AgentsView", () => {
expect(css).toContain(".agents-split-sidebar .agent-card-actions .agent-card-action-label {\n display: none;\n }");
});
it("keeps long agent identities and populated health badges readable in split-sidebar cards", async () => {
const collisionAgent: Agent = {
...mockAgents[1],
id: "agent-marketing-manager",
name: "Marketing Manager",
role: "custom",
state: "active",
runtimeConfig: { enabled: false },
metadata: {
skills: ["auto::skills/../../.agents/skills/brand-strategy/SKILL.md", "auto::skills/../../.agents/skills/campaign-analytics/SKILL.md"],
},
};
mockFetchAgents.mockResolvedValueOnce([collisionAgent]);
mockFetchAgentStats.mockResolvedValueOnce({ total: 1, byState: { active: 1 }, byRole: { custom: 1 } });
const { container } = renderView(<AgentsView addToast={mockAddToast} />);
const card = await waitFor(() => {
const renderedCard = container.querySelector<HTMLElement>(".agents-split-sidebar .agent-card");
expect(renderedCard).toBeTruthy();
return renderedCard!;
});
expect(within(card).getByText("Marketing Manager")).toBeInTheDocument();
expect(within(card).getByText("agent-marketing-manager")).toBeInTheDocument();
expect(within(card).getByText("active")).toBeInTheDocument();
expect(within(card).getByText("Heartbeat Disabled")).toBeInTheDocument();
expect(within(card).getByText("Custom")).toBeInTheDocument();
expect(within(card).getByText("brand-strategy")).toBeInTheDocument();
expect(within(card).getByText("campaign-analytics")).toBeInTheDocument();
const css = loadAllAppCss();
expect(css).toMatch(/\.agent-card-header\s*\{[^}]*flex-wrap:\s*wrap;[^}]*gap:\s*var\(--space-sm\);[^}]*\}/);
expect(css).toMatch(/\.agent-info\s*\{[^}]*flex:\s*1 1 auto;[^}]*min-width:\s*0;[^}]*\}/);
expect(css).toMatch(/\.agent-meta\s*\{[^}]*min-width:\s*0;[^}]*\}/);
expect(css).toMatch(/\.agent-name,\s*\.agent-id\s*\{[^}]*overflow-wrap:\s*anywhere;[^}]*\}/);
expect(css).toMatch(/\.agent-badges\s*\{[^}]*flex:\s*0 1 auto;[^}]*flex-wrap:\s*wrap;[^}]*min-width:\s*0;[^}]*\}/);
});
it("opens matching detail view when clicking View Details button", async () => {
renderView(<AgentsView addToast={mockAddToast} />);

View File

@@ -314,6 +314,18 @@ describe("agents-view mobile CSS", () => {
expect(block).toContain("flex-wrap: wrap");
});
it("stacks card identity and populated badges on narrow mobile cards", () => {
const headerBlock = extractRuleBlock(mobileMediaBlock, ".agents-view .agent-card-header");
expect(headerBlock).toContain("flex-direction: column");
expect(headerBlock).toContain("align-items: stretch");
const infoBlock = extractRuleBlock(mobileMediaBlock, ".agents-view .agent-info");
expect(infoBlock).toContain("min-width: 0");
const badgesBlock = extractRuleBlock(mobileMediaBlock, ".agents-view .agent-badges");
expect(badgesBlock).toContain("justify-content: flex-start");
});
it("defines mobile org chart sizing and pan/zoom controls rules", () => {
expect(extractRuleBlock(mobileMediaBlock, ".agent-org-chart-controls")).toContain("display: flex");
expect(extractRuleBlock(mobileMediaBlock, ".agent-org-chart-controls")).toContain("gap: var(--space-sm)");