From a69e66b54b888ffb9c8af7d3c7bb842797414023 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 23 Jul 2026 12:40:10 -0700 Subject: [PATCH] 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) --- .../dashboard/app/components/AgentsView.css | 38 ++++++++++++++++++ .../components/__tests__/AgentsView.test.tsx | 39 +++++++++++++++++++ .../__tests__/agents-view-mobile.test.tsx | 12 ++++++ 3 files changed, 89 insertions(+) diff --git a/packages/dashboard/app/components/AgentsView.css b/packages/dashboard/app/components/AgentsView.css index c7d306c698..fd77e6d5d8 100644 --- a/packages/dashboard/app/components/AgentsView.css +++ b/packages/dashboard/app/components/AgentsView.css @@ -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; + } } diff --git a/packages/dashboard/app/components/__tests__/AgentsView.test.tsx b/packages/dashboard/app/components/__tests__/AgentsView.test.tsx index 5bdcc9575d..7fedde9290 100644 --- a/packages/dashboard/app/components/__tests__/AgentsView.test.tsx +++ b/packages/dashboard/app/components/__tests__/AgentsView.test.tsx @@ -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(); + + const card = await waitFor(() => { + const renderedCard = container.querySelector(".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(); diff --git a/packages/dashboard/app/components/__tests__/agents-view-mobile.test.tsx b/packages/dashboard/app/components/__tests__/agents-view-mobile.test.tsx index 71d75d8642..81eee4067b 100644 --- a/packages/dashboard/app/components/__tests__/agents-view-mobile.test.tsx +++ b/packages/dashboard/app/components/__tests__/agents-view-mobile.test.tsx @@ -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)");