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)");