FN-6774: remove colored card edge accents
Remove saturated edge stripes from stuck task and agent cards while preserving state legibility. - Keep stuck task board cards and list rows on neutral borders with tinted backgrounds and existing status badges. - Replace agent card status edge accents with subtle state backgrounds and a selected-card inset ring. - Add CSS regression coverage to prevent colored edge-border variants from returning. Files changed: packages/dashboard/app/components/AgentsView.css | 46 ++++++++--- packages/dashboard/app/components/ListView.css | 5 +- packages/dashboard/app/components/TaskCard.css | 5 +- .../__tests__/stuck-and-agent-card-border.test.ts | 92 ++++++++++++++++++++++ 4 files changed, 133 insertions(+), 15 deletions(-) Fusion-Task-Id: FN-6774 Fusion-Task-Lineage: da49ca64-2c10-4594-888f-332cf4f9c8c8
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user