- {/* Hero Icon */}
+ {/* Hero Icon - Using gradient brand background */}
-
-
@@ -84,23 +75,15 @@ export function CoordinatorEmptyState({
{formatMessage({ id: 'coordinator.emptyState.subtitle' })}
- {/* Start Button - Using primary theme color */}
+ {/* Start Button - Using gradient and glow utilities */}
{/* Feature Cards */}
@@ -175,38 +158,26 @@ export function CoordinatorEmptyState({
{/* Quick Start Guide */}
-
- ✓
+
+ ok
{formatMessage({ id: 'coordinator.emptyState.quickStart.title' })}
-
+
1
{formatMessage({ id: 'coordinator.emptyState.quickStart.step1' })}
-
+
2
{formatMessage({ id: 'coordinator.emptyState.quickStart.step2' })}
-
+
3
{formatMessage({ id: 'coordinator.emptyState.quickStart.step3' })}
diff --git a/ccw/frontend/src/components/shared/ThemeSelector.tsx b/ccw/frontend/src/components/shared/ThemeSelector.tsx
index 08946de0..29786a13 100644
--- a/ccw/frontend/src/components/shared/ThemeSelector.tsx
+++ b/ccw/frontend/src/components/shared/ThemeSelector.tsx
@@ -192,25 +192,31 @@ export function ThemeSelector() {
/>
{/* Preview Swatches */}
-
-
+
+
{formatMessage({ id: 'theme.preview' })}:
-
-
-
+
+
+
{formatMessage({ id: 'theme.preview.background' })}
+
+
+
+
{formatMessage({ id: 'theme.preview.surface' })}
+
+
+
+
{formatMessage({ id: 'theme.preview.accent' })}
+
{/* Save and Reset Buttons */}
diff --git a/ccw/frontend/src/locales/en/theme.json b/ccw/frontend/src/locales/en/theme.json
index 4dc6683a..c86b445d 100644
--- a/ccw/frontend/src/locales/en/theme.json
+++ b/ccw/frontend/src/locales/en/theme.json
@@ -22,6 +22,9 @@
"current": "Current theme: {name}",
"hueValue": "Hue: {value}°",
"preview": "Preview",
+ "preview.background": "Background",
+ "preview.surface": "Card",
+ "preview.accent": "Accent",
"save": "Save Custom Theme",
"reset": "Reset to Preset"
}
diff --git a/ccw/frontend/src/locales/zh/theme.json b/ccw/frontend/src/locales/zh/theme.json
index a7935b40..9909340d 100644
--- a/ccw/frontend/src/locales/zh/theme.json
+++ b/ccw/frontend/src/locales/zh/theme.json
@@ -22,6 +22,9 @@
"current": "当前主题: {name}",
"hueValue": "色调: {value}°",
"preview": "预览",
+ "preview.background": "背景",
+ "preview.surface": "卡片",
+ "preview.accent": "强调色",
"save": "保存自定义主题",
"reset": "重置为预设"
}
diff --git a/ccw/frontend/tailwind.config.js b/ccw/frontend/tailwind.config.js
index 6f598ec7..73266d77 100644
--- a/ccw/frontend/tailwind.config.js
+++ b/ccw/frontend/tailwind.config.js
@@ -8,6 +8,9 @@ const gradientPlugin = plugin(function({ addUtilities, addComponents }) {
'.bg-gradient-primary': {
backgroundImage: 'radial-gradient(circle, hsl(var(--accent)) 0%, transparent 70%)',
},
+ '.bg-gradient-secondary': {
+ backgroundImage: 'radial-gradient(circle, hsl(var(--secondary)) 0%, transparent 70%)',
+ },
'.bg-gradient-brand': {
backgroundImage: 'linear-gradient(to right, hsl(var(--primary)), hsl(var(--secondary)))',
},
@@ -17,6 +20,19 @@ const gradientPlugin = plugin(function({ addUtilities, addComponents }) {
'.bg-gradient-conic': {
backgroundImage: 'conic-gradient(var(--tw-gradient-stops))',
},
+ // Hover glow effect utilities
+ '.hover-glow': {
+ transition: 'box-shadow 0.3s ease-in-out',
+ '&:hover': {
+ boxShadow: '0 0 40px 10px hsl(var(--accent) / 0.7)',
+ },
+ },
+ '.hover-glow-primary': {
+ transition: 'box-shadow 0.3s ease-in-out',
+ '&:hover': {
+ boxShadow: '0 0 40px 10px hsl(var(--primary) / 0.5)',
+ },
+ },
});
// 2. Gradient border component
diff --git a/ccw/tests/cli-final-only-output.test.js b/ccw/tests/cli-final-only-output.test.js
index 72f0f098..e51002ca 100644
--- a/ccw/tests/cli-final-only-output.test.js
+++ b/ccw/tests/cli-final-only-output.test.js
@@ -8,9 +8,13 @@
import { afterEach, describe, it, mock } from 'node:test';
import assert from 'node:assert/strict';
import http from 'node:http';
+import { mkdtempSync, rmSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
const cliCommandPath = new URL('../dist/commands/cli.js', import.meta.url).href;
const cliExecutorPath = new URL('../dist/tools/cli-executor.js', import.meta.url).href;
+const historyStorePath = new URL('../dist/tools/cli-history-store.js', import.meta.url).href;
function stubHttpRequest() {
mock.method(http, 'request', () => {
@@ -35,8 +39,17 @@ describe('ccw cli exec --final', async () => {
it('writes only finalOutput to stdout (no banner/summary)', async () => {
stubHttpRequest();
+ const testHome = mkdtempSync(join(tmpdir(), 'ccw-cli-final-only-'));
+ const prevHome = process.env.CCW_DATA_DIR;
+ process.env.CCW_DATA_DIR = testHome;
+
+ // Ensure the CLI doesn't wait for stdin in Node's test runner environment.
+ const prevStdinIsTty = process.stdin.isTTY;
+ Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
+
const cliModule = await import(cliCommandPath);
const cliExecutorModule = await import(cliExecutorPath);
+ const historyStoreModule = await import(historyStorePath);
const stdoutWrites = [];
mock.method(process.stdout, 'write', (chunk) => {
@@ -72,5 +85,14 @@ describe('ccw cli exec --final', async () => {
await cliModule.cliCommand('exec', [], { prompt: 'Hello', tool: 'gemini', final: true });
assert.equal(stdoutWrites.join(''), 'FINAL');
+
+ try {
+ historyStoreModule?.closeAllStores?.();
+ } catch {
+ // ignore
+ }
+ Object.defineProperty(process.stdin, 'isTTY', { value: prevStdinIsTty, configurable: true });
+ process.env.CCW_DATA_DIR = prevHome;
+ rmSync(testHome, { recursive: true, force: true });
});
});