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);
|
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 {
|
.cursor-cli-binary-path-label {
|
||||||
color: var(--text-secondary);
|
color: var(--text-muted);
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
font-weight: 600;
|
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);
|
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 {
|
.grok-cli-binary-path-label {
|
||||||
color: var(--text-secondary);
|
color: var(--text-muted);
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
font-weight: 600;
|
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);
|
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 {
|
.omp-cli-binary-path-label {
|
||||||
color: var(--text-secondary);
|
color: var(--text-muted);
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1742,12 +1742,16 @@ One-click fix affordances: a quiet pill per fixable warning row plus a
|
|||||||
line-height: 1.35;
|
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-code,
|
||||||
.wf-lifecycle-warning-node {
|
.wf-lifecycle-warning-node {
|
||||||
padding: 1px 5px;
|
padding: 1px 5px;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
color: var(--text-secondary);
|
color: var(--text-muted);
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
font-family: var(--font-mono, monospace);
|
font-family: var(--font-mono, monospace);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user