feat(FN-4016): convert hardcoded rgba colors to CSS token variables across

Migrated dashboard component CSS from hardcoded `rgba()` values to design token variables across nine components including `CustomModelDropdown`, `CliBinaryPanel`, and `CliBinaryInstallBanner`, with test coverage added for the affected components.

Fusion-Task-Id: FN-4016
This commit is contained in:
Fusion
2026-05-11 10:49:33 -07:00
committed by gsxdsm
parent fc34907553
commit d6bea0d6e5
14 changed files with 160 additions and 95 deletions

View File

@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const root = resolve(__dirname, "../components");
const auditedCompliant = ["ChatView.css", "MobileNavBar.css", "ListView.css", "WorkflowResultsTab.css"];
const cleanedFiles = [
"ScriptsModal.css",
"InlineCreateCard.css",
"FileMentionPopup.css",
"BackgroundTasksIndicator.css",
"CliBinaryPanel.css",
"CliBinaryInstallBanner.css",
"GitHubImportModal.css",
"WorkspaceSelector.css",
"AgentReflectionsTab.css",
];
describe("dashboard component color tokenization", () => {
it("keeps audited compliant files free of raw rgba()", () => {
for (const file of auditedCompliant) {
const source = readFileSync(resolve(root, file), "utf8");
expect(source).not.toMatch(/rgba\(/);
}
});
it("keeps cleaned files free of raw rgba()", () => {
for (const file of cleanedFiles) {
const source = readFileSync(resolve(root, file), "utf8");
expect(source).not.toMatch(/rgba\(/);
}
});
it("keeps CustomModelDropdown free of raw rgba()", () => {
const source = readFileSync(resolve(root, "CustomModelDropdown.css"), "utf8");
expect(source).not.toMatch(/rgba\(/);
});
});

View File

@@ -348,7 +348,7 @@
gap: 8px;
padding: 8px 12px;
border-radius: 6px;
background: var(--state-error-bg, rgba(248,81,73,0.15));
background: var(--state-error-bg, color-mix(in srgb, var(--color-error) 15%, transparent));
color: var(--state-error-text, #f85149);
border: 1px solid var(--state-error-border, #f85149);
font-size: 0.875rem;

View File

@@ -43,7 +43,7 @@
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
box-shadow: var(--shadow-md);
z-index: 1000;
overflow: hidden;
}

View File

@@ -6,10 +6,10 @@
padding: 12px 16px;
background: linear-gradient(
90deg,
rgba(59, 130, 246, 0.12),
rgba(59, 130, 246, 0.04)
color-mix(in srgb, var(--todo) 12%, transparent),
color-mix(in srgb, var(--todo) 4%, transparent)
);
border: 1px solid rgba(59, 130, 246, 0.35);
border: 1px solid color-mix(in srgb, var(--todo) 35%, transparent);
border-radius: 8px;
}
@@ -24,7 +24,7 @@
.cli-binary-banner__title {
font-weight: 600;
font-size: 14px;
color: var(--text-primary, #e6edf3);
color: var(--text);
}
.cli-binary-banner__text {
@@ -34,7 +34,7 @@
}
.cli-binary-banner__text code {
background: rgba(255, 255, 255, 0.06);
background: color-mix(in srgb, var(--text) 6%, transparent);
padding: 1px 6px;
border-radius: 4px;
font-size: 0.92em;
@@ -59,7 +59,7 @@
.cli-binary-banner__primary {
background: var(--accent, #3b82f6);
color: #fff;
color: var(--accent-text);
}
.cli-binary-banner__primary:hover:not(:disabled) {
@@ -72,22 +72,22 @@
}
.cli-binary-banner__secondary {
background: rgba(255, 255, 255, 0.04);
color: var(--text-primary, #e6edf3);
border-color: rgba(255, 255, 255, 0.12);
background: color-mix(in srgb, var(--text) 4%, transparent);
color: var(--text);
border-color: color-mix(in srgb, var(--text) 12%, transparent);
}
.cli-binary-banner__secondary:hover {
background: rgba(255, 255, 255, 0.08);
background: color-mix(in srgb, var(--text) 8%, transparent);
}
.cli-binary-banner__error {
margin-top: 4px;
padding: 6px 10px;
background: rgba(218, 54, 51, 0.1);
border-left: 3px solid rgba(218, 54, 51, 0.55);
border-radius: 0 4px 4px 0;
color: #f7c4c2;
background: color-mix(in srgb, var(--color-error) 10%, transparent);
border-left: 3px solid color-mix(in srgb, var(--color-error) 55%, transparent);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
color: var(--color-error);
font-size: 12.5px;
line-height: 1.5;
}
@@ -108,6 +108,6 @@
}
.cli-binary-banner__dismiss:hover {
background: rgba(255, 255, 255, 0.06);
color: var(--text-primary, #e6edf3);
background: color-mix(in srgb, var(--text) 6%, transparent);
color: var(--text);
}

View File

@@ -34,21 +34,21 @@
}
.cli-binary-pill--ok {
background: rgba(46, 160, 67, 0.18);
color: #57c97a;
border: 1px solid rgba(46, 160, 67, 0.4);
background: color-mix(in srgb, var(--color-success) 18%, transparent);
color: var(--color-success);
border: 1px solid color-mix(in srgb, var(--color-success) 40%, transparent);
}
.cli-binary-pill--warn {
background: rgba(210, 153, 34, 0.18);
color: #e3b341;
border: 1px solid rgba(210, 153, 34, 0.4);
background: color-mix(in srgb, var(--color-warning) 18%, transparent);
color: var(--color-warning);
border: 1px solid color-mix(in srgb, var(--color-warning) 40%, transparent);
}
.cli-binary-pill--err {
background: rgba(218, 54, 51, 0.18);
color: #f48581;
border: 1px solid rgba(218, 54, 51, 0.4);
background: color-mix(in srgb, var(--color-error) 18%, transparent);
color: var(--color-error);
border: 1px solid color-mix(in srgb, var(--color-error) 40%, transparent);
}
.cli-binary-help {
@@ -57,7 +57,7 @@
}
.cli-binary-help code {
background: rgba(255, 255, 255, 0.06);
background: color-mix(in srgb, var(--text) 6%, transparent);
padding: 1px 6px;
border-radius: 4px;
font-size: 0.92em;
@@ -69,7 +69,7 @@
}
.cli-binary-status-line code {
background: rgba(255, 255, 255, 0.06);
background: color-mix(in srgb, var(--text) 6%, transparent);
padding: 1px 6px;
border-radius: 4px;
}
@@ -103,7 +103,7 @@
}
.cli-binary-info-list code {
background: rgba(255, 255, 255, 0.06);
background: color-mix(in srgb, var(--text) 6%, transparent);
padding: 1px 6px;
border-radius: 4px;
word-break: break-all;
@@ -133,7 +133,7 @@
.cli-binary-install-btn {
background: var(--accent, #3b82f6);
color: #fff;
color: var(--accent-text);
}
.cli-binary-install-btn:hover:not(:disabled) {
@@ -148,14 +148,14 @@
.cli-binary-refresh-btn,
.cli-binary-copy-btn {
background: rgba(255, 255, 255, 0.04);
color: var(--text-primary, #e6edf3);
border-color: rgba(255, 255, 255, 0.12);
background: color-mix(in srgb, var(--text) 4%, transparent);
color: var(--text);
border-color: color-mix(in srgb, var(--text) 12%, transparent);
}
.cli-binary-refresh-btn:hover:not(:disabled),
.cli-binary-copy-btn:hover {
background: rgba(255, 255, 255, 0.08);
background: color-mix(in srgb, var(--text) 8%, transparent);
}
.cli-binary-commands {
@@ -175,8 +175,8 @@
display: flex;
align-items: center;
gap: 8px;
background: rgba(0, 0, 0, 0.25);
border: 1px solid rgba(255, 255, 255, 0.06);
background: color-mix(in srgb, var(--bg) 25%, transparent);
border: 1px solid color-mix(in srgb, var(--text) 6%, transparent);
border-radius: 6px;
padding: 6px 8px 6px 12px;
}
@@ -191,7 +191,7 @@
}
.cli-binary-install-log {
border-top: 1px solid rgba(255, 255, 255, 0.06);
border-top: 1px solid color-mix(in srgb, var(--text) 6%, transparent);
padding-top: 10px;
font-size: 12.5px;
}
@@ -204,7 +204,7 @@
.cli-binary-install-output {
margin: 6px 0 0;
padding: 8px 10px;
background: rgba(0, 0, 0, 0.35);
background: color-mix(in srgb, var(--bg) 35%, transparent);
border-radius: 4px;
max-height: 220px;
overflow: auto;
@@ -214,16 +214,16 @@
}
.cli-binary-install-output--err {
color: #f48581;
color: var(--color-error);
}
.cli-binary-permissions-hint {
margin: 6px 0;
padding: 8px 10px;
background: rgba(218, 54, 51, 0.1);
border-left: 3px solid rgba(218, 54, 51, 0.55);
border-radius: 0 4px 4px 0;
color: #f7c4c2;
background: color-mix(in srgb, var(--color-error) 10%, transparent);
border-left: 3px solid color-mix(in srgb, var(--color-error) 55%, transparent);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
color: var(--color-error);
font-size: 12.5px;
line-height: 1.5;
}

View File

@@ -153,12 +153,12 @@
}
.model-combobox-option--selected {
background: rgba(88, 166, 255, 0.15);
background: color-mix(in srgb, var(--todo) 15%, transparent);
}
.model-combobox-option--selected:hover,
.model-combobox-option--selected.model-combobox-option--highlighted {
background: rgba(88, 166, 255, 0.25);
background: color-mix(in srgb, var(--todo) 25%, transparent);
}
.model-combobox-option-main {
@@ -380,22 +380,22 @@ html .column.drag-over * {
--status-color-unknown: #6e7781;
/* Shadow for light mode */
--shadow-sm: 0 1px 2px rgba(31, 35, 40, 0.08);
--shadow-md: 0 4px 6px rgba(31, 35, 40, 0.12);
--shadow-lg: 0 4px 24px rgba(31, 35, 40, 0.15);
--shadow-glow: 0 0 8px rgba(9, 105, 218, 0.18);
--glow-success: 0 0 8px rgba(26, 127, 55, 0.3);
--glow-warning: 0 0 8px rgba(154, 103, 0, 0.3);
--glow-danger: 0 0 8px rgba(207, 34, 46, 0.3);
--focus-ring: 0 0 0 2px rgba(9, 105, 218, 0.12);
--focus-ring-strong: 0 0 0 2px rgba(9, 105, 218, 0.24);
--shadow-sm: 0 1px 2px color-mix(in srgb, var(--text) 8%, transparent);
--shadow-md: 0 4px 6px color-mix(in srgb, var(--text) 12%, transparent);
--shadow-lg: 0 4px 24px color-mix(in srgb, var(--text) 15%, transparent);
--shadow-glow: 0 0 8px color-mix(in srgb, var(--todo) 18%, transparent);
--glow-success: 0 0 8px color-mix(in srgb, var(--color-success) 30%, transparent);
--glow-warning: 0 0 8px color-mix(in srgb, var(--color-warning) 30%, transparent);
--glow-danger: 0 0 8px color-mix(in srgb, var(--color-error) 30%, transparent);
--focus-ring: 0 0 0 2px color-mix(in srgb, var(--todo) 12%, transparent);
--focus-ring-strong: 0 0 0 2px color-mix(in srgb, var(--todo) 24%, transparent);
/* CTA tokens for light mode */
--cta-bg: #1a7f37;
--cta-border: #1f883d;
--cta-text: #fff;
--cta-bg-hover: #1f883d;
--cta-border-hover: #2ea043;
--cta-glow: 0 0 8px rgba(26, 127, 55, 0.3);
--cta-glow: 0 0 8px color-mix(in srgb, var(--color-success) 30%, transparent);
/* Info color for light mode */
--color-info: #0969da;
@@ -435,7 +435,7 @@ html .column.drag-over * {
/* Mission autopilot — darker for light backgrounds */
--autopilot-icon: #ca8a04;
--autopilot-shadow: rgba(34, 197, 94, 0.4);
--autopilot-shadow: color-mix(in srgb, var(--color-success) 40%, transparent);
/* Terminal background */
--terminal-bg: #f6f8fa;
@@ -443,8 +443,8 @@ html .column.drag-over * {
/* Card mission badge — darker purple for light bg */
--badge-mission-text: #7c3aed;
--badge-mission-text-hover: #8b5cf6;
--badge-mission-bg: rgba(124, 58, 237, 0.12);
--badge-mission-bg-hover: rgba(124, 58, 237, 0.22);
--badge-mission-bg: color-mix(in srgb, var(--badge-mission-text) 12%, transparent);
--badge-mission-bg-hover: color-mix(in srgb, var(--badge-mission-text-hover) 22%, transparent);
/* Mission event type colors — darker/more saturated for light readability */
--event-error-text: #dc2626;
@@ -452,15 +452,15 @@ html .column.drag-over * {
--event-task-text: #059669;
--event-slice-text: #d97706;
--event-autopilot-text: #9333ea;
--event-error-bg: rgba(220, 38, 38, 0.12);
--event-state-bg: rgba(37, 99, 235, 0.12);
--event-task-bg: rgba(5, 150, 105, 0.12);
--event-slice-bg: rgba(217, 119, 6, 0.12);
--event-autopilot-bg: rgba(147, 51, 234, 0.12);
--event-error-bg: color-mix(in srgb, var(--event-error-text) 12%, transparent);
--event-state-bg: color-mix(in srgb, var(--event-state-text) 12%, transparent);
--event-task-bg: color-mix(in srgb, var(--event-task-text) 12%, transparent);
--event-slice-bg: color-mix(in srgb, var(--event-slice-text) 12%, transparent);
--event-autopilot-bg: color-mix(in srgb, var(--event-autopilot-text) 12%, transparent);
/* Mission toggle & badge backgrounds */
--toggle-checked-bg: rgba(34, 197, 94, 0.15);
--meta-badge-bg: rgba(63, 185, 80, 0.08);
--toggle-checked-bg: color-mix(in srgb, var(--color-success) 15%, transparent);
--meta-badge-bg: color-mix(in srgb, var(--color-success) 8%, transparent);
/* Star favorites */
--star-idle: #9ca3af;
@@ -472,19 +472,19 @@ html .column.drag-over * {
[data-theme="light"] .agent-detail-overlay,
[data-theme="light"] .agent-dialog-overlay,
[data-theme="light"] .chat-new-dialog-backdrop {
background: rgba(31, 35, 40, 0.5);
background: color-mix(in srgb, var(--text) 50%, transparent);
}
[data-theme="light"] .modal-header {
background: rgba(246, 248, 250, 0.8);
background: color-mix(in srgb, var(--surface) 80%, transparent);
}
[data-theme="light"] .modal-actions {
background: rgba(246, 248, 250, 0.6);
background: color-mix(in srgb, var(--surface) 60%, transparent);
}
[data-theme="light"] .settings-sidebar {
background: rgba(246, 248, 250, 0.6);
background: color-mix(in srgb, var(--surface) 60%, transparent);
}
[data-theme="light"] .toast-success {
@@ -1062,7 +1062,7 @@ html .column.drag-over * {
.theme-swatch-glass {
--swatch-sample-1: #13111f;
--swatch-sample-2: rgba(34, 27, 52, 0.78);
--swatch-sample-2: color-mix(in srgb, #221b34 78%, transparent);
--swatch-sample-3: #c86bff;
--swatch-sample-4: #ff7aa8;
}
@@ -1273,7 +1273,7 @@ html .column.drag-over * {
[data-theme="light"] .theme-swatch-glass {
--swatch-sample-1: #eceaf5;
--swatch-sample-2: rgba(255, 255, 255, 0.75);
--swatch-sample-2: color-mix(in srgb, #ffffff 75%, transparent);
--swatch-sample-3: #9d40cf;
--swatch-sample-4: #c74b7a;
}

View File

@@ -144,7 +144,7 @@
.chat-new-dialog-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
background: color-mix(in srgb, var(--bg) 60%, transparent);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
@@ -281,8 +281,8 @@
}
.chat-new-dialog-agent-item--selected {
background: var(--accent-color-light, rgba(59, 130, 246, 0.1));
border: 1px solid var(--accent-color, rgba(59, 130, 246, 0.3));
background: color-mix(in srgb, var(--todo) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--todo) 30%, transparent);
}
.chat-new-dialog-agent-name {

View File

@@ -40,7 +40,7 @@
.github-import-toolbar__zone--filter input:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.2);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 20%, transparent);
outline: none;
}
@@ -106,7 +106,7 @@
.github-import-remote-select select:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.2);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 20%, transparent);
}
/* Load button */
@@ -540,8 +540,8 @@
}
.issue-item.selected {
background: rgba(var(--in-progress-rgb), 0.12);
box-shadow: inset 0 0 0 1px rgba(var(--in-progress-rgb), 0.35);
background: color-mix(in srgb, var(--in-progress) 12%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--in-progress) 35%, transparent);
}
.issue-item.imported {
@@ -653,7 +653,7 @@
[data-theme="light"] .github-import-tab.active {
background: var(--card);
border-color: var(--border);
box-shadow: 0 1px 3px rgba(31, 35, 40, 0.06);
box-shadow: var(--shadow-sm);
}
[data-theme="light"] .github-import-toolbar {
@@ -668,7 +668,7 @@
[data-theme="light"] .github-import-toolbar__zone--filter input:focus {
border-color: var(--color-primary, var(--todo));
box-shadow: 0 0 0 2px rgba(var(--in-progress-rgb), 0.15);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--in-progress) 15%, transparent);
}
[data-theme="light"] .issues-list {
@@ -681,8 +681,8 @@
}
[data-theme="light"] .issue-item.selected {
background: rgba(var(--in-progress-rgb), 0.08);
box-shadow: inset 0 0 0 1px rgba(var(--in-progress-rgb), 0.3);
background: color-mix(in srgb, var(--in-progress) 8%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--in-progress) 30%, transparent);
}
[data-theme="light"] .issue-item.imported input[type="radio"] {

View File

@@ -189,8 +189,8 @@
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.6);
color: #fff;
background: color-mix(in srgb, var(--bg) 60%, transparent);
color: var(--text);
border: none;
border-radius: 50%;
width: 16px;
@@ -203,7 +203,7 @@
}
.inline-create-preview-remove:hover {
background: rgba(248, 81, 73, 0.9);
background: color-mix(in srgb, var(--color-error) 90%, transparent);
}
.inline-create-model-wrap {

View File

@@ -3940,16 +3940,16 @@
.changes-diff-patch .diff-add,
.gm-diff-patch [data-prefix="+"],
.changes-diff-patch [data-prefix="+"] {
color: #3fb950;
background-color: rgba(63, 185, 80, 0.12);
color: var(--color-success);
background-color: color-mix(in srgb, var(--color-success) 12%, transparent);
}
.gm-diff-patch .diff-del,
.changes-diff-patch .diff-del,
.gm-diff-patch [data-prefix="-"],
.changes-diff-patch [data-prefix="-"] {
color: #f85149;
background-color: rgba(248, 81, 73, 0.12);
color: var(--color-error);
background-color: color-mix(in srgb, var(--color-error) 12%, transparent);
}
.gm-diff-patch .diff-hunk,
@@ -3976,16 +3976,16 @@
[data-theme="light"] .changes-diff-patch .diff-add,
[data-theme="light"] .gm-diff-patch [data-prefix="+"],
[data-theme="light"] .changes-diff-patch [data-prefix="+"] {
color: #1a7f37;
background-color: rgba(26, 127, 55, 0.1);
color: var(--color-success);
background-color: color-mix(in srgb, var(--color-success) 10%, transparent);
}
[data-theme="light"] .gm-diff-patch .diff-del,
[data-theme="light"] .changes-diff-patch .diff-del,
[data-theme="light"] .gm-diff-patch [data-prefix="-"],
[data-theme="light"] .changes-diff-patch [data-prefix="-"] {
color: #cf222e;
background-color: rgba(207, 34, 46, 0.1);
color: var(--color-error);
background-color: color-mix(in srgb, var(--color-error) 10%, transparent);
}
[data-theme="light"] .gm-diff-patch .diff-hunk,

View File

@@ -89,7 +89,7 @@
}
.workspace-selector-option.active {
background: rgba(88, 166, 255, 0.12);
background: color-mix(in srgb, var(--todo) 12%, transparent);
}
.workspace-selector-option-main {

View File

@@ -1,6 +1,8 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { AgentReflectionsTab } from "../AgentReflectionsTab";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import {
addAgentRating,
deleteAgentRating,
@@ -281,4 +283,10 @@ describe("AgentReflectionsTab", () => {
expect(screen.queryByText("Insights")).not.toBeInTheDocument();
});
});
it("keeps budget warning banner tokenized", () => {
const source = readFileSync(resolve(__dirname, "../AgentReflectionsTab.css"), "utf8");
expect(source).toMatch(/\.budget-warning-banner\s*\{[^}]*var\(--state-error-bg, color-mix\(in srgb, var\(--color-error\) 15%, transparent\)\)/);
expect(source).not.toMatch(/\.budget-warning-banner\s*\{[^}]*rgba\(/);
});
});

View File

@@ -10,6 +10,8 @@ import {
} from "../../api";
import type { Task } from "@fusion/core";
import type { GitRemote } from "../../api";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
// Mock the API module
vi.mock("../../api", async (importOriginal) => {
@@ -68,6 +70,14 @@ describe("GitHubImportModal", () => {
const onClose = vi.fn();
const onImport = vi.fn();
it("uses color-mix tokens for focus and selection surfaces", () => {
const source = readFileSync(resolve(__dirname, "../GitHubImportModal.css"), "utf8");
expect(source).not.toContain("rgba(var(--color-primary-rgb)");
expect(source).not.toContain("rgba(var(--in-progress-rgb)");
expect(source).toContain("color-mix(in srgb, var(--in-progress) 12%, transparent)");
expect(source).toContain("Some hardcoded colors below");
});
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(fetchGitRemotes).mockReset();

View File

@@ -4,6 +4,8 @@ import userEvent from "@testing-library/user-event";
import { WorkspaceSelector } from "../WorkspaceSelector";
import type { WorkspaceInfo } from "../../hooks/useWorkspaces";
import { loadAllAppCss } from "../../test/cssFixture";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const workspaces: WorkspaceInfo[] = [
{
@@ -96,4 +98,10 @@ describe("WorkspaceSelector", () => {
expect(css).toMatch(/\.workspace-selector-menu\s*\{[^}]*left:\s*auto;[^}]*right:\s*0;/);
});
it("uses tokenized active-option background styling", () => {
const source = readFileSync(resolve(__dirname, "../WorkspaceSelector.css"), "utf8");
expect(source).toMatch(/\.workspace-selector-option\.active\s*\{[^}]*color-mix\(in srgb, var\(--todo\) 12%, transparent\)/);
expect(source).not.toMatch(/\.workspace-selector-option\.active\s*\{[^}]*rgba\(/);
});
});