import { readFileSync } from "node:fs"; import { join } from "node:path"; const statusTones = ["neutral", "info", "success", "warning", "danger"] as const; describe("semantic status language", () => { it("keeps the production status foregrounds above 4.5:1", () => { const styles = readFileSync(join(process.cwd(), "src/styles.css"), "utf8"); statusTones.forEach((tone) => { const soft = readOklchToken(styles, `--status-${tone}-soft`); const foreground = readOklchToken(styles, `--status-${tone}-foreground`); expect(getOklchContrastRatio(foreground, soft), tone).toBeGreaterThanOrEqual(4.5); }); }); it("defines the complete softened OKLCH token set", () => { const styles = readFileSync(join(process.cwd(), "src/styles.css"), "utf8"); statusTones.forEach((tone) => { expect(styles).toContain(`--status-${tone}-soft:`); expect(styles).toContain(`--status-${tone}-border:`); expect(styles).toContain(`--status-${tone}-foreground:`); expect(styles).toContain(`--status-${tone}-mark:`); expect(styles).toContain(`.status-tone-${tone}`); }); }); it("stops shared status motion when reduced motion is requested", () => { const styles = readFileSync(join(process.cwd(), "src/styles.css"), "utf8"); expect(styles).toMatch( /@media \(prefers-reduced-motion: reduce\)[\s\S]*?\.status-dot\[data-activity="live"\][\s\S]*?animation:\s*none/ ); }); it("does not derive Agent connection styling from localized copy", () => { const agentHook = readFileSync( join(process.cwd(), "src/features/workbench/hooks/use-workbench-agent.ts"), "utf8" ); const agentPanel = readFileSync( join(process.cwd(), "src/features/agent/components/agent-command-panel.tsx"), "utf8" ); expect(agentPanel).not.toMatch(/statusLabel\.(?:includes|match|startsWith)/); expect(agentPanel).not.toContain("getAgentConnection"); expect(agentHook).not.toContain("runtimeAvailability"); expect(agentPanel).toContain("AgentConnectionStatus"); }); }); type OklchColor = { lightness: number; chroma: number; hue: number; }; function readOklchToken(styles: string, token: string): OklchColor { const match = styles.match( new RegExp(`${token}:\\s*oklch\\((\\d+(?:\\.\\d+)?)%\\s+(\\d+(?:\\.\\d+)?)\\s+(\\d+(?:\\.\\d+)?)\\)`) ); if (!match) { throw new Error(`Missing OKLCH token: ${token}`); } return { lightness: Number(match[1]) / 100, chroma: Number(match[2]), hue: Number(match[3]) }; } function getOklchContrastRatio(foreground: OklchColor, background: OklchColor) { const foregroundLuminance = getOklchRelativeLuminance(foreground); const backgroundLuminance = getOklchRelativeLuminance(background); const lighter = Math.max(foregroundLuminance, backgroundLuminance); const darker = Math.min(foregroundLuminance, backgroundLuminance); return (lighter + 0.05) / (darker + 0.05); } function getOklchRelativeLuminance({ lightness, chroma, hue }: OklchColor) { const hueRadians = (hue * Math.PI) / 180; const a = chroma * Math.cos(hueRadians); const b = chroma * Math.sin(hueRadians); const lPrime = lightness + 0.3963377774 * a + 0.2158037573 * b; const mPrime = lightness - 0.1055613458 * a - 0.0638541728 * b; const sPrime = lightness - 0.0894841775 * a - 1.291485548 * b; const l = lPrime ** 3; const m = mPrime ** 3; const s = sPrime ** 3; const red = clampLinearRgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s); const green = clampLinearRgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s); const blue = clampLinearRgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s); return 0.2126 * red + 0.7152 * green + 0.0722 * blue; } function clampLinearRgb(value: number) { return Math.min(1, Math.max(0, value)); }