FN-6686: canonicalize Command Center CSS tokens
Canonicalize Command Center CSS token references so chart accents and primary text no longer rely on undefined aliases. - Replace Command Center uses of `--color-accent` with `--accent` and `--text-primary` with `--text` across shell, chart, area, and control styles. - Add a scoped CSS token canonicalization regression test for Command Center styles. - Document the Command Center token-only rule and add a patch changeset for the published package. Files changed: .changeset/fn-6686-command-center-css-token-fix.md | 5 +++ docs/dashboard-guide.md | 2 +- .../components/command-center/CommandCenter.css | 28 +++++++-------- .../components/command-center/DateRangePicker.css | 2 +- .../command-center/MissionControlPanel.css | 2 +- ...mmand-center-css-token-canonicalization.test.ts | 40 ++++++++++++++++++++++ .../command-center/areas/SystemStatsArea.css | 4 +-- .../app/components/command-center/areas/areas.css | 14 ++++---- .../components/command-center/charts/charts.css | 39 ++++++++++++--------- 9 files changed, 93 insertions(+), 43 deletions(-) Fusion-Task-Id: FN-6686 Fusion-Task-Lineage: 10a79117-9716-4228-b2db-78791ec73d0d
This commit is contained in:
5
.changeset/fn-6686-command-center-css-token-fix.md
Normal file
5
.changeset/fn-6686-command-center-css-token-fix.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Fix Command Center charts and shell styling to use the canonical `--accent` and `--text` dashboard tokens instead of undefined `--color-accent` and `--text-primary` aliases, so chart accents and primary text render with the intended colors.
|
||||
@@ -1255,7 +1255,7 @@ The `index.html` shell is templated server-side: the server injects a per-user `
|
||||
|
||||
**Always reference tokens. Never hardcode pixels, hex, or `rgba()` in component CSS** — global/theme token CSS is also covered by `global-theme-css-no-raw-rgba.test.ts`, so raw `rgba()` belongs only in explicit `var(--token, rgba(...))` fallbacks. For translucent backgrounds use `color-mix(in srgb, var(--color) X%, transparent)`, not `rgba()`.
|
||||
|
||||
Command Center chart surfaces are a stricter token-only zone: `CommandCenter.css`, `areas/areas.css`, and `charts/charts.css` should avoid raw color fallbacks and hardcoded dimensions in component rules, keep secondary copy on `--text-muted`, use `--duration-*` for animation durations, and encode mobile chart invariants with shared classes rather than one-off area styles.
|
||||
Command Center chart surfaces are a stricter token-only zone: `CommandCenter.css`, `areas/areas.css`, and `charts/charts.css` should avoid raw color fallbacks and hardcoded dimensions in component rules, keep secondary copy on `--text-muted`, use canonical `--accent` / `--text` for generic accent and primary text styling, use `--duration-*` for animation durations, and encode mobile chart invariants with shared classes rather than one-off area styles. The undefined `--color-accent` / `--text-primary` aliases are forbidden under `components/command-center/**` and guarded by `command-center-css-token-canonicalization.test.ts`.
|
||||
|
||||
### Theme system
|
||||
|
||||
|
||||
@@ -59,12 +59,12 @@ The Command Center must remain scrollable on mobile inside the overflow-hidden .
|
||||
}
|
||||
|
||||
.cc-tab:hover {
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cc-tab.active {
|
||||
color: var(--text-primary);
|
||||
border-bottom-color: var(--color-accent);
|
||||
color: var(--text);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
.cc-tabpanel {
|
||||
@@ -121,7 +121,7 @@ FN-6680 standardizes the Command Center card rhythm across overview, area, table
|
||||
.cc-stat-value {
|
||||
font-size: var(--font-size-xl);
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cc-stat-card--gauge {
|
||||
@@ -182,9 +182,9 @@ FN-6680 keeps the FN-6664 live/chart shrink contract but replaces hardcoded trac
|
||||
border: var(--border-width) solid var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
background:
|
||||
linear-gradient(135deg, color-mix(in srgb, var(--color-accent) 14%, transparent), transparent),
|
||||
linear-gradient(135deg, color-mix(in srgb, var(--accent) 14%, transparent), transparent),
|
||||
var(--surface-1);
|
||||
box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--color-accent) 18%, transparent);
|
||||
box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
overflow: hidden;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
@@ -193,7 +193,7 @@ FN-6680 keeps the FN-6664 live/chart shrink contract but replaces hardcoded trac
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--color-accent) 24%, transparent), transparent);
|
||||
background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 24%, transparent), transparent);
|
||||
opacity: 0.45;
|
||||
transform: translateX(-100%);
|
||||
animation: cc-live-signal-sweep calc(var(--duration-slow) * 8) linear infinite;
|
||||
@@ -215,7 +215,7 @@ FN-6680 keeps the FN-6664 live/chart shrink contract but replaces hardcoded trac
|
||||
}
|
||||
|
||||
.cc-live-strip-label {
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ FN-6680 keeps the FN-6664 live/chart shrink contract but replaces hardcoded trac
|
||||
}
|
||||
|
||||
.cc-live-metric-value {
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
@@ -283,7 +283,7 @@ Overview token totals now live-poll and should visibly count up on change in bot
|
||||
}
|
||||
|
||||
.cc-live-trend .cc-sparkline-bar {
|
||||
box-shadow: 0 0 var(--space-2) color-mix(in srgb, var(--color-accent) 28%, transparent);
|
||||
box-shadow: 0 0 var(--space-2) color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
animation: cc-live-signal-pulse calc(var(--duration-slow) * 5) ease-in-out infinite;
|
||||
}
|
||||
|
||||
@@ -337,9 +337,9 @@ Overview charts must use dashboard tokens only and keep motion decorative; anima
|
||||
border: var(--border-width) solid var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
background:
|
||||
linear-gradient(145deg, color-mix(in srgb, var(--color-accent) 10%, transparent), transparent),
|
||||
linear-gradient(145deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent),
|
||||
var(--surface-1);
|
||||
box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--color-accent) 12%, transparent);
|
||||
box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--accent) 12%, transparent);
|
||||
overflow: hidden;
|
||||
animation: cc-overview-chart-rise var(--duration-normal) ease-out both;
|
||||
}
|
||||
@@ -352,7 +352,7 @@ Overview charts must use dashboard tokens only and keep motion decorative; anima
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--color-accent) 16%, transparent), transparent);
|
||||
background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 16%, transparent), transparent);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
animation: cc-overview-chart-sheen calc(var(--duration-slow) * 7) ease-in-out infinite;
|
||||
@@ -397,7 +397,7 @@ Overview recharts cards use token-derived height so ResponsiveContainer can rend
|
||||
|
||||
.cc-overview-chart-card .cc-bar-fill,
|
||||
.cc-overview-chart-card .cc-sparkline-bar {
|
||||
box-shadow: 0 0 var(--space-2) color-mix(in srgb, var(--color-accent) 30%, transparent);
|
||||
box-shadow: 0 0 var(--space-2) color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
}
|
||||
|
||||
@keyframes cc-overview-chart-rise {
|
||||
|
||||
@@ -57,6 +57,6 @@ Raw rgba fallbacks are replaced with byte-equivalent concrete-hex color-mix fall
|
||||
background: var(--surface-2, color-mix(in srgb, #7f7f7f 12%, transparent));
|
||||
border: 1px solid var(--border-subtle, color-mix(in srgb, #7f7f7f 25%, transparent));
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
color: var(--text-primary, #ddd);
|
||||
color: var(--text);
|
||||
padding: var(--space-1, 0.25rem) var(--space-2, 0.5rem);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ Raw rgba fallbacks are replaced with byte-equivalent concrete-hex color-mix fall
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--radius-sm, 6px);
|
||||
background: var(--surface-2, color-mix(in srgb, #3b82f6 15%, transparent));
|
||||
color: var(--text-primary, #ddd);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cc-mc-badge.inactive {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const COMMAND_CENTER_ROOT = path.resolve(__dirname, "..");
|
||||
|
||||
function collectCssFiles(dir: string): string[] {
|
||||
const out: string[] = [];
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const fullPath = path.join(dir, entry);
|
||||
const stats = statSync(fullPath);
|
||||
if (stats.isDirectory()) {
|
||||
out.push(...collectCssFiles(fullPath));
|
||||
continue;
|
||||
}
|
||||
if (entry.endsWith(".css")) {
|
||||
out.push(path.relative(COMMAND_CENTER_ROOT, fullPath).split(path.sep).join("/"));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe("Command Center CSS token canonicalization", () => {
|
||||
it("keeps undefined accent and primary text aliases out of Command Center CSS", () => {
|
||||
const offenders: string[] = [];
|
||||
for (const relPath of collectCssFiles(COMMAND_CENTER_ROOT)) {
|
||||
const content = readFileSync(path.join(COMMAND_CENTER_ROOT, relPath), "utf8");
|
||||
if (/--(?:color-accent|text-primary)\b/.test(content)) offenders.push(relPath);
|
||||
}
|
||||
|
||||
expect(offenders, `Unexpected undefined Command Center token aliases in: ${offenders.join(", ")}`).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps chart primitives wired to canonical accent and text tokens", () => {
|
||||
const chartsCss = readFileSync(path.join(COMMAND_CENTER_ROOT, "charts/charts.css"), "utf8");
|
||||
|
||||
expect(chartsCss).toContain("var(--accent)");
|
||||
expect(chartsCss).toContain("var(--text)");
|
||||
});
|
||||
});
|
||||
@@ -72,12 +72,12 @@ System control cards sit beside chart/stat cards, so FN-6680 gives them the same
|
||||
|
||||
.cc-system-toggle-row,
|
||||
.cc-system-threshold-row {
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.cc-system-threshold-controls input[type="range"] {
|
||||
accent-color: var(--color-accent);
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.cc-system-threshold-controls .input {
|
||||
|
||||
@@ -76,9 +76,9 @@ The Tokens area needs a real hour/day/week control and live token-number motion.
|
||||
}
|
||||
|
||||
.cc-token-granularity .btn.active {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--text-primary);
|
||||
background: color-mix(in srgb, var(--color-accent) 14%, var(--surface-1));
|
||||
border-color: var(--accent);
|
||||
color: var(--text);
|
||||
background: color-mix(in srgb, var(--accent) 14%, var(--surface-1));
|
||||
}
|
||||
|
||||
.cc-token-count-live {
|
||||
@@ -159,7 +159,7 @@ FN-6680 keeps table-heavy chart areas on the same --border-width/--border-subtle
|
||||
}
|
||||
|
||||
.cc-table th.cc-sortable:hover {
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cc-table tbody tr {
|
||||
@@ -171,13 +171,13 @@ FN-6680 keeps table-heavy chart areas on the same --border-width/--border-subtle
|
||||
}
|
||||
|
||||
.cc-table tbody tr.cc-row-selected td {
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cc-sort-caret {
|
||||
margin-inline-start: var(--space-1);
|
||||
font-size: 0.7em;
|
||||
color: var(--color-accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Unavailable sentinel ("—") with a help cursor for its tooltip. */
|
||||
@@ -264,7 +264,7 @@ The Team view must use dashboard design tokens only, preserve .cc-tabpanel as th
|
||||
}
|
||||
|
||||
.cc-team-agent-name {
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
FNXC:CommandCenterStyling 2026-06-19-00:00:
|
||||
FN-6686 requires Command Center CSS to use canonical --accent and --text tokens. Undefined legacy accent/text-primary aliases made chart primitives inherit fallback colors; the scoped token guard prevents reintroducing those aliases after the FN-6682 review finding.
|
||||
*/
|
||||
|
||||
/*
|
||||
FNXC:CommandCenterStyling 2026-06-17-18:46:
|
||||
Chart labels and legends must use --text-muted so command-center CSS stays aligned with the FN-4286 canonical text-token guard and avoids raw color fallbacks.
|
||||
@@ -65,7 +70,7 @@ FN-6680 re-checked FN-6664 in a real Blink layout engine because jsdom does not
|
||||
|
||||
.cc-bar-fill {
|
||||
block-size: 100%;
|
||||
background: var(--color-accent);
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: width var(--transition-normal);
|
||||
}
|
||||
@@ -74,7 +79,7 @@ FN-6680 re-checked FN-6664 in a real Blink layout engine because jsdom does not
|
||||
min-inline-size: 0;
|
||||
font-size: var(--font-size-sm);
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
overflow-wrap: anywhere;
|
||||
text-align: end;
|
||||
}
|
||||
@@ -96,7 +101,7 @@ FN-6680 re-checked FN-6664 in a real Blink layout engine because jsdom does not
|
||||
|
||||
.cc-stacked-segment {
|
||||
block-size: 100%;
|
||||
background: var(--color-accent);
|
||||
background: var(--accent);
|
||||
transition: width var(--transition-normal);
|
||||
}
|
||||
|
||||
@@ -123,7 +128,7 @@ FN-6680 re-checked FN-6664 in a real Blink layout engine because jsdom does not
|
||||
inline-size: var(--space-2);
|
||||
block-size: var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-accent);
|
||||
background: var(--accent);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -138,7 +143,7 @@ FN-6680 re-checked FN-6664 in a real Blink layout engine because jsdom does not
|
||||
.cc-sparkline-bar {
|
||||
flex: 1 1 0;
|
||||
min-inline-size: var(--border-width);
|
||||
background: var(--color-accent);
|
||||
background: var(--accent);
|
||||
border-radius: var(--border-width);
|
||||
transition: height var(--transition-normal);
|
||||
}
|
||||
@@ -163,7 +168,7 @@ The token-over-time chart is live-updated and animated, but the motion is decora
|
||||
padding: var(--space-3);
|
||||
border: var(--border-width) solid var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--color-accent) 10%, transparent), transparent), var(--surface-1);
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent), var(--surface-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -171,8 +176,8 @@ The token-over-time chart is live-updated and animated, but the motion is decora
|
||||
flex: 1 1 0;
|
||||
min-inline-size: var(--space-1);
|
||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||
background: linear-gradient(180deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 45%, var(--surface-2)));
|
||||
box-shadow: 0 0 var(--space-2) color-mix(in srgb, var(--color-accent) 24%, transparent);
|
||||
background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 45%, var(--surface-2)));
|
||||
box-shadow: 0 0 var(--space-2) color-mix(in srgb, var(--accent) 24%, transparent);
|
||||
transition: height var(--transition-normal);
|
||||
animation: cc-token-series-rise var(--duration-normal) ease-out both;
|
||||
}
|
||||
@@ -240,12 +245,12 @@ Line-chart motion is decorative, token-timed, and disabled for reduced-motion us
|
||||
inline-size: 100%;
|
||||
block-size: clamp(var(--space-16), 22vw, calc(var(--space-20) * 2));
|
||||
aspect-ratio: 5 / 2;
|
||||
color: var(--color-accent);
|
||||
color: var(--accent);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cc-line-chart-series {
|
||||
color: var(--color-accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.cc-line-chart-series:nth-child(2n) {
|
||||
@@ -299,7 +304,7 @@ Line-chart motion is decorative, token-timed, and disabled for reduced-motion us
|
||||
display: grid;
|
||||
place-items: center;
|
||||
gap: var(--space-2);
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cc-radial-gauge-ring {
|
||||
@@ -311,8 +316,8 @@ Line-chart motion is decorative, token-timed, and disabled for reduced-motion us
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(circle at center, var(--surface-1) 0 54%, transparent 55%),
|
||||
conic-gradient(var(--color-accent) var(--cc-radial-value), var(--surface-2) 0);
|
||||
box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--color-accent) 35%, transparent);
|
||||
conic-gradient(var(--accent) var(--cc-radial-value), var(--surface-2) 0);
|
||||
box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--accent) 35%, transparent);
|
||||
isolation: isolate;
|
||||
animation: cc-radial-gauge-pulse calc(var(--duration-slow) * 6) ease-in-out infinite;
|
||||
}
|
||||
@@ -322,8 +327,8 @@ Line-chart motion is decorative, token-timed, and disabled for reduced-motion us
|
||||
position: absolute;
|
||||
inset: var(--space-2);
|
||||
border-radius: inherit;
|
||||
border: var(--border-width) solid color-mix(in srgb, var(--color-accent) 45%, transparent);
|
||||
box-shadow: inset 0 0 var(--space-3) color-mix(in srgb, var(--color-accent) 22%, transparent);
|
||||
border: var(--border-width) solid color-mix(in srgb, var(--accent) 45%, transparent);
|
||||
box-shadow: inset 0 0 var(--space-3) color-mix(in srgb, var(--accent) 22%, transparent);
|
||||
animation: cc-radial-gauge-sweep calc(var(--duration-slow) * 8) linear infinite;
|
||||
}
|
||||
|
||||
@@ -429,7 +434,7 @@ Line-chart motion is decorative, token-timed, and disabled for reduced-motion us
|
||||
|
||||
.cc-funnel-fill {
|
||||
block-size: 100%;
|
||||
background: var(--color-accent);
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: width var(--transition-normal);
|
||||
}
|
||||
@@ -437,7 +442,7 @@ Line-chart motion is decorative, token-timed, and disabled for reduced-motion us
|
||||
.cc-funnel-value {
|
||||
font-size: var(--font-size-sm);
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--text-primary);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* ---- Loading shimmer (used by chart skeletons) ---- */
|
||||
|
||||
Reference in New Issue
Block a user