feat(FN-3011): merge fusion/fn-3011
Commits merged: - test(FN-3011): complete Step 2 — harden light-theme source assertions - feat(FN-3011): complete Step 1 — restore global token scope - fix(FN-3011): complete Step 3 — satisfy lint/test/typecheck/build gates - test(FN-3011): complete Step 2 — harden light-theme source assertions - feat(FN-3011): complete Step 1 — restore default light token ownership Files changed: .../app/__tests__/status-colors-theme.test.ts | 100 +++++-- .../app/components/CustomModelDropdown.css | 6 - .../__tests__/ModelOnboardingModal.test.tsx | 4 +- packages/dashboard/app/styles.css | 299 +++++++++++---------- packages/dashboard/app/test/cssFixture.ts | 11 +- 5 files changed, 242 insertions(+), 178 deletions(-) Fusion-Task-Id: FN-3011
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { loadAllAppCss } from "../test/cssFixture";
|
||||
import { loadAllAppCss, loadStylesCss } from "../test/cssFixture";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const themeDataPath = path.resolve(__dirname, "../public/theme-data.css");
|
||||
|
||||
/**
|
||||
* Theme-safety regression tests for status color tokens across
|
||||
* TaskCard, GitHubBadge, and PrSection components.
|
||||
@@ -14,55 +16,57 @@ import path from "path";
|
||||
|
||||
describe("Status color CSS custom properties", () => {
|
||||
let css: string;
|
||||
let stylesCss: string;
|
||||
|
||||
beforeAll(() => {
|
||||
css = loadAllAppCss();
|
||||
stylesCss = loadStylesCss();
|
||||
});
|
||||
|
||||
it("defines --status-triage-bg custom property in :root using color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--status-triage-bg");
|
||||
expect(rootBlock).toContain("color-mix(in srgb, var(--triage) 15%, transparent)");
|
||||
});
|
||||
|
||||
it("defines --status-todo-bg custom property in :root using color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--status-todo-bg");
|
||||
expect(rootBlock).toContain("color-mix(in srgb, var(--todo) 15%, transparent)");
|
||||
});
|
||||
|
||||
it("defines --status-in-progress-bg custom property in :root using color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--status-in-progress-bg");
|
||||
expect(rootBlock).toContain("color-mix(in srgb, var(--in-progress) 15%, transparent)");
|
||||
});
|
||||
|
||||
it("defines --status-in-review-bg custom property in :root using color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--status-in-review-bg");
|
||||
expect(rootBlock).toContain("color-mix(in srgb, var(--in-review) 15%, transparent)");
|
||||
});
|
||||
|
||||
it("defines --status-done-bg custom property in :root using color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--status-done-bg");
|
||||
expect(rootBlock).toContain("color-mix(in srgb, var(--done) 15%, transparent)");
|
||||
});
|
||||
|
||||
it("defines --status-error-bg custom property in :root using color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--status-error-bg");
|
||||
expect(rootBlock).toContain("color-mix(in srgb, var(--color-error-dark");
|
||||
});
|
||||
|
||||
it("defines --status-archived-bg custom property in :root using color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--status-archived-bg");
|
||||
expect(rootBlock).toContain("color-mix(in srgb, var(--text-muted");
|
||||
});
|
||||
|
||||
it("defines --surface-hover in :root using semantic color-mix()", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--surface-hover");
|
||||
expect(rootBlock).toContain(
|
||||
"--surface-hover: color-mix(in srgb, var(--surface) 90%, var(--text) 10%)"
|
||||
@@ -70,7 +74,7 @@ describe("Status color CSS custom properties", () => {
|
||||
});
|
||||
|
||||
it("defines light theme override for --surface-hover", () => {
|
||||
const lightBlock = extractLightThemeBlock(css);
|
||||
const lightBlock = extractLightThemeBlock(stylesCss);
|
||||
expect(lightBlock).toContain("--surface-hover");
|
||||
expect(lightBlock).toContain(
|
||||
"--surface-hover: color-mix(in srgb, var(--surface) 92%, var(--text) 8%)"
|
||||
@@ -78,7 +82,7 @@ describe("Status color CSS custom properties", () => {
|
||||
});
|
||||
|
||||
it("defines semantic neutral surface tiers in :root", () => {
|
||||
const rootBlock = extractRootBlock(css);
|
||||
const rootBlock = extractRootBlock(stylesCss);
|
||||
expect(rootBlock).toContain("--surface-subtle");
|
||||
expect(rootBlock).toContain("--surface-muted");
|
||||
expect(rootBlock).toContain("--surface-emphasis");
|
||||
@@ -86,7 +90,7 @@ describe("Status color CSS custom properties", () => {
|
||||
});
|
||||
|
||||
it("defines semantic neutral surface tier overrides in light theme", () => {
|
||||
const lightBlock = extractLightThemeBlock(css);
|
||||
const lightBlock = extractLightThemeBlock(stylesCss);
|
||||
expect(lightBlock).toContain("--surface-subtle");
|
||||
expect(lightBlock).toContain("--surface-muted");
|
||||
expect(lightBlock).toContain("--surface-emphasis");
|
||||
@@ -94,11 +98,11 @@ describe("Status color CSS custom properties", () => {
|
||||
});
|
||||
|
||||
it("defines semantic neutral tiers with tokenized color-mix expressions", () => {
|
||||
expect(css).toMatch(/--surface-subtle:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+96%,\s*var\(--text\)\s+4%\)/);
|
||||
expect(css).toMatch(/--surface-muted:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+94%,\s*var\(--text\)\s+6%\)/);
|
||||
expect(css).toMatch(/--surface-emphasis:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+92%,\s*var\(--text\)\s+8%\)/);
|
||||
expect(css).toMatch(/--surface-hover-strong:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+88%,\s*var\(--text\)\s+12%\)/);
|
||||
expect(css).not.toMatch(/--surface-(subtle|muted|emphasis|hover-strong):\s*rgba\(/);
|
||||
expect(stylesCss).toMatch(/--surface-subtle:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+96%,\s*var\(--text\)\s+4%\)/);
|
||||
expect(stylesCss).toMatch(/--surface-muted:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+94%,\s*var\(--text\)\s+6%\)/);
|
||||
expect(stylesCss).toMatch(/--surface-emphasis:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+92%,\s*var\(--text\)\s+8%\)/);
|
||||
expect(stylesCss).toMatch(/--surface-hover-strong:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+88%,\s*var\(--text\)\s+12%\)/);
|
||||
expect(stylesCss).not.toMatch(/--surface-(subtle|muted|emphasis|hover-strong):\s*rgba\(/);
|
||||
});
|
||||
|
||||
it("uses --surface-hover token references with tokenized fallback (no raw rgba)", () => {
|
||||
@@ -108,17 +112,34 @@ describe("Status color CSS custom properties", () => {
|
||||
});
|
||||
|
||||
it("defines --surface-hover with tokenized color-mix (no raw rgba/hex)", () => {
|
||||
expect(css).toMatch(/--surface-hover:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+90%,\s*var\(--text\)\s+10%\)/);
|
||||
expect(css).toMatch(/\[data-theme="light"\]\s*\{[^}]*--surface-hover:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+92%,\s*var\(--text\)\s+8%\)/s);
|
||||
expect(css).not.toMatch(/--surface-hover:\s*rgba\(/);
|
||||
expect(css).not.toMatch(/--surface-hover:\s*#[0-9a-fA-F]{3,8}/);
|
||||
expect(stylesCss).toMatch(/--surface-hover:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+90%,\s*var\(--text\)\s+10%\)/);
|
||||
expect(stylesCss).toMatch(/:root\[data-theme="light"\]\s*\{[^}]*--surface-hover:\s*color-mix\(in\s+srgb,\s*var\(--surface\)\s+92%,\s*var\(--text\)\s+8%\)/s);
|
||||
expect(stylesCss).not.toMatch(/--surface-hover:\s*rgba\(/);
|
||||
expect(stylesCss).not.toMatch(/--surface-hover:\s*#[0-9a-fA-F]{3,8}/);
|
||||
});
|
||||
|
||||
it("defines light theme override for --status-error-bg", () => {
|
||||
const lightBlock = extractLightThemeBlock(css);
|
||||
const lightBlock = extractLightThemeBlock(stylesCss);
|
||||
expect(lightBlock).toContain("--status-error-bg");
|
||||
expect(lightBlock).toContain("--status-error-bg-deep");
|
||||
});
|
||||
|
||||
it("keeps :root[data-theme=\"light\"] owned by styles.css", () => {
|
||||
const stylesLightMatches = stylesCss.match(/:root\[data-theme="light"\]\s*\{/g) ?? [];
|
||||
const allCssLightMatches = css.match(/:root\[data-theme="light"\]\s*\{/g) ?? [];
|
||||
|
||||
expect(stylesLightMatches).toHaveLength(1);
|
||||
expect(allCssLightMatches).toHaveLength(stylesLightMatches.length);
|
||||
});
|
||||
|
||||
it("does not scope global design tokens under runtime-card cobrand selector", () => {
|
||||
const cobrandMarkBlock = extractSelectorBlock(stylesCss, ".runtime-card__cobrand-mark");
|
||||
|
||||
expect(cobrandMarkBlock).not.toContain("--cta-bg");
|
||||
expect(cobrandMarkBlock).not.toContain("--state-idle-bg");
|
||||
expect(cobrandMarkBlock).not.toContain("--event-error-text");
|
||||
expect(cobrandMarkBlock).not.toContain("--star-idle");
|
||||
});
|
||||
});
|
||||
|
||||
describe("TaskCard theme safety", () => {
|
||||
@@ -316,8 +337,7 @@ describe("CSS modifier classes for status colors", () => {
|
||||
});
|
||||
|
||||
describe("Accent color per color theme", () => {
|
||||
// Theme blocks are now in a separate theme-data.css file
|
||||
const themeDataPath = path.resolve(__dirname, "../public/theme-data.css");
|
||||
// Theme blocks are in theme-data.css and intentionally outside loadAllAppCss().
|
||||
let css: string;
|
||||
let themeData: string;
|
||||
|
||||
@@ -513,9 +533,12 @@ function extractRootBlock(css: string): string {
|
||||
}
|
||||
|
||||
function extractLightThemeBlock(css: string): string {
|
||||
const startMatch = css.match(/\[data-theme="light"\]\s*\{/);
|
||||
// Intentionally parses the default light token block from styles.css
|
||||
// (`:root[data-theme="light"]`). Color-theme light variants are validated
|
||||
// from theme-data.css.
|
||||
const startMatch = css.match(/:root\[data-theme="light"\]\s*\{/);
|
||||
if (!startMatch) {
|
||||
throw new Error("Could not find [data-theme=\"light\"] block");
|
||||
throw new Error("Could not find :root[data-theme=\"light\"] block");
|
||||
}
|
||||
|
||||
const startIdx = startMatch.index!;
|
||||
@@ -534,3 +557,28 @@ function extractLightThemeBlock(css: string): string {
|
||||
|
||||
return css.slice(startIdx, end + 1);
|
||||
}
|
||||
|
||||
function extractSelectorBlock(css: string, selector: string): string {
|
||||
const startIdx = css.indexOf(selector);
|
||||
if (startIdx === -1) {
|
||||
throw new Error(`Could not find selector block: ${selector}`);
|
||||
}
|
||||
|
||||
const openBraceIdx = css.indexOf("{", startIdx);
|
||||
if (openBraceIdx === -1) {
|
||||
throw new Error(`Could not find opening brace for selector: ${selector}`);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -347,12 +347,6 @@ html .column.drag-over * {
|
||||
--surface: #f6f8fa;
|
||||
--card: #ffffff;
|
||||
--card-hover: #f3f4f6;
|
||||
/* Light mode keeps the same semantic role with slightly softer contrast. */
|
||||
--surface-hover: color-mix(in srgb, var(--surface) 92%, var(--text) 8%);
|
||||
--surface-subtle: color-mix(in srgb, var(--surface) 98%, var(--text) 2%);
|
||||
--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%);
|
||||
--bg-secondary: #f6f8fa;
|
||||
--bg-tertiary: #eaeef2;
|
||||
--border: #d0d7de;
|
||||
|
||||
@@ -2399,8 +2399,8 @@ describe("ModelOnboardingModal", () => {
|
||||
expect(mockFetchAuthStatus).toHaveBeenCalled();
|
||||
|
||||
// GitHub should show as connected
|
||||
expect(screen.getByTestId("onboarding-auth-status-github")).toBeTruthy();
|
||||
expect(screen.getByText("✓ Connected")).toBeTruthy();
|
||||
expect(await screen.findByTestId("onboarding-auth-status-github")).toBeTruthy();
|
||||
expect(await screen.findByText("✓ Connected")).toBeTruthy();
|
||||
|
||||
// Should show Disconnect instead of Connect
|
||||
expect(screen.getByText("Disconnect")).toBeTruthy();
|
||||
|
||||
@@ -256,6 +256,162 @@ html {
|
||||
--provider-openclaw: #ff4f40;
|
||||
--provider-paperclip: var(--text);
|
||||
--provider-icon-contrast: var(--bg);
|
||||
|
||||
/* Task-creation CTA tokens */
|
||||
--cta-bg: #238636;
|
||||
--cta-border: #2ea043;
|
||||
--cta-text: #fff;
|
||||
--cta-bg-hover: #2ea043;
|
||||
--cta-border-hover: #3fb950;
|
||||
--cta-glow: 0 0 8px rgba(46, 160, 67, 0.3);
|
||||
|
||||
/* === Agent State Colors === */
|
||||
--state-idle-bg: rgba(139, 148, 158, 0.15);
|
||||
--state-idle-text: #8b949e;
|
||||
--state-idle-border: #8b949e;
|
||||
--state-active-bg: rgba(46, 160, 67, 0.15);
|
||||
--state-active-text: #3fb950;
|
||||
--state-active-border: #3fb950;
|
||||
--state-paused-bg: rgba(227, 179, 65, 0.15);
|
||||
--state-paused-text: #e3b541;
|
||||
--state-paused-border: #e3b541;
|
||||
--state-error-bg: rgba(248, 81, 73, 0.15);
|
||||
--state-error-text: #f85149;
|
||||
--state-error-border: #f85149;
|
||||
|
||||
/* === Mission & Milestone Status Colors === */
|
||||
--mission-planning-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--mission-planning-text: var(--triage);
|
||||
--mission-active-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--mission-active-text: var(--in-review);
|
||||
--mission-blocked-bg: color-mix(in srgb, var(--color-error) 15%, transparent);
|
||||
--mission-blocked-text: var(--color-error);
|
||||
--mission-complete-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--mission-complete-text: #3b82f6;
|
||||
--mission-archived-bg: var(--bg-tertiary);
|
||||
--mission-archived-text: var(--text-secondary);
|
||||
|
||||
/* === Slice Status Colors === */
|
||||
--slice-pending-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--slice-pending-text: var(--triage);
|
||||
--slice-active-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--slice-active-text: var(--in-review);
|
||||
--slice-complete-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--slice-complete-text: #3b82f6;
|
||||
|
||||
/* === Feature (Task) Status Colors === */
|
||||
--feature-defined-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--feature-defined-text: var(--triage);
|
||||
--feature-triaged-bg: color-mix(in srgb, #a855f7 15%, transparent);
|
||||
--feature-triaged-text: #a855f7;
|
||||
--feature-in-progress-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--feature-in-progress-text: var(--in-review);
|
||||
--feature-done-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--feature-done-text: #3b82f6;
|
||||
|
||||
/* === Autopilot State Colors === */
|
||||
--autopilot-inactive-bg: color-mix(in srgb, #94a3b8 15%, transparent);
|
||||
--autopilot-inactive-text: #94a3b8;
|
||||
--autopilot-watching-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--autopilot-watching-text: var(--in-review);
|
||||
--autopilot-activating-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--autopilot-activating-text: #3b82f6;
|
||||
--autopilot-completing-bg: color-mix(in srgb, #a855f7 15%, transparent);
|
||||
--autopilot-completing-text: #a855f7;
|
||||
|
||||
/* === Loop State Colors === */
|
||||
--loop-idle-bg: var(--bg-tertiary);
|
||||
--loop-idle-text: var(--text-secondary);
|
||||
--loop-idle-indicator: var(--text-dim);
|
||||
--loop-implementing-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--loop-implementing-text: var(--in-review);
|
||||
--loop-implementing-indicator: var(--in-review);
|
||||
--loop-validating-bg: color-mix(in srgb, var(--color-warning) 15%, transparent);
|
||||
--loop-validating-text: var(--color-warning);
|
||||
--loop-validating-indicator: var(--color-warning);
|
||||
--loop-needs-fix-bg: color-mix(in srgb, #f97316 15%, transparent);
|
||||
--loop-needs-fix-text: #f97316;
|
||||
--loop-needs-fix-indicator: #f97316;
|
||||
--loop-passed-bg: color-mix(in srgb, var(--color-success) 15%, transparent);
|
||||
--loop-passed-text: var(--color-success);
|
||||
--loop-passed-indicator: var(--color-success);
|
||||
--loop-blocked-bg: color-mix(in srgb, var(--color-error) 15%, transparent);
|
||||
--loop-blocked-text: var(--color-error);
|
||||
--loop-blocked-indicator: var(--color-error);
|
||||
|
||||
/* === Assertion Status Colors === */
|
||||
--assertion-pending-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--assertion-pending-text: var(--triage);
|
||||
--assertion-passed-bg: color-mix(in srgb, var(--color-success) 15%, transparent);
|
||||
--assertion-passed-text: var(--color-success);
|
||||
--assertion-failed-bg: color-mix(in srgb, var(--color-error) 15%, transparent);
|
||||
--assertion-failed-text: var(--color-error);
|
||||
--assertion-blocked-bg: color-mix(in srgb, var(--color-warning) 15%, transparent);
|
||||
--assertion-blocked-text: var(--color-warning);
|
||||
|
||||
/* === Interview Modal Icon Colors === */
|
||||
--icon-milestone: var(--triage);
|
||||
--icon-slice: var(--in-review);
|
||||
--icon-feature: #3b82f6;
|
||||
|
||||
/* === Accent Color === */
|
||||
--accent: #7c5cbf;
|
||||
|
||||
/* === Log Entry Background Tokens === */
|
||||
--log-tool-bg: color-mix(in srgb, var(--accent) 8%, transparent);
|
||||
--log-success-bg: color-mix(in srgb, var(--color-success) 6%, transparent);
|
||||
--log-error-bg: color-mix(in srgb, var(--color-error) 6%, transparent);
|
||||
|
||||
/* === Mailbox compose FAB & badges === */
|
||||
--fab-bg: var(--todo);
|
||||
--fab-text: #fff;
|
||||
|
||||
/* === Mission autopilot indicators === */
|
||||
--autopilot-pulse: var(--color-success);
|
||||
--autopilot-icon: #eab308;
|
||||
--autopilot-shadow: rgba(34, 197, 94, 0.4);
|
||||
|
||||
/* === Mission toggle & badge backgrounds === */
|
||||
--toggle-checked-bg: rgba(34, 197, 94, 0.2);
|
||||
--meta-badge-bg: rgba(63, 185, 80, 0.1);
|
||||
|
||||
/* === Mission event type colors (semantic — adapted for light in [data-theme="light"]) === */
|
||||
--event-error-text: #fca5a5;
|
||||
--event-state-text: #93c5fd;
|
||||
--event-task-text: #6ee7b7;
|
||||
--event-slice-text: #fcd34d;
|
||||
--event-autopilot-text: #d8b4fe;
|
||||
--event-error-bg: rgba(239, 68, 68, 0.15);
|
||||
--event-state-bg: rgba(59, 130, 246, 0.15);
|
||||
--event-task-bg: rgba(16, 185, 129, 0.15);
|
||||
--event-slice-bg: rgba(245, 158, 11, 0.15);
|
||||
--event-autopilot-bg: rgba(168, 85, 247, 0.15);
|
||||
|
||||
/* === Card mission badge === */
|
||||
--badge-mission-text: #a78bfa;
|
||||
--badge-mission-text-hover: #c4b5fd;
|
||||
--badge-mission-bg: rgba(167, 139, 250, 0.12);
|
||||
--badge-mission-bg-hover: rgba(167, 139, 250, 0.22);
|
||||
|
||||
/* === Terminal background === */
|
||||
--terminal-bg: #1e1e1e;
|
||||
|
||||
/* === Model combobox favorite star === */
|
||||
--star-idle: #6b7280;
|
||||
--star-active: #f59e0b;
|
||||
}
|
||||
|
||||
/* Default light-mode token contract.
|
||||
NOTE: color-theme-specific light overrides live in public/theme-data.css.
|
||||
This block is the source of truth for non-color-theme light mode. */
|
||||
:root[data-theme="light"] {
|
||||
--surface-hover: color-mix(in srgb, var(--surface) 92%, var(--text) 8%);
|
||||
--surface-subtle: color-mix(in srgb, var(--surface) 98%, var(--text) 2%);
|
||||
--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%);
|
||||
--status-error-bg: color-mix(in srgb, var(--color-error-dark) 15%, transparent);
|
||||
--status-error-bg-deep: color-mix(in srgb, var(--color-error-dark) 20%, transparent);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
@@ -421,149 +577,6 @@ html {
|
||||
color: var(--text-muted);
|
||||
border-radius: 2px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
/* Task-creation CTA tokens */
|
||||
--cta-bg: #238636;
|
||||
--cta-border: #2ea043;
|
||||
--cta-text: #fff;
|
||||
--cta-bg-hover: #2ea043;
|
||||
--cta-border-hover: #3fb950;
|
||||
--cta-glow: 0 0 8px rgba(46, 160, 67, 0.3);
|
||||
|
||||
/* === Agent State Colors === */
|
||||
--state-idle-bg: rgba(139, 148, 158, 0.15);
|
||||
--state-idle-text: #8b949e;
|
||||
--state-idle-border: #8b949e;
|
||||
--state-active-bg: rgba(46, 160, 67, 0.15);
|
||||
--state-active-text: #3fb950;
|
||||
--state-active-border: #3fb950;
|
||||
--state-paused-bg: rgba(227, 179, 65, 0.15);
|
||||
--state-paused-text: #e3b541;
|
||||
--state-paused-border: #e3b541;
|
||||
--state-error-bg: rgba(248, 81, 73, 0.15);
|
||||
--state-error-text: #f85149;
|
||||
--state-error-border: #f85149;
|
||||
|
||||
/* === Mission & Milestone Status Colors === */
|
||||
--mission-planning-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--mission-planning-text: var(--triage);
|
||||
--mission-active-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--mission-active-text: var(--in-review);
|
||||
--mission-blocked-bg: color-mix(in srgb, var(--color-error) 15%, transparent);
|
||||
--mission-blocked-text: var(--color-error);
|
||||
--mission-complete-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--mission-complete-text: #3b82f6;
|
||||
--mission-archived-bg: var(--bg-tertiary);
|
||||
--mission-archived-text: var(--text-secondary);
|
||||
|
||||
/* === Slice Status Colors === */
|
||||
--slice-pending-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--slice-pending-text: var(--triage);
|
||||
--slice-active-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--slice-active-text: var(--in-review);
|
||||
--slice-complete-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--slice-complete-text: #3b82f6;
|
||||
|
||||
/* === Feature (Task) Status Colors === */
|
||||
--feature-defined-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--feature-defined-text: var(--triage);
|
||||
--feature-triaged-bg: color-mix(in srgb, #a855f7 15%, transparent);
|
||||
--feature-triaged-text: #a855f7;
|
||||
--feature-in-progress-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--feature-in-progress-text: var(--in-review);
|
||||
--feature-done-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--feature-done-text: #3b82f6;
|
||||
|
||||
/* === Autopilot State Colors === */
|
||||
--autopilot-inactive-bg: color-mix(in srgb, #94a3b8 15%, transparent);
|
||||
--autopilot-inactive-text: #94a3b8;
|
||||
--autopilot-watching-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--autopilot-watching-text: var(--in-review);
|
||||
--autopilot-activating-bg: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
--autopilot-activating-text: #3b82f6;
|
||||
--autopilot-completing-bg: color-mix(in srgb, #a855f7 15%, transparent);
|
||||
--autopilot-completing-text: #a855f7;
|
||||
|
||||
/* === Loop State Colors === */
|
||||
--loop-idle-bg: var(--bg-tertiary);
|
||||
--loop-idle-text: var(--text-secondary);
|
||||
--loop-idle-indicator: var(--text-dim);
|
||||
--loop-implementing-bg: color-mix(in srgb, var(--in-review) 15%, transparent);
|
||||
--loop-implementing-text: var(--in-review);
|
||||
--loop-implementing-indicator: var(--in-review);
|
||||
--loop-validating-bg: color-mix(in srgb, var(--color-warning) 15%, transparent);
|
||||
--loop-validating-text: var(--color-warning);
|
||||
--loop-validating-indicator: var(--color-warning);
|
||||
--loop-needs-fix-bg: color-mix(in srgb, #f97316 15%, transparent);
|
||||
--loop-needs-fix-text: #f97316;
|
||||
--loop-needs-fix-indicator: #f97316;
|
||||
--loop-passed-bg: color-mix(in srgb, var(--color-success) 15%, transparent);
|
||||
--loop-passed-text: var(--color-success);
|
||||
--loop-passed-indicator: var(--color-success);
|
||||
--loop-blocked-bg: color-mix(in srgb, var(--color-error) 15%, transparent);
|
||||
--loop-blocked-text: var(--color-error);
|
||||
--loop-blocked-indicator: var(--color-error);
|
||||
|
||||
/* === Assertion Status Colors === */
|
||||
--assertion-pending-bg: color-mix(in srgb, var(--triage) 15%, transparent);
|
||||
--assertion-pending-text: var(--triage);
|
||||
--assertion-passed-bg: color-mix(in srgb, var(--color-success) 15%, transparent);
|
||||
--assertion-passed-text: var(--color-success);
|
||||
--assertion-failed-bg: color-mix(in srgb, var(--color-error) 15%, transparent);
|
||||
--assertion-failed-text: var(--color-error);
|
||||
--assertion-blocked-bg: color-mix(in srgb, var(--color-warning) 15%, transparent);
|
||||
--assertion-blocked-text: var(--color-warning);
|
||||
|
||||
/* === Interview Modal Icon Colors === */
|
||||
--icon-milestone: var(--triage);
|
||||
--icon-slice: var(--in-review);
|
||||
--icon-feature: #3b82f6;
|
||||
|
||||
/* === Accent Color === */
|
||||
--accent: #7c5cbf;
|
||||
|
||||
/* === Log Entry Background Tokens === */
|
||||
--log-tool-bg: color-mix(in srgb, var(--accent) 8%, transparent);
|
||||
--log-success-bg: color-mix(in srgb, var(--color-success) 6%, transparent);
|
||||
--log-error-bg: color-mix(in srgb, var(--color-error) 6%, transparent);
|
||||
|
||||
/* === Mailbox compose FAB & badges === */
|
||||
--fab-bg: var(--todo);
|
||||
--fab-text: #fff;
|
||||
|
||||
/* === Mission autopilot indicators === */
|
||||
--autopilot-pulse: var(--color-success);
|
||||
--autopilot-icon: #eab308;
|
||||
--autopilot-shadow: rgba(34, 197, 94, 0.4);
|
||||
|
||||
/* === Mission toggle & badge backgrounds === */
|
||||
--toggle-checked-bg: rgba(34, 197, 94, 0.2);
|
||||
--meta-badge-bg: rgba(63, 185, 80, 0.1);
|
||||
|
||||
/* === Mission event type colors (semantic — adapted for light in [data-theme="light"]) === */
|
||||
--event-error-text: #fca5a5;
|
||||
--event-state-text: #93c5fd;
|
||||
--event-task-text: #6ee7b7;
|
||||
--event-slice-text: #fcd34d;
|
||||
--event-autopilot-text: #d8b4fe;
|
||||
--event-error-bg: rgba(239, 68, 68, 0.15);
|
||||
--event-state-bg: rgba(59, 130, 246, 0.15);
|
||||
--event-task-bg: rgba(16, 185, 129, 0.15);
|
||||
--event-slice-bg: rgba(245, 158, 11, 0.15);
|
||||
--event-autopilot-bg: rgba(168, 85, 247, 0.15);
|
||||
|
||||
/* === Card mission badge === */
|
||||
--badge-mission-text: #a78bfa;
|
||||
--badge-mission-text-hover: #c4b5fd;
|
||||
--badge-mission-bg: rgba(167, 139, 250, 0.12);
|
||||
--badge-mission-bg-hover: rgba(167, 139, 250, 0.22);
|
||||
|
||||
/* === Terminal background === */
|
||||
--terminal-bg: #1e1e1e;
|
||||
|
||||
/* === Model combobox favorite star === */
|
||||
--star-idle: #6b7280;
|
||||
--star-active: #f59e0b;
|
||||
}
|
||||
|
||||
html,
|
||||
|
||||
@@ -5,6 +5,13 @@ const APP_DIR = resolve(__dirname, "..");
|
||||
const COMPONENTS_DIR = join(APP_DIR, "components");
|
||||
|
||||
let cached: string | null = null;
|
||||
let stylesCached: string | null = null;
|
||||
|
||||
export function loadStylesCss(): string {
|
||||
if (stylesCached !== null) return stylesCached;
|
||||
stylesCached = readFileSync(join(APP_DIR, "styles.css"), "utf-8");
|
||||
return stylesCached;
|
||||
}
|
||||
|
||||
export function loadAllAppCss(): string {
|
||||
if (cached !== null) return cached;
|
||||
@@ -12,7 +19,9 @@ export function loadAllAppCss(): string {
|
||||
// tests), then component CSS files in alphabetical order. This keeps both
|
||||
// styles.css's intra-file order intact AND lets co-located component base
|
||||
// rules be matched before any cross-file @media override referencing them.
|
||||
const parts: string[] = [readFileSync(join(APP_DIR, "styles.css"), "utf-8")];
|
||||
// NOTE: public/theme-data.css is intentionally excluded here; tests that
|
||||
// validate per-color-theme contracts should load it explicitly.
|
||||
const parts: string[] = [loadStylesCss()];
|
||||
const entries = readdirSync(COMPONENTS_DIR, { withFileTypes: true })
|
||||
.filter((e) => e.isFile() && e.name.endsWith(".css"))
|
||||
.map((e) => e.name)
|
||||
|
||||
Reference in New Issue
Block a user