FN-8043: migrate deprecated dashboard text tokens
Replace undefined dashboard secondary-text aliases with the canonical muted token. - Update CLI provider labels and workflow lifecycle warning chips - Add regression coverage for deprecated token use across base and light themes - Add a patch changeset for the dashboard styling fix Files changed: .changeset/fn-8043-text-secondary-token-migration.md | 7 ++ ...precated-text-secondary-token-migration.test.ts | 103 +++++++++++++++++++++ .../app/components/CursorCliProviderCard.css | 6 +- .../app/components/GrokCliProviderCard.css | 6 +- .../app/components/OmpCliProviderCard.css | 6 +- .../app/components/WorkflowNodeEditor.css | 6 +- 6 files changed, 130 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-8043 Fusion-Task-Lineage: 95936078-a40b-4815-bb85-f9597d65ac35 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8043-text-secondary-token-migration.md
Normal file
7
.changeset/fn-8043-text-secondary-token-migration.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix dashboard secondary text labels rendering an unintended color from an undefined CSS token.
|
||||
category: fix
|
||||
dev: Migrate var(--text-secondary) to var(--text-muted) in four dashboard component stylesheets (FN-8043).
|
||||
@@ -0,0 +1,103 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { loadStylesCss } from "../test/cssFixture";
|
||||
|
||||
const componentsDir = resolve(__dirname, "../components");
|
||||
|
||||
const migratedRules = [
|
||||
{ file: "GrokCliProviderCard.css", selector: ".grok-cli-binary-path-label" },
|
||||
{ file: "OmpCliProviderCard.css", selector: ".omp-cli-binary-path-label" },
|
||||
{ file: "CursorCliProviderCard.css", selector: ".cursor-cli-binary-path-label" },
|
||||
{
|
||||
file: "WorkflowNodeEditor.css",
|
||||
selector: ".wf-lifecycle-warning-code, .wf-lifecycle-warning-node",
|
||||
},
|
||||
];
|
||||
|
||||
function stripCssComments(css: string): string {
|
||||
return css.replace(/\/\*[\s\S]*?\*\//g, "");
|
||||
}
|
||||
|
||||
function extractBlockAt(css: string, startIdx: number): string {
|
||||
const openBraceIdx = startIdx + css.slice(startIdx).indexOf("{");
|
||||
let depth = 1;
|
||||
|
||||
for (let index = openBraceIdx + 1; index < css.length; index++) {
|
||||
if (css[index] === "{") depth++;
|
||||
if (css[index] === "}") depth--;
|
||||
if (depth === 0) return css.slice(startIdx, index + 1);
|
||||
}
|
||||
|
||||
throw new Error(`Could not find closing brace for block at ${startIdx}`);
|
||||
}
|
||||
|
||||
function extractRootBlock(css: string): string {
|
||||
const rootRegex = /:root\s*\{/g;
|
||||
let match: RegExpExecArray | null;
|
||||
let rootCount = 0;
|
||||
|
||||
while ((match = rootRegex.exec(css)) !== null) {
|
||||
rootCount++;
|
||||
if (rootCount === 2) return extractBlockAt(css, match.index);
|
||||
}
|
||||
|
||||
throw new Error("Could not find canonical base :root block");
|
||||
}
|
||||
|
||||
function extractLightThemeBlock(css: string): string {
|
||||
const match = css.match(/:root\[data-theme="light"\]\s*\{/);
|
||||
if (!match) throw new Error("Could not find :root[data-theme=\"light\"] block");
|
||||
return extractBlockAt(css, match.index!);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
function extractSelectorBlock(css: string, selector: string): string {
|
||||
const selectorPattern = selector
|
||||
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
.replace(/\s+/g, "\\s+");
|
||||
const match = css.match(new RegExp(`${selectorPattern}\\s*\\{`));
|
||||
if (!match) throw new Error(`Could not find ${selector} rule`);
|
||||
return extractBlockAt(css, match.index!);
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:DashboardTextTokens 2026-07-15-00:00:
|
||||
FN-8043 requires these four secondary-text rules to use canonical --text-muted, never the undefined
|
||||
--text-secondary alias. Strip CSS comments before scanning so required migration rationale comments
|
||||
can name the deprecated alias without violating the executable token-use invariant; require the
|
||||
replacement token in both base and light theme blocks so it resolves on both supported theme paths.
|
||||
*/
|
||||
describe("deprecated secondary dashboard text token migration", () => {
|
||||
it("keeps the undefined secondary alias out of uncommented migrated CSS", () => {
|
||||
const offenders = migratedRules.flatMap(({ file }) => {
|
||||
const css = stripCssComments(readFileSync(resolve(componentsDir, file), "utf8"));
|
||||
return /--text-secondary\b/.test(css) ? [file] : [];
|
||||
});
|
||||
|
||||
expect(offenders, `Unexpected deprecated --text-secondary usage in: ${offenders.join(", ")}`).toEqual([]);
|
||||
});
|
||||
|
||||
it("uses the canonical muted token in each migrated secondary-text rule", () => {
|
||||
for (const { file, selector } of migratedRules) {
|
||||
const css = readFileSync(resolve(componentsDir, file), "utf8");
|
||||
const rule = extractSelectorBlock(css, selector);
|
||||
expect(rule, `${file} ${selector} must use --text-muted`).toMatch(/color:\s*var\(--text-muted\);/);
|
||||
}
|
||||
});
|
||||
|
||||
it("defines the muted replacement token in base and light theme blocks", () => {
|
||||
const css = loadStylesCss();
|
||||
const baseDefinition = extractTokenDefinition(extractRootBlock(css), "--text-muted");
|
||||
const lightDefinition = extractTokenDefinition(extractLightThemeBlock(css), "--text-muted");
|
||||
|
||||
expect(baseDefinition, "--text-muted must be defined in the base :root theme").not.toBe("");
|
||||
expect(lightDefinition, "--text-muted must be defined in :root[data-theme=\"light\"]").not.toBe("");
|
||||
});
|
||||
});
|
||||
@@ -25,8 +25,12 @@ binary-path control render flush against the card's left/right/bottom edges. See
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:CursorCli 2026-07-15-00:00:
|
||||
FN-8043 uses --text-muted for secondary labels because --text-secondary is an undefined deprecated alias in both themes.
|
||||
*/
|
||||
.cursor-cli-binary-path-label {
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -34,8 +34,12 @@ notch quieter than the primary status line via reduced opacity on the shared `.s
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GrokCli 2026-07-15-00:00:
|
||||
FN-8043 uses --text-muted for secondary labels because --text-secondary is an undefined deprecated alias in both themes.
|
||||
*/
|
||||
.grok-cli-binary-path-label {
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,12 @@ Compact card body inset to match auth-provider-header horizontal padding (same a
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:OmpAcp 2026-07-15-00:00:
|
||||
FN-8043 uses --text-muted for secondary labels because --text-secondary is an undefined deprecated alias in both themes.
|
||||
*/
|
||||
.omp-cli-binary-path-label {
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -1742,12 +1742,16 @@ One-click fix affordances: a quiet pill per fixable warning row plus a
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:WorkflowNodeEditor 2026-07-15-00:00:
|
||||
FN-8043 uses --text-muted for lifecycle warning chips because --text-secondary is an undefined deprecated alias in both themes.
|
||||
*/
|
||||
.wf-lifecycle-warning-code,
|
||||
.wf-lifecycle-warning-node {
|
||||
padding: 1px 5px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.7rem;
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user