FN-6678: define dashboard surface tokens
Add missing dashboard theme tokens so surface and border styling resolves consistently across themes. - Define --surface-1, --surface-2, and --border-subtle in base and light dashboard theme blocks. - Keep the new tokens derived from existing theme variables with color-mix for dark and light mode consistency. - Add a dashboard CSS guard that requires these tokens in both theme blocks without raw color definitions. Files changed: .../dashboard-component-color-tokenization.test.ts | 81 ++++++++++++++++++++++ packages/dashboard/app/styles.css | 10 +++ 2 files changed, 91 insertions(+) Fusion-Task-Id: FN-6678 Fusion-Task-Lineage: a866af20-fbbb-46c6-9403-f395708bcd3f
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { loadStylesCss } from "../test/cssFixture";
|
||||
|
||||
const root = resolve(__dirname, "../components");
|
||||
|
||||
@@ -40,6 +41,86 @@ function stripVarCalls(line: string): string {
|
||||
return line.replace(/var\([^)]*\)/g, "");
|
||||
}
|
||||
|
||||
function extractRootBlock(css: string): string {
|
||||
const rootRegex = /:root\s*\{/g;
|
||||
let match;
|
||||
let secondRootIdx = -1;
|
||||
let count = 0;
|
||||
|
||||
while ((match = rootRegex.exec(css)) !== null) {
|
||||
count++;
|
||||
if (count === 2) {
|
||||
secondRootIdx = match.index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (secondRootIdx === -1) {
|
||||
throw new Error("Could not find second :root block");
|
||||
}
|
||||
|
||||
return extractBlockAt(css, secondRootIdx);
|
||||
}
|
||||
|
||||
function extractLightThemeBlock(css: string): string {
|
||||
const startMatch = css.match(/:root\[data-theme="light"\]\s*\{/);
|
||||
if (!startMatch) {
|
||||
throw new Error("Could not find :root[data-theme=\"light\"] block");
|
||||
}
|
||||
|
||||
return extractBlockAt(css, startMatch.index!);
|
||||
}
|
||||
|
||||
function extractBlockAt(css: string, startIdx: number): string {
|
||||
const openBraceIdx = startIdx + css.slice(startIdx).indexOf("{");
|
||||
let depth = 1;
|
||||
let end = openBraceIdx;
|
||||
|
||||
for (let i = openBraceIdx + 1; i < css.length; i++) {
|
||||
if (css[i] === "{") depth++;
|
||||
if (css[i] === "}") depth--;
|
||||
if (depth === 0) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return css.slice(startIdx, end + 1);
|
||||
}
|
||||
|
||||
function extractTokenDefinition(block: string, token: string): string {
|
||||
const escapedToken = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const match = block.match(new RegExp(`${escapedToken}:\\s*([^;]+);`));
|
||||
if (!match) {
|
||||
throw new Error(`Could not find ${token} definition`);
|
||||
}
|
||||
|
||||
return match[1].trim();
|
||||
}
|
||||
|
||||
describe("dashboard surface token definitions", () => {
|
||||
it("defines neutral surface and subtle border tokens in base and light themes", () => {
|
||||
const css = loadStylesCss();
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const lightThemeBlock = extractLightThemeBlock(css);
|
||||
const tokens = ["--surface-1", "--surface-2", "--border-subtle"];
|
||||
|
||||
/*
|
||||
FNXC:DashboardSurfaceTokens 2026-06-18-21:29:
|
||||
FN-6678 keeps these tokens defined in both canonical theme blocks because charts, Command Center surfaces, areas, and NewTaskModal consume them through bare var() calls with no fallback. Require color-mix token derivations so removing a definition or replacing it with a raw color fails the guard.
|
||||
*/
|
||||
for (const token of tokens) {
|
||||
const rootDefinition = extractTokenDefinition(rootBlock, token);
|
||||
const lightDefinition = extractTokenDefinition(lightThemeBlock, token);
|
||||
|
||||
expect(rootDefinition, `${token} must be defined in :root`).toMatch(/^color-mix\(in\s+srgb,/);
|
||||
expect(lightDefinition, `${token} must be defined in :root[data-theme="light"]`).toMatch(/^color-mix\(in\s+srgb,/);
|
||||
expect(rootDefinition, `${token} root definition must not use raw colors`).not.toMatch(/rgba\(|#[0-9a-fA-F]{3,8}/);
|
||||
expect(lightDefinition, `${token} light definition must not use raw colors`).not.toMatch(/rgba\(|#[0-9a-fA-F]{3,8}/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("dashboard component color tokenization", () => {
|
||||
it("keeps audited compliant files free of raw rgba()", () => {
|
||||
for (const file of auditedCompliant) {
|
||||
|
||||
@@ -231,6 +231,13 @@ svg.spin {
|
||||
--surface-muted: color-mix(in srgb, var(--surface) 94%, var(--text) 6%);
|
||||
--surface-emphasis: color-mix(in srgb, var(--surface) 92%, var(--text) 8%);
|
||||
--surface-hover-strong: color-mix(in srgb, var(--surface) 88%, var(--text) 12%);
|
||||
/*
|
||||
FNXC:DashboardSurfaceTokens 2026-06-18-21:29:
|
||||
FN-6678 defines neutral surface tiers and a subtle border because the command-center subtree, NewTaskModal, and related dashboard surfaces consume these tokens via bare var() calls. Keep them tokenized so light and dark modes cascade from each theme's --surface, --text, and --border values instead of resolving unset.
|
||||
*/
|
||||
--surface-1: color-mix(in srgb, var(--surface) 97%, var(--text) 3%);
|
||||
--surface-2: color-mix(in srgb, var(--surface) 92%, var(--text) 8%);
|
||||
--border-subtle: color-mix(in srgb, var(--border) 60%, transparent);
|
||||
--bg-secondary: color-mix(in srgb, var(--surface) 70%, var(--card));
|
||||
--bg-tertiary: color-mix(in srgb, var(--surface) 40%, var(--card));
|
||||
--border: #30363d;
|
||||
@@ -467,6 +474,9 @@ svg.spin {
|
||||
--surface-muted: color-mix(in srgb, var(--surface) 97%, var(--text) 3%);
|
||||
--surface-emphasis: color-mix(in srgb, var(--surface) 95%, var(--text) 5%);
|
||||
--surface-hover-strong: color-mix(in srgb, var(--surface) 94%, var(--text) 6%);
|
||||
--surface-1: color-mix(in srgb, var(--surface) 98%, var(--text) 2%);
|
||||
--surface-2: color-mix(in srgb, var(--surface) 95%, var(--text) 5%);
|
||||
--border-subtle: color-mix(in srgb, var(--border) 60%, transparent);
|
||||
--bg: #ffffff;
|
||||
--surface: #f6f8fa;
|
||||
--card: #ffffff;
|
||||
|
||||
Reference in New Issue
Block a user