diff --git a/packages/dashboard/app/components/AgentsView.css b/packages/dashboard/app/components/AgentsView.css index ec229e3500..eb0cea5a86 100644 --- a/packages/dashboard/app/components/AgentsView.css +++ b/packages/dashboard/app/components/AgentsView.css @@ -469,6 +469,10 @@ The base grid keeps a token-sized handle column as the no-JS fallback, while the gap: var(--space-lg); } +/* +FNXC:AgentsView 2026-06-20-03:25: +FN-6774 removes the saturated top-edge status stripe from agent board cards. Agent state remains legible through state chips, subtle card background tints, hover feedback, and the neutral card border. +*/ .agent-board-card { display: flex; flex-direction: column; @@ -476,7 +480,6 @@ The base grid keeps a token-sized handle column as the no-JS fallback, while the padding: var(--space-lg); background: var(--surface); border: 1px solid var(--border); - border-top-width: 3px; border-radius: var(--radius-md); transition: background var(--transition-fast), border-color var(--transition-fast); } @@ -568,17 +571,20 @@ The base grid keeps a token-sized handle column as the no-JS fallback, while the gap: var(--space-md); } +/* +FNXC:AgentsView 2026-06-20-03:25: +FN-6774 removes the saturated left-edge status stripe from split-sidebar agent cards. Selection uses a full-card ring and state uses subtle backgrounds/status chips so no single edge becomes the status indicator. +*/ .agent-card { border: 1px solid var(--border); - border-left-width: 4px; border-radius: var(--radius-md); padding: var(--space-lg); background: var(--surface); } .agent-card--selected { - border-left-color: var(--todo) !important; background: color-mix(in srgb, var(--todo) 8%, transparent); + box-shadow: inset 0 0 0 calc(var(--space-xs) / 4) var(--todo); } .agent-card-header { @@ -909,27 +915,41 @@ The base grid keeps a token-sized handle column as the no-JS fallback, while the border: 1px solid var(--state-error-border); } -.agent-board-card--idle, -.agent-board-card--active, -.agent-board-card--paused { border-top-color: var(--state-idle-border); } +.agent-board-card--active { + background: color-mix(in srgb, var(--state-active-border) 6%, var(--surface)); +} + +.agent-board-card--paused { + background: var(--state-paused-bg); +} + .agent-board-card--running { - border-top-color: var(--state-active-border); background: var(--state-active-bg); } -.agent-board-card--error { border-top-color: var(--state-error-border); } -.agent-card--idle, -.agent-card--active, -.agent-card--paused { border-left-color: var(--state-idle-border); } +.agent-board-card--error { + background: var(--state-error-bg); +} + +.agent-card--active { + background: color-mix(in srgb, var(--state-active-border) 6%, var(--surface)); +} + +.agent-card--paused { + background: var(--state-paused-bg); +} + .agent-card--running { - border-left-color: var(--state-active-border); background: var(--state-active-bg); } .agent-card--running:hover { background: color-mix(in srgb, var(--state-active-border) 20%, var(--card-hover)); } -.agent-card--error { border-left-color: var(--state-error-border); } + +.agent-card--error { + background: var(--state-error-bg); +} /* === FN-1167: Agent Org Chart + Chain of Command === */ .agent-org-chart-shell { diff --git a/packages/dashboard/app/components/ListView.css b/packages/dashboard/app/components/ListView.css index 6f0dd90efd..62abc29aea 100644 --- a/packages/dashboard/app/components/ListView.css +++ b/packages/dashboard/app/components/ListView.css @@ -515,8 +515,11 @@ opacity: 0.55; } +/* +FNXC:ListView 2026-06-20-03:25: +FN-6774 removes the saturated stuck-task edge stripe from list rows. Keep the subtle triage background and stuck status badge so stuck rows stay distinguishable without changing the neutral row border. +*/ .list-row.stuck { - border-left: 3px solid var(--triage); background: color-mix(in srgb, var(--triage) 8%, transparent); } diff --git a/packages/dashboard/app/components/TaskCard.css b/packages/dashboard/app/components/TaskCard.css index d432ae81fc..c7c4b9feda 100644 --- a/packages/dashboard/app/components/TaskCard.css +++ b/packages/dashboard/app/components/TaskCard.css @@ -337,8 +337,11 @@ The global mobile touch-action reset applies to descendants, and browsers inters background: color-mix(in srgb, var(--color-warning) 6%, transparent); } +/* +FNXC:TaskCard 2026-06-20-03:25: +FN-6774 removes the saturated stuck-task edge stripe from board cards. Keep the triage-tinted surface plus the stuck status badge so the state remains legible while the card uses the neutral board border. +*/ .card.stuck { - border-left: 3px solid var(--triage); background: color-mix(in srgb, var(--triage) 6%, transparent); } diff --git a/packages/dashboard/app/components/__tests__/stuck-and-agent-card-border.test.ts b/packages/dashboard/app/components/__tests__/stuck-and-agent-card-border.test.ts new file mode 100644 index 0000000000..135843448c --- /dev/null +++ b/packages/dashboard/app/components/__tests__/stuck-and-agent-card-border.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, it } from "vitest"; +import { loadAllAppCss } from "../../test/cssFixture"; + +const css = loadAllAppCss(); + +type CssRule = { + selector: string; + body: string; +}; + +function allRules(): CssRule[] { + return Array.from(css.matchAll(/([^{}]+)\{([^{}]*)\}/g), ([, selector, body]) => ({ + selector: selector.replace(/\/\*[\s\S]*?\*\//g, "").trim(), + body: body.trim(), + })); +} + +function selectorParts(selector: string): string[] { + return selector.split(",").map((part) => part.trim()); +} + +function rulesFor(selector: string): CssRule[] { + return allRules().filter((rule) => selectorParts(rule.selector).includes(selector)); +} + +function baseRule(selector: string): string { + const rule = rulesFor(selector).find((candidate) => !candidate.selector.startsWith("@")); + expect(rule, `${selector} should have a CSS rule`).toBeTruthy(); + return rule?.body ?? ""; +} + +function expectNoEmptyVariantShell(selector: string, axis: "border-left-color" | "border-top-color"): void { + for (const rule of rulesFor(selector)) { + const body = rule.body.trim(); + expect(body, `${selector} must not leave an empty CSS rule shell`).not.toBe(""); + expect(body, `${selector} must not keep a colored edge override`).not.toContain(axis); + expect( + /(background|box-shadow|border:\s*1px\s+solid\s+var\(--border\))/.test(body), + `${selector} rules should either be deleted or carry a non-stripe legibility declaration`, + ).toBe(true); + } +} + +describe("stuck task and agent card colored edge borders (FN-6774)", () => { + it("keeps stuck task board cards legible without a triage left stripe", () => { + const stuckCard = baseRule(".card.stuck"); + + expect(stuckCard).not.toContain("border-left: 3px solid var(--triage)"); + expect(stuckCard).not.toContain("border-left"); + expect(stuckCard).toContain("background: color-mix(in srgb, var(--triage) 6%, transparent)"); + expect(baseRule(".card-status-badge.stuck")).toContain("background: var(--status-triage-bg-deep)"); + }); + + it("keeps stuck list rows legible without a triage left stripe", () => { + const stuckRow = baseRule(".list-row.stuck"); + + expect(stuckRow).not.toContain("border-left: 3px solid var(--triage)"); + expect(stuckRow).not.toContain("border-left"); + expect(stuckRow).toContain("background: color-mix(in srgb, var(--triage) 8%, transparent)"); + expect(baseRule(".list-status-badge.stuck")).toContain("background: color-mix(in srgb, var(--triage) 20%, transparent)"); + }); + + it("uses neutral split-sidebar agent card borders across agent states", () => { + const agentCard = baseRule(".agent-card"); + const selectedCard = baseRule(".agent-card--selected"); + + expect(agentCard).toContain("border: 1px solid var(--border)"); + expect(agentCard).not.toContain("border-left-width: 4px"); + expect(selectedCard).not.toContain("border-left-color: var(--todo)"); + expect(selectedCard).not.toContain("!important"); + expect(selectedCard).toContain("box-shadow: inset 0 0 0 calc(var(--space-xs) / 4) var(--todo)"); + + for (const state of ["active", "paused", "running", "error"]) { + expectNoEmptyVariantShell(`.agent-card--${state}`, "border-left-color"); + } + + expect(css).not.toMatch(/\.agent-card--(?:idle|active|paused|running|error)\b[^{}]*\{[^}]*border-left-color/s); + }); + + it("uses neutral grid agent board card borders across agent states", () => { + const boardCard = baseRule(".agent-board-card"); + + expect(boardCard).toContain("border: 1px solid var(--border)"); + expect(boardCard).not.toContain("border-top-width: 3px"); + + for (const state of ["active", "paused", "running", "error"]) { + expectNoEmptyVariantShell(`.agent-board-card--${state}`, "border-top-color"); + } + + expect(css).not.toMatch(/\.agent-board-card--(?:idle|active|paused|running|error)\b[^{}]*\{[^}]*border-top-color/s); + }); +});