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:
gsxdsm
2026-07-16 04:15:18 -07:00
parent de05cb7566
commit b885aff4fd
6 changed files with 130 additions and 4 deletions

View 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).

View File

@@ -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("");
});
});

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}