From 7edfbb11dc2af4f46fddf4e6909ecf72f14f64d7 Mon Sep 17 00:00:00 2001 From: Douglas Date: Mon, 20 Apr 2026 20:11:30 +0100 Subject: [PATCH] refactor(design-tokens): V2 engine + verifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates component styling to the element/tone/emphasis/state token system and adds a standalone verifier (`npm run verify:theme`) plus vitest coverage that exercises every mode × theme × contrast-grid variant. Co-Authored-By: Claude Opus 4.7 --- electron/main/index.ts | 10 +- package.json | 1 + scripts/verify-theme-tokens.ts | 195 ++++++ src/components/AddWorker/ToolSelect.tsx | 17 +- src/components/AddWorker/index.tsx | 10 +- .../BrowserAgentWorkspace/index.tsx | 26 +- src/components/ChatBox/FloatingAction.tsx | 4 +- src/components/ChatBox/TaskBox/TaskItem.tsx | 2 +- .../GroupedHistoryView/ProjectGroup.tsx | 28 +- .../Dashboard/GroupedHistoryView/TaskItem.tsx | 15 +- .../Dashboard/GroupedHistoryView/index.tsx | 21 +- src/components/Dashboard/Navigation/index.tsx | 6 +- .../Dashboard/SearchInput/index.tsx | 2 +- src/components/Dialog/CloseNotice.tsx | 2 +- src/components/Dialog/EndNotice.tsx | 4 +- src/components/Dialog/SearchHistoryDialog.tsx | 4 +- src/components/Folder/ZoomControls.tsx | 8 +- src/components/Folder/index.tsx | 4 +- src/components/HistorySidebar/index.tsx | 8 +- src/components/InstallStep/Carousel.tsx | 4 +- src/components/Layout/ThemeProvider.test.tsx | 101 +++ src/components/Layout/ThemeProvider.tsx | 29 +- src/components/Layout/tokenAliases.ts | 8 +- .../ProjectPageSidebar/HeaderAction.tsx | 2 +- src/components/ProjectPageSidebar/NavList.tsx | 8 +- src/components/ProjectPageSidebar/NavTab.tsx | 6 +- src/components/Terminal/index.tsx | 4 +- .../TerminalAgentWorkspace/index.tsx | 30 +- src/components/TopBar/index.tsx | 4 +- .../Trigger/DynamicTriggerConfig.tsx | 4 +- src/components/Trigger/Triggers.tsx | 2 +- src/components/WorkFlow/MarkDown.tsx | 6 +- src/components/WorkFlow/agents.tsx | 24 +- src/components/Workforce/ExpandedOverlay.tsx | 6 +- src/components/WorkforceMenu/index.tsx | 24 +- .../Workspace/WorkforceAgentList.tsx | 2 +- src/components/ui/accordion.tsx | 6 +- src/components/ui/alert.tsx | 7 +- src/components/ui/alertDialog.tsx | 20 +- src/components/ui/badge.tsx | 2 +- src/components/ui/button.tsx | 20 +- src/components/ui/card.tsx | 8 +- src/components/ui/checkbox.tsx | 2 +- src/components/ui/command.tsx | 21 +- src/components/ui/dialog.tsx | 4 +- src/components/ui/dropdown-menu.tsx | 12 +- src/components/ui/input-select.tsx | 20 +- src/components/ui/input.tsx | 20 +- src/components/ui/menu-button.tsx | 8 +- src/components/ui/popover.tsx | 20 +- src/components/ui/resizable.tsx | 4 +- src/components/ui/select.tsx | 22 +- src/components/ui/separator.tsx | 2 +- src/components/ui/sheet.tsx | 17 +- src/components/ui/sidebar.tsx | 48 +- src/components/ui/sonner.tsx | 8 +- src/components/ui/switch.tsx | 4 +- src/components/ui/table.tsx | 74 ++- src/components/ui/tabs.tsx | 6 +- src/components/ui/tag.tsx | 439 +++++++++---- src/components/ui/textarea.tsx | 32 +- src/components/ui/toggle.tsx | 4 +- src/components/ui/tokenAliases.ts | 30 +- src/components/update/index.tsx | 2 +- src/lib/themeTokens/MAPPING.md | 71 -- src/lib/themeTokens/README.md | 226 ++----- src/lib/themeTokens/catalog.ts | 181 +++--- src/lib/themeTokens/colorMath.ts | 185 +++++- src/lib/themeTokens/dtcg.ts | 150 +++++ src/lib/themeTokens/engine.ts | 606 +++++++++++------- src/lib/themeTokens/engine.v2.test.ts | 306 +++++++++ src/lib/themeTokens/fixedToneSchema.ts | 96 --- src/lib/themeTokens/index.ts | 3 +- src/lib/themeTokens/legacyMapping.ts | 172 ----- src/lib/themeTokens/types.ts | 116 ++-- src/lib/themeTokens/verifier.test.ts | 65 ++ src/lib/themeTokens/verifier.ts | 407 ++++++++++++ src/pages/Agents/Models.tsx | 40 +- .../Agents/components/SkillDeleteDialog.tsx | 2 +- .../Agents/components/SkillUploadDialog.tsx | 2 +- src/pages/Browser/CDP.tsx | 8 +- src/pages/Setting/Appearance.tsx | 502 +++++++-------- src/pages/Setting/General.tsx | 12 +- src/style/index.css | 40 +- src/style/token.css | 573 +---------------- tailwind.config.js | 200 +++--- tokens/base.color.json | 75 +++ tokens/component.color.json | 403 ++++++++++++ tokens/contracts/default.base.json | 15 + tokens/contracts/default.dark.json | 4 + tokens/contracts/default.light.json | 4 + tokens/manifest.json | 29 + tokens/semantic.color.json | 143 +++++ tsconfig.node.json | 2 +- 94 files changed, 3820 insertions(+), 2311 deletions(-) create mode 100644 scripts/verify-theme-tokens.ts create mode 100644 src/components/Layout/ThemeProvider.test.tsx delete mode 100644 src/lib/themeTokens/MAPPING.md create mode 100644 src/lib/themeTokens/dtcg.ts create mode 100644 src/lib/themeTokens/engine.v2.test.ts delete mode 100644 src/lib/themeTokens/fixedToneSchema.ts delete mode 100644 src/lib/themeTokens/legacyMapping.ts create mode 100644 src/lib/themeTokens/verifier.test.ts create mode 100644 src/lib/themeTokens/verifier.ts create mode 100644 tokens/base.color.json create mode 100644 tokens/component.color.json create mode 100644 tokens/contracts/default.base.json create mode 100644 tokens/contracts/default.dark.json create mode 100644 tokens/contracts/default.light.json create mode 100644 tokens/manifest.json create mode 100644 tokens/semantic.color.json diff --git a/electron/main/index.ts b/electron/main/index.ts index f6c49584..75a52aa5 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -413,13 +413,8 @@ protocol.registerSchemesAsPrivileged([ process.env.APP_ROOT = MAIN_DIST; process.env.VITE_PUBLIC = VITE_PUBLIC; -// Respect system theme on Windows, keep light theme on macOS for consistency -const isWindows = process.platform === 'win32'; -if (isWindows) { - nativeTheme.themeSource = 'system'; // Respect Windows dark/light mode -} else { - nativeTheme.themeSource = 'light'; // Keep existing behavior for macOS -} +// Always follow OS appearance so renderer `prefers-color-scheme` stays accurate. +nativeTheme.themeSource = 'system'; // Set log level log.transports.console.level = 'info'; @@ -2737,6 +2732,7 @@ let installationLock: Promise = Promise.resolve({ // ==================== window create ==================== async function createWindow() { const isMac = process.platform === 'darwin'; + const isWindows = process.platform === 'win32'; // Ensure .eigent directories exist before anything else ensureEigentDirectories(); diff --git a/package.json b/package.json index a21c7821..a112aa90 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "test:e2e": "vitest run --config vitest.config.ts", "test:coverage": "vitest run --coverage", "check:i18n": "node scripts/check-i18n-locale-parity.js", + "verify:theme": "vite-node scripts/verify-theme-tokens.ts", "type-check": "tsc -p tsconfig.build.json --noEmit", "lint": "eslint . --no-warn-ignored", "lint:fix": "eslint . --fix --no-warn-ignored", diff --git a/scripts/verify-theme-tokens.ts b/scripts/verify-theme-tokens.ts new file mode 100644 index 00000000..da70a125 --- /dev/null +++ b/scripts/verify-theme-tokens.ts @@ -0,0 +1,195 @@ +// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= + +// Standalone V2 design-token verification CLI. +// +// Runs the verifier over every registered theme/mode/contrast variant and +// prints a human-readable report. Exits with a non-zero code if any `error` +// findings are produced. Auxiliary contrast warnings do not fail the run +// unless `--strict` is passed. +// +// Usage: +// npm run verify:theme +// npm run verify:theme -- --strict # auxiliary warnings fail too +// npm run verify:theme -- --json # machine-readable output +// npm run verify:theme -- --contrast 0,50,100 + +import { + getDefaultContrastGrid, + listRegisteredThemes, + verifyThemeEngine, + type VerifyFinding, +} from '../src/lib/themeTokens/verifier'; + +type CliFlags = { + strict: boolean; + json: boolean; + contrastGrid: number[]; +}; + +function parseArgs(argv: string[]): CliFlags { + const flags: CliFlags = { + strict: false, + json: false, + contrastGrid: getDefaultContrastGrid(), + }; + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + if (arg === '--strict') flags.strict = true; + else if (arg === '--json') flags.json = true; + else if (arg === '--contrast') { + const raw = argv[++i]; + if (raw) { + flags.contrastGrid = raw + .split(',') + .map((s) => Number(s.trim())) + .filter((n) => Number.isFinite(n)); + } + } else if (arg === '--help' || arg === '-h') { + process.stdout.write( + [ + 'Usage: verify-theme-tokens [options]', + '', + 'Options:', + ' --strict Treat auxiliary contrast warnings as errors', + ' --json Emit JSON report on stdout', + ' --contrast a,b,c Override contrast grid (default: 0,25,43,75,100)', + ' -h, --help Show this help', + '', + ].join('\n') + ); + process.exit(0); + } + } + return flags; +} + +const COLORS = { + reset: '\x1b[0m', + red: '\x1b[31m', + yellow: '\x1b[33m', + green: '\x1b[32m', + dim: '\x1b[2m', + bold: '\x1b[1m', + cyan: '\x1b[36m', +}; + +function colorize(text: string, code: string): string { + if (!process.stdout.isTTY) return text; + return `${code}${text}${COLORS.reset}`; +} + +function groupFindings( + findings: VerifyFinding[] +): Map { + const groups = new Map(); + for (const f of findings) { + const key = `${f.mode} / ${f.themeId} / contrast=${f.contrast}`; + const bucket = groups.get(key); + if (bucket) bucket.push(f); + else groups.set(key, [f]); + } + return groups; +} + +function printHumanReport( + themes: Array<{ mode: string; id: string }>, + flags: CliFlags, + report: ReturnType +): void { + const { summary, findings } = report; + + process.stdout.write( + `\n${colorize('Design Token Engine Verification (V2)', COLORS.bold)}\n` + ); + process.stdout.write( + colorize( + ` Registered themes: ${themes.map((t) => `${t.mode}/${t.id}`).join(', ')}\n`, + COLORS.dim + ) + ); + process.stdout.write( + colorize( + ` Contrast grid: ${flags.contrastGrid.join(', ')}\n`, + COLORS.dim + ) + ); + process.stdout.write( + colorize(` Variants checked: ${summary.variantsChecked}\n\n`, COLORS.dim) + ); + + if (findings.length === 0) { + process.stdout.write( + `${colorize('PASS', COLORS.green)} No findings — engine is clean.\n\n` + ); + return; + } + + const groups = groupFindings(findings); + for (const [variant, bucket] of groups) { + process.stdout.write(`${colorize(variant, COLORS.cyan)}\n`); + for (const f of bucket) { + const badge = + f.severity === 'error' + ? colorize('ERROR', COLORS.red) + : colorize('WARN ', COLORS.yellow); + const ratioSuffix = + f.ratio !== undefined && f.threshold !== undefined + ? colorize( + ` (ratio ${f.ratio.toFixed(2)} / threshold ${f.threshold})`, + COLORS.dim + ) + : ''; + process.stdout.write( + ` ${badge} [${f.code}] ${f.message}${ratioSuffix}\n` + ); + if (f.tokenKey && f.value) { + process.stdout.write( + colorize(` ↳ ${f.tokenKey} = ${f.value}\n`, COLORS.dim) + ); + } + } + process.stdout.write('\n'); + } + + const errBadge = + summary.errors === 0 + ? colorize(`${summary.errors} errors`, COLORS.green) + : colorize(`${summary.errors} errors`, COLORS.red); + const warnBadge = + summary.warnings === 0 + ? colorize(`${summary.warnings} warnings`, COLORS.green) + : colorize(`${summary.warnings} warnings`, COLORS.yellow); + process.stdout.write(`Summary: ${errBadge}, ${warnBadge}\n\n`); +} + +function main() { + const flags = parseArgs(process.argv.slice(2)); + const themes = listRegisteredThemes(); + const report = verifyThemeEngine({ + contrastGrid: flags.contrastGrid, + strictAuxContrast: flags.strict, + }); + + if (flags.json) { + process.stdout.write(JSON.stringify({ themes, ...report }, null, 2) + '\n'); + } else { + printHumanReport(themes, flags, report); + } + + const failed = report.summary.errors > 0; + process.exit(failed ? 1 : 0); +} + +main(); diff --git a/src/components/AddWorker/ToolSelect.tsx b/src/components/AddWorker/ToolSelect.tsx index 4db50bf6..ea798b87 100644 --- a/src/components/AddWorker/ToolSelect.tsx +++ b/src/components/AddWorker/ToolSelect.tsx @@ -758,12 +758,12 @@ const ToolSelect = forwardRef< {(initialSelectedTools || []).map((item: any) => ( {item.name || item.mcp_name || item.key || `tool_${item.id}`} -
+
removeOption(item)} />
@@ -852,7 +852,7 @@ const ToolSelect = forwardRef< >
{/* {getCategoryIcon(item.category?.name)} */} -
+
{item.mcp_name}
@@ -863,10 +863,7 @@ const ToolSelect = forwardRef<
-
@@ -889,7 +886,7 @@ const ToolSelect = forwardRef< inputRef.current?.focus(); setIsOpen(true); }} - className="gap-1 rounded-lg border-input-border-default bg-input-bg-default py-1 flex max-h-[120px] min-h-[60px] w-full flex-wrap justify-start overflow-y-auto border border-solid px-[6px]" + className="gap-1 rounded-lg border-ds-border-neutral-default-default bg-ds-bg-neutral-default-default py-1 flex max-h-[120px] min-h-[60px] w-full flex-wrap justify-start overflow-y-auto border border-solid px-[6px]" > {renderSelectedItems()}