Make pure white theme complete across desktop surfaces

The pure white appearance option needed to avoid warm-theme leakage while preserving the existing warm classic brand theme. This adds the white theme mode, keeps local browser startup from reusing stale H5 server URLs in dev, and moves visible legacy warm surfaces onto theme tokens.

Constraint: H5 server auth policy, CORS policy, SDK routes, adapter routes, and IM access paths must not change for a visual theme fix
Rejected: Rename the original light theme to pure white | the original theme is a warm classic palette, not a neutral white workspace
Confidence: high
Scope-risk: moderate
Directive: Keep structural white-theme borders neutral; reserve the warm brand color for selected states, primary actions, and small accents
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx src/lib/desktopRuntime.test.ts src/lib/persistenceMigrations.test.ts src/stores/uiStore.test.ts src/stores/settingsStore.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: bun test src/server/__tests__/settings.test.ts src/server/__tests__/h5-access-policy.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/middleware/cors.test.ts
Tested: Browser smoke at http://127.0.0.1:5173 with data-theme=white, no H5 token prompt, /status inspector visible, inspector border #DDE3EA
Not-tested: Full bun run verify gate
This commit is contained in:
程序员阿江(Relakkes) 2026-05-12 20:37:21 +08:00
parent 9b540554cc
commit a381bf0412
23 changed files with 555 additions and 240 deletions

View File

@ -6,8 +6,8 @@
<title>Claude Code Companion</title>
<!-- Fonts are self-hosted in /fonts/ and declared via @font-face in globals.css -->
<style>
/* Prevent white flash on load */
html { background: #FAF9F5; }
/* Prevent a warm flash before the theme is hydrated. */
html { background: #FFFFFF; }
</style>
</head>
<body>

View File

@ -8,6 +8,7 @@ import { useUIStore } from '../stores/uiStore'
import { useUpdateStore } from '../stores/updateStore'
import type { SavedProvider } from '../types/provider'
import type { ProviderPreset } from '../types/providerPreset'
import type { ThemeMode } from '../types/settings'
const MOCK_DELETE_PROVIDER = vi.fn()
const MOCK_GET_SETTINGS = vi.fn()
@ -138,6 +139,7 @@ describe('Settings > General tab', () => {
useSettingsStore.setState({
locale: 'en',
theme: 'light',
thinkingEnabled: true,
skipWebFetchPreflight: true,
desktopNotificationsEnabled: true,
@ -153,6 +155,9 @@ describe('Settings > General tab', () => {
setThinkingEnabled: vi.fn().mockImplementation(async (enabled: boolean) => {
useSettingsStore.setState({ thinkingEnabled: enabled })
}),
setTheme: vi.fn().mockImplementation(async (theme: ThemeMode) => {
useSettingsStore.setState({ theme })
}),
setSkipWebFetchPreflight: vi.fn().mockImplementation(async (enabled: boolean) => {
useSettingsStore.setState({ skipWebFetchPreflight: enabled })
}),
@ -227,6 +232,25 @@ describe('Settings > General tab', () => {
expect(toggle).toBeChecked()
})
it('offers the pure white appearance theme', () => {
render(<Settings />)
fireEvent.click(screen.getByText('General'))
fireEvent.click(screen.getByRole('button', { name: 'Pure White' }))
expect(useSettingsStore.getState().setTheme).toHaveBeenCalledWith('white')
})
it('marks the pure white appearance theme as selected', () => {
useSettingsStore.setState({ theme: 'white' })
render(<Settings />)
fireEvent.click(screen.getByText('General'))
expect(screen.getByRole('button', { name: 'Pure White' })).toHaveAttribute('aria-pressed', 'true')
expect(screen.getByRole('button', { name: 'Warm Classic' })).toHaveAttribute('aria-pressed', 'false')
})
it('opens the Token usage tab from Settings navigation above Diagnostics', () => {
render(<Settings />)

View File

@ -32,11 +32,11 @@ type Translate = ReturnType<typeof useTranslation>
function toneForStatus(status: McpServerRecord['status']) {
switch (status) {
case 'connected':
return 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20'
return 'bg-[var(--color-inspector-success-bg)] text-[var(--color-inspector-success)] border-[var(--color-inspector-border)]'
case 'needs-auth':
return 'bg-amber-500/10 text-amber-600 border-amber-500/20'
return 'bg-[var(--color-surface-container-low)] text-[var(--color-warning)] border-[var(--color-border)]'
case 'failed':
return 'bg-rose-500/10 text-rose-600 border-rose-500/20'
return 'bg-[var(--color-inspector-danger-bg)] text-[var(--color-inspector-danger)] border-[var(--color-inspector-border)]'
case 'disabled':
return 'bg-[var(--color-surface-hover)] text-[var(--color-text-secondary)] border-[var(--color-border)]'
default:
@ -114,7 +114,7 @@ function EmptyState({ title, body }: { title: string; body: string }) {
function ErrorState({ message }: { message: string }) {
return (
<div className="rounded-2xl border border-[var(--color-error)]/20 bg-[var(--color-error)]/8 px-5 py-4 text-sm text-[var(--color-error)]">
<div className="rounded-2xl border border-[var(--color-inspector-border)] bg-[var(--color-inspector-panel)] px-5 py-4 text-sm text-[var(--color-inspector-danger)]">
{message}
</div>
)
@ -270,7 +270,7 @@ function UsageTab({
</div>
)}
{usage.hasUnknownModelCost && (
<div className="rounded-xl border border-amber-500/20 bg-amber-500/10 px-4 py-3 text-sm text-amber-700">
<div className="rounded-xl border border-[var(--color-inspector-border)] bg-[var(--color-inspector-panel)] px-4 py-3 text-sm text-[var(--color-warning)]">
{t('slash.inspector.usage.unknownCost')}
</div>
)}
@ -593,9 +593,7 @@ function StatusTab({
{mcpServers.map((server) => (
<div
key={`${server.name}:${server.status}`}
className={`flex min-h-[48px] items-center justify-between gap-4 rounded-md border px-4 py-3 font-mono ${
server.status === 'failed' ? 'border-[var(--color-inspector-danger-border)] bg-[var(--color-inspector-danger-surface)]' : 'border-[var(--color-inspector-border)] bg-[var(--color-inspector-panel)]'
}`}
className="flex min-h-[48px] items-center justify-between gap-4 rounded-md border border-[var(--color-inspector-border)] bg-[var(--color-inspector-panel)] px-4 py-3 font-mono"
>
<div className="flex min-w-0 items-center gap-3">
<McpServerIcon status={server.status} />

View File

@ -677,9 +677,10 @@ export const en = {
// Settings > General
'settings.general.appearanceTitle': 'Appearance',
'settings.general.appearanceDescription': 'Switch between the original light workspace and the new dark workspace.',
'settings.general.appearance.light': 'Light',
'settings.general.appearanceDescription': 'Switch between the warm classic workspace, dark workspace, and a pure white workspace.',
'settings.general.appearance.light': 'Warm Classic',
'settings.general.appearance.dark': 'Dark',
'settings.general.appearance.white': 'Pure White',
'settings.general.languageTitle': 'Language',
'settings.general.languageDescription': 'Choose the display language for the application.',
'settings.general.responseLangTitle': 'Response Language',

View File

@ -679,9 +679,10 @@ export const zh: Record<TranslationKey, string> = {
// Settings > General
'settings.general.appearanceTitle': '配色主题',
'settings.general.appearanceDescription': '在亮色与暗色工作区之间切换,不影响原有亮色主题。',
'settings.general.appearance.light': '色',
'settings.general.appearanceDescription': '在经典暖色、暗色与纯白工作区之间切换。',
'settings.general.appearance.light': '经典暖色',
'settings.general.appearance.dark': '暗色',
'settings.general.appearance.white': '纯白',
'settings.general.languageTitle': '语言',
'settings.general.languageDescription': '选择应用程序的显示语言。',
'settings.general.responseLangTitle': '回复语言',

View File

@ -158,6 +158,26 @@ describe('desktopRuntime browser H5 bootstrap', () => {
})
})
it('prefers an explicit Vite desktop server URL over a remembered H5 server URL', async () => {
clientMocks.defaultBaseUrl = 'http://127.0.0.1:55189'
clientMocks.explicitDefaultBaseUrl = true
window.history.pushState({}, '', '/')
window.localStorage.setItem(H5_SERVER_URL_STORAGE_KEY, 'http://192.168.0.102:3456')
window.localStorage.setItem(H5_TOKEN_STORAGE_KEY, 'stale-h5-token')
globalThis.fetch = vi.fn().mockResolvedValue(
healthOkResponse(),
) as typeof fetch
await expect(initializeDesktopServerUrl()).resolves.toBe('http://127.0.0.1:55189')
expect(clientMocks.setBaseUrl).toHaveBeenLastCalledWith('http://127.0.0.1:55189')
expect(clientMocks.setAuthToken).toHaveBeenLastCalledWith(null)
expect(clientMocks.postVerify).not.toHaveBeenCalled()
expect(globalThis.fetch).toHaveBeenCalledWith('http://127.0.0.1:55189/api/status', {
cache: 'no-store',
})
})
it('normalizes unreachable remote browser startup into a recoverable H5 error', async () => {
vi.useFakeTimers()
globalThis.fetch = vi.fn().mockRejectedValue(new TypeError('Failed to fetch')) as typeof fetch

View File

@ -137,10 +137,11 @@ async function initializeBrowserServerUrl(fallbackUrl: string) {
const queryUrl = query?.get('serverUrl') ?? null
const queryToken = normalizeToken(query?.get('h5Token') ?? query?.get('token'))
const stored = readStoredH5Connection()
const configuredUrl = getConfiguredBrowserServerUrl(fallbackUrl)
const requestedUrl =
normalizeServerUrl(queryUrl) ??
configuredUrl ??
stored.serverUrl ??
getConfiguredBrowserServerUrl(fallbackUrl) ??
fallbackUrl
const token = queryToken ?? stored.token
const browserH5Runtime = requiresH5AuthForServerUrl(requestedUrl)

View File

@ -56,6 +56,15 @@ describe('desktop persistence migrations', () => {
expect(window.localStorage.getItem('cc-haha-theme')).toBeNull()
})
test('preserves the pure white theme as a valid persisted theme', () => {
window.localStorage.setItem('cc-haha-theme', 'white')
const report = runDesktopPersistenceMigrations()
expect(report.migratedKeys).not.toContain('cc-haha-theme')
expect(window.localStorage.getItem('cc-haha-theme')).toBe('white')
})
test('does not throw if schema version persistence is blocked', () => {
const storage = {
getItem: window.localStorage.getItem.bind(window.localStorage),

View File

@ -1,3 +1,5 @@
import { THEME_MODES } from '../types/settings'
export const CURRENT_DESKTOP_PERSISTENCE_SCHEMA_VERSION = 1
export const DESKTOP_PERSISTENCE_VERSION_KEY = 'cc-haha.persistence.schemaVersion'
@ -138,7 +140,7 @@ export function runDesktopPersistenceMigrations(storage: StorageLike | null = ge
runMigrationStep(report, TAB_STORAGE_KEY, () => migrateTabs(storage, report))
runMigrationStep(report, SESSION_RUNTIME_STORAGE_KEY, () => migrateSessionRuntime(storage, report))
runMigrationStep(report, THEME_STORAGE_KEY, () => normalizeEnumKey(storage, THEME_STORAGE_KEY, ['light', 'dark'], report))
runMigrationStep(report, THEME_STORAGE_KEY, () => normalizeEnumKey(storage, THEME_STORAGE_KEY, [...THEME_MODES], report))
runMigrationStep(report, LOCALE_STORAGE_KEY, () => normalizeEnumKey(storage, LOCALE_STORAGE_KEY, ['zh', 'en'], report))
try {
storage.setItem(DESKTOP_PERSISTENCE_VERSION_KEY, String(CURRENT_DESKTOP_PERSISTENCE_SCHEMA_VERSION))

View File

@ -31,10 +31,10 @@ const HEAT_CELL_MAX = 22
const TOOLTIP_WIDTH = 172
const HEAT_COLORS = [
'var(--color-surface-container)',
'#f5d4c4',
'#eea47f',
'#df6b47',
'#bd432c',
'var(--color-primary-fixed)',
'var(--color-primary-fixed-dim)',
'var(--color-primary-container)',
'var(--color-primary)',
]
const DATE_LOCALES: Record<Locale, string> = {
en: 'en-US',
@ -401,11 +401,11 @@ export function ActivitySettings() {
<div
id={`activity-day-tooltip-${tooltipDay.date}`}
role="tooltip"
className="pointer-events-none absolute z-20 min-w-[172px] rounded-md border border-[#2f2825] bg-[#2f2825] px-3 py-2 text-xs shadow-xl"
className="pointer-events-none absolute z-20 min-w-[172px] rounded-md border border-[var(--color-inverse-surface)] bg-[var(--color-inverse-surface)] px-3 py-2 text-xs shadow-xl"
style={tooltipStyle}
>
<div className="font-medium text-[#fffaf6]">{formatDateLabel(tooltipDay.date, locale)}</div>
<div className="mt-1 text-[#f2d0c0]">
<div className="font-medium text-[var(--color-inverse-on-surface)]">{formatDateLabel(tooltipDay.date, locale)}</div>
<div className="mt-1 text-[var(--color-primary-fixed)]">
{formatSessionCount(tooltipDay.sessionCount, t)} · {formatTokens(tooltipDay.tokens)} tokens
</div>
</div>

View File

@ -62,10 +62,10 @@ const MCP_GROUP_ORDER: McpGroupKey[] = [
]
const STATUS_TONE: Record<McpServerRecord['status'], string> = {
connected: 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20',
checking: 'bg-slate-500/10 text-slate-600 border-slate-500/20',
'needs-auth': 'bg-amber-500/10 text-amber-600 border-amber-500/20',
failed: 'bg-rose-500/10 text-rose-600 border-rose-500/20',
connected: 'bg-[var(--color-inspector-success-bg)] text-[var(--color-inspector-success)] border-[var(--color-border)]',
checking: 'bg-[var(--color-surface-hover)] text-[var(--color-text-secondary)] border-[var(--color-border)]',
'needs-auth': 'bg-[var(--color-surface-container-low)] text-[var(--color-warning)] border-[var(--color-border)]',
failed: 'bg-[var(--color-inspector-danger-bg)] text-[var(--color-inspector-danger)] border-[var(--color-border)]',
disabled: 'bg-[var(--color-surface-hover)] text-[var(--color-text-secondary)] border-[var(--color-border)]',
}

View File

@ -22,35 +22,35 @@ export default function NewTaskModal() {
return (
<div className="bg-[var(--color-background)] text-[var(--color-on-surface)] font-[var(--font-body)] min-h-screen flex flex-col">
{/* ─── TopAppBar / Header ──────────────────────────────────── */}
<header className="bg-[#FAF9F5] flex justify-between items-center px-6 h-12 w-full z-40">
<header className="bg-[var(--color-background)] flex justify-between items-center px-6 h-12 w-full z-40">
<div className="flex items-center gap-6">
<span className="text-sm font-bold text-[#1B1C1A] uppercase tracking-tighter font-[var(--font-headline)]">
<span className="text-sm font-bold text-[var(--color-text-primary)] uppercase tracking-tighter font-[var(--font-headline)]">
Claude Code Companion
</span>
<nav className="hidden md:flex gap-4 font-[var(--font-headline)] font-semibold tracking-wide text-sm">
<a className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer">Code</a>
<a className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer">Terminal</a>
<a className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer">History</a>
<a className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer">Code</a>
<a className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer">Terminal</a>
<a className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer">History</a>
</nav>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-[#8F482F]">
<div className="flex items-center gap-2 text-[var(--color-brand)]">
<span className="material-symbols-outlined cursor-pointer active:opacity-70 text-sm">arrow_back_ios</span>
<span className="material-symbols-outlined cursor-pointer active:opacity-70 text-sm">arrow_forward_ios</span>
</div>
<span className="font-[var(--font-headline)] font-semibold tracking-wide text-sm text-[#87736D] cursor-pointer hover:text-[#8F482F] transition-colors">
<span className="font-[var(--font-headline)] font-semibold tracking-wide text-sm text-[var(--color-text-tertiary)] cursor-pointer hover:text-[var(--color-brand)] transition-colors">
Settings
</span>
</div>
</header>
{/* header divider */}
<div className="bg-[#F4F4F0] h-px w-full" />
<div className="bg-[var(--color-surface-container-low)] h-px w-full" />
{/* ─── Main area (sidebar + content + overlay) ──────────────── */}
<main className="flex-1 flex overflow-hidden relative">
{/* ── Sidebar (dimmed) ────────────────────────────────────── */}
<aside className="hidden md:flex flex-col p-4 gap-2 bg-[#F4F4F0] h-full w-[280px] opacity-30 pointer-events-none">
<aside className="hidden md:flex flex-col p-4 gap-2 bg-[var(--color-surface-container-low)] h-full w-[280px] opacity-30 pointer-events-none">
{/* project selector */}
<div className="flex items-center gap-3 px-2 py-3 mb-2">
<div className="w-8 h-8 rounded bg-[var(--color-surface-dim)] flex items-center justify-center">
@ -64,22 +64,22 @@ export default function NewTaskModal() {
{/* nav items -- session counts derived from mockSessions */}
<div className="font-[var(--font-body)] text-sm font-medium space-y-1">
<div className="flex items-center gap-3 px-3 py-2 text-[#87736D]">
<div className="flex items-center gap-3 px-3 py-2 text-[var(--color-text-tertiary)]">
<span className="material-symbols-outlined">add</span>New session
</div>
<div className="flex items-center gap-3 px-3 py-2 bg-[#FAF9F5] text-[#1B1C1A] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[#8F482F] before:rounded-full">
<div className="flex items-center gap-3 px-3 py-2 bg-[var(--color-background)] text-[var(--color-text-primary)] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[var(--color-brand)] before:rounded-full">
<span className="material-symbols-outlined">calendar_today</span>Scheduled
</div>
<div className="flex items-center gap-3 px-3 py-2 text-[#87736D]">
<div className="flex items-center gap-3 px-3 py-2 text-[var(--color-text-tertiary)]">
<span className="material-symbols-outlined">history</span>Today
{mockSessions.today.length > 0 && (
<span className="ml-auto text-[10px] text-[#87736D]">{mockSessions.today.length}</span>
<span className="ml-auto text-[10px] text-[var(--color-text-tertiary)]">{mockSessions.today.length}</span>
)}
</div>
<div className="flex items-center gap-3 px-3 py-2 text-[#87736D]">
<div className="flex items-center gap-3 px-3 py-2 text-[var(--color-text-tertiary)]">
<span className="material-symbols-outlined">event_note</span>Previous 7 Days
{mockSessions.previous7Days.length > 0 && (
<span className="ml-auto text-[10px] text-[#87736D]">{mockSessions.previous7Days.length}</span>
<span className="ml-auto text-[10px] text-[var(--color-text-tertiary)]">{mockSessions.previous7Days.length}</span>
)}
</div>
</div>
@ -317,25 +317,25 @@ export default function NewTaskModal() {
</main>
{/* ─── Footer / Status Bar ─────────────────────────────────── */}
<footer className="bg-[#FAF9F5] flex items-center justify-between px-4 z-50 fixed bottom-0 left-0 w-full h-8 border-t border-[#87736D]/20 font-[var(--font-body)] text-xs tracking-tight">
<footer className="bg-[var(--color-background)] flex items-center justify-between px-4 z-50 fixed bottom-0 left-0 w-full h-8 border-t border-[var(--color-border)]/20 font-[var(--font-body)] text-xs tracking-tight">
<div className="flex items-center gap-3">
<span className="text-[#87736D]">
<span className="text-[var(--color-text-tertiary)]">
{mockStatusBar.user} &bull; {mockStatusBar.username} &bull; {mockStatusBar.plan}
</span>
<div className="flex items-center gap-4 ml-4">
<span className="text-[#87736D] hover:bg-[#F4F4F0] px-2 py-0.5 rounded cursor-pointer transition-colors">
<span className="text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded cursor-pointer transition-colors">
{mockStatusBar.branch}
</span>
<span className="text-[#87736D] hover:bg-[#F4F4F0] px-2 py-0.5 rounded cursor-pointer transition-colors">
<span className="text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded cursor-pointer transition-colors">
{mockStatusBar.worktreeToggle}
</span>
</div>
</div>
<div className="flex items-center gap-4">
<span className="text-[#8F482F] font-bold cursor-pointer">
<span className="text-[var(--color-brand)] font-bold cursor-pointer">
{mockStatusBar.localSwitch}
</span>
<div className="flex items-center gap-1 text-[#87736D]">
<div className="flex items-center gap-1 text-[var(--color-text-tertiary)]">
<span className="material-symbols-outlined text-[14px]">terminal</span>
<span>{mockStatusBar.status === 'Ready' ? 'Active' : mockStatusBar.status}</span>
</div>

View File

@ -4,50 +4,50 @@ import { mockStatusBar } from '../mocks/data'
export function ScheduledTasksEmpty() {
const t = useTranslation()
return (
<div className="bg-[#FAF9F5] text-[#1B1C1A] flex min-h-screen font-[Inter,sans-serif]">
<div className="bg-[var(--color-background)] text-[var(--color-text-primary)] flex min-h-screen font-[Inter,sans-serif]">
{/* SideNavBar */}
<aside className="fixed left-0 top-0 h-full w-[280px] bg-[#F4F4F0] flex flex-col p-4 gap-2 text-sm font-medium z-40">
<aside className="fixed left-0 top-0 h-full w-[280px] bg-[var(--color-surface-container-low)] flex flex-col p-4 gap-2 text-sm font-medium z-40">
<div className="flex items-center gap-3 px-2 mb-6 mt-12">
<div className="w-8 h-8 rounded bg-[#E3E2DF] flex items-center justify-center">
<span className="material-symbols-outlined text-[#87736D]">filter_list</span>
<div className="w-8 h-8 rounded bg-[var(--color-surface-container-highest)] flex items-center justify-center">
<span className="material-symbols-outlined text-[var(--color-text-tertiary)]">filter_list</span>
</div>
<div>
<div className="text-[#1B1C1A] font-bold">{t('sidebar.allProjects')}</div>
<div className="text-[10px] text-[#87736D] uppercase tracking-widest">{t('scheduledPage.activeSession')}</div>
<div className="text-[var(--color-text-primary)] font-bold">{t('sidebar.allProjects')}</div>
<div className="text-[10px] text-[var(--color-text-tertiary)] uppercase tracking-widest">{t('scheduledPage.activeSession')}</div>
</div>
</div>
<nav className="flex-1 space-y-1">
<div className="px-3 py-2 text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<div className="px-3 py-2 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">add</span>
<span>{t('sidebar.newSession')}</span>
</div>
{/* Active State: Scheduled */}
<div className="px-3 py-2 bg-[#FAF9F5] text-[#1B1C1A] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[#8F482F] before:rounded-full cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<div className="px-3 py-2 bg-[var(--color-background)] text-[var(--color-text-primary)] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[var(--color-brand)] before:rounded-full cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">calendar_today</span>
<span>{t('sidebar.scheduled')}</span>
</div>
<div className="px-3 py-2 text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<div className="px-3 py-2 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">history</span>
<span>{t('sidebar.timeGroup.today')}</span>
</div>
<div className="px-3 py-2 text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<div className="px-3 py-2 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">event_note</span>
<span>{t('sidebar.timeGroup.last7days')}</span>
</div>
<div className="px-3 py-2 text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<div className="px-3 py-2 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">archive</span>
<span>{t('sidebar.timeGroup.older')}</span>
</div>
</nav>
<div className="mt-auto pt-4 border-t border-[#87736D]/10">
<div className="text-[10px] text-[#87736D] uppercase tracking-widest px-3 mb-2">{t('scheduledPage.executionMode')}</div>
<div className="px-3 py-2 text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<div className="mt-auto pt-4 border-t border-[var(--color-border)]/10">
<div className="text-[10px] text-[var(--color-text-tertiary)] uppercase tracking-widest px-3 mb-2">{t('scheduledPage.executionMode')}</div>
<div className="px-3 py-2 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">computer</span>
<span>{t('scheduledPage.localMode')}</span>
</div>
<div className="px-3 py-2 text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<div className="px-3 py-2 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">cloud</span>
<span>{t('scheduledPage.remoteMode')}</span>
</div>
@ -57,49 +57,49 @@ export function ScheduledTasksEmpty() {
{/* Main Canvas */}
<main className="flex-1 ml-[280px] flex flex-col min-h-screen">
{/* TopAppBar */}
<header className="fixed top-0 right-0 left-[280px] z-30 bg-[#FAF9F5] flex justify-between items-center px-6 h-12 w-full border-b border-[#F4F4F0]">
<header className="fixed top-0 right-0 left-[280px] z-30 bg-[var(--color-background)] flex justify-between items-center px-6 h-12 w-full border-b border-[var(--color-border)]">
<div className="flex items-center gap-6 h-full">
<div className="text-sm font-bold text-[#1B1C1A] uppercase tracking-tighter font-[Manrope,sans-serif]">Claude Code Companion</div>
<div className="text-sm font-bold text-[var(--color-text-primary)] uppercase tracking-tighter font-[Manrope,sans-serif]">Claude Code Companion</div>
<nav className="flex items-center gap-4 h-full font-[Manrope,sans-serif] font-semibold tracking-wide text-sm">
<span className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer active:opacity-70 h-full flex items-center">{t('titlebar.code')}</span>
<span className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer active:opacity-70 h-full flex items-center">{t('titlebar.terminal')}</span>
<span className="text-[#1B1C1A] border-b-2 border-[#8F482F] pb-1 h-full flex items-center pt-1">{t('titlebar.history')}</span>
<span className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer active:opacity-70 h-full flex items-center">{t('titlebar.code')}</span>
<span className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer active:opacity-70 h-full flex items-center">{t('titlebar.terminal')}</span>
<span className="text-[var(--color-text-primary)] border-b-2 border-[var(--color-brand)] pb-1 h-full flex items-center pt-1">{t('titlebar.history')}</span>
</nav>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<span className="material-symbols-outlined text-[#87736D] text-sm cursor-pointer">arrow_back_ios</span>
<span className="material-symbols-outlined text-[#87736D] text-sm cursor-pointer">arrow_forward_ios</span>
<span className="material-symbols-outlined text-[var(--color-text-tertiary)] text-sm cursor-pointer">arrow_back_ios</span>
<span className="material-symbols-outlined text-[var(--color-text-tertiary)] text-sm cursor-pointer">arrow_forward_ios</span>
</div>
<div className="h-4 w-[1px] bg-[#87736D]/20"></div>
<div className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer text-xs font-semibold uppercase tracking-wider">{t('sidebar.settings')}</div>
<div className="h-4 w-[1px] bg-[var(--color-text-tertiary)]/20"></div>
<div className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer text-xs font-semibold uppercase tracking-wider">{t('sidebar.settings')}</div>
</div>
</header>
{/* Content Area */}
<div className="flex-1 mt-12 mb-8 flex flex-col items-center justify-center p-8 bg-[#FAF9F5]">
<div className="flex-1 mt-12 mb-8 flex flex-col items-center justify-center p-8 bg-[var(--color-background)]">
<div className="max-w-2xl w-full text-center">
<h1 className="text-3xl font-[Manrope,sans-serif] font-extrabold text-[#1B1C1A] tracking-tight mb-16">{t('scheduledPage.title')}</h1>
<h1 className="text-3xl font-[Manrope,sans-serif] font-extrabold text-[var(--color-text-primary)] tracking-tight mb-16">{t('scheduledPage.title')}</h1>
{/* Empty State Illustration/Card */}
<div className="relative group">
{/* Architectural Background Detail */}
<div className="absolute -inset-4 bg-[#F4F4F0] rounded-[32px] -z-10 transition-all"></div>
<div className="absolute -inset-4 bg-[var(--color-surface-container-low)] rounded-[32px] -z-10 transition-all"></div>
<div className="flex flex-col items-center py-20 px-8">
<div className="w-32 h-32 rounded-full bg-[#E9E8E4] flex items-center justify-center mb-10 shadow-sm">
<div className="w-20 h-20 rounded-full bg-white flex items-center justify-center border border-[#DAC1BA]/10">
<div className="w-32 h-32 rounded-full bg-[var(--color-surface-container-high)] flex items-center justify-center mb-10 shadow-sm">
<div className="w-20 h-20 rounded-full bg-[var(--color-surface-container-lowest)] flex items-center justify-center border border-[var(--color-border)]/10">
<span
className="material-symbols-outlined text-[#8F482F] text-5xl"
className="material-symbols-outlined text-[var(--color-brand)] text-5xl"
style={{ fontVariationSettings: "'wght' 300" }}
>
schedule
</span>
</div>
</div>
<p className="text-[#54433E] font-[Inter,sans-serif] text-lg max-w-sm mx-auto leading-relaxed mb-12">
<p className="text-[var(--color-text-secondary)] font-[Inter,sans-serif] text-lg max-w-sm mx-auto leading-relaxed mb-12">
{t('tasks.emptyTitle')} {t('tasks.emptyDesc')}
</p>
<button className="group relative px-8 py-4 bg-[#8F482F] text-white rounded-xl font-[Manrope,sans-serif] font-bold text-sm tracking-wide shadow-lg hover:shadow-[#8F482F]/20 transition-all flex items-center gap-3 overflow-hidden">
<button className="group relative px-8 py-4 bg-[var(--color-brand)] text-white rounded-xl font-[Manrope,sans-serif] font-bold text-sm tracking-wide shadow-lg hover:shadow-[var(--color-brand)]/20 transition-all flex items-center gap-3 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-white/10 to-transparent opacity-30"></div>
<span className="material-symbols-outlined text-lg">add_task</span>
<span>{t('tasks.newTask')}</span>
@ -109,38 +109,38 @@ export function ScheduledTasksEmpty() {
{/* Subtle Decorative Bento Elements */}
<div className="grid grid-cols-3 gap-4 mt-20 opacity-40">
<div className="h-24 bg-[#F4F4F0] rounded-xl border border-[#DAC1BA]/10 flex flex-col items-center justify-center p-4">
<span className="material-symbols-outlined text-[#87736D] mb-2">commit</span>
<div className="w-12 h-1 bg-[#DAC1BA]/30 rounded-full"></div>
<div className="h-24 bg-[var(--color-surface-container-low)] rounded-xl border border-[var(--color-border)]/10 flex flex-col items-center justify-center p-4">
<span className="material-symbols-outlined text-[var(--color-text-tertiary)] mb-2">commit</span>
<div className="w-12 h-1 bg-[var(--color-border)]/30 rounded-full"></div>
</div>
<div className="h-24 bg-[#F4F4F0] rounded-xl border border-[#DAC1BA]/10 flex flex-col items-center justify-center p-4">
<span className="material-symbols-outlined text-[#87736D] mb-2">terminal</span>
<div className="w-8 h-1 bg-[#DAC1BA]/30 rounded-full"></div>
<div className="h-24 bg-[var(--color-surface-container-low)] rounded-xl border border-[var(--color-border)]/10 flex flex-col items-center justify-center p-4">
<span className="material-symbols-outlined text-[var(--color-text-tertiary)] mb-2">terminal</span>
<div className="w-8 h-1 bg-[var(--color-border)]/30 rounded-full"></div>
</div>
<div className="h-24 bg-[#F4F4F0] rounded-xl border border-[#DAC1BA]/10 flex flex-col items-center justify-center p-4">
<span className="material-symbols-outlined text-[#87736D] mb-2">code_blocks</span>
<div className="w-10 h-1 bg-[#DAC1BA]/30 rounded-full"></div>
<div className="h-24 bg-[var(--color-surface-container-low)] rounded-xl border border-[var(--color-border)]/10 flex flex-col items-center justify-center p-4">
<span className="material-symbols-outlined text-[var(--color-text-tertiary)] mb-2">code_blocks</span>
<div className="w-10 h-1 bg-[var(--color-border)]/30 rounded-full"></div>
</div>
</div>
</div>
</div>
{/* Footer */}
<footer className="fixed bottom-0 left-0 w-full h-8 bg-[#FAF9F5] flex items-center justify-between px-4 z-50 border-t border-[#87736D]/20 font-[Inter,sans-serif] text-xs tracking-tight">
<div className="flex items-center gap-3 text-[#87736D]">
<div className="w-2 h-2 rounded-full bg-[#677B4E]"></div>
<footer className="fixed bottom-0 left-0 w-full h-8 bg-[var(--color-background)] flex items-center justify-between px-4 z-50 border-t border-[var(--color-border)]/20 font-[Inter,sans-serif] text-xs tracking-tight">
<div className="flex items-center gap-3 text-[var(--color-text-tertiary)]">
<div className="w-2 h-2 rounded-full bg-[var(--color-success)]"></div>
<span>{mockStatusBar.user} &bull; {mockStatusBar.username} &bull; {mockStatusBar.plan}</span>
</div>
<div className="flex items-center gap-6">
<span className="text-[#87736D] hover:bg-[#F4F4F0] px-2 py-0.5 rounded transition-colors cursor-pointer flex items-center gap-1">
<span className="text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded transition-colors cursor-pointer flex items-center gap-1">
<span className="material-symbols-outlined text-[14px]">account_tree</span>
{mockStatusBar.branch}
</span>
<span className="text-[#87736D] hover:bg-[#F4F4F0] px-2 py-0.5 rounded transition-colors cursor-pointer flex items-center gap-1">
<span className="text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded transition-colors cursor-pointer flex items-center gap-1">
<span className="material-symbols-outlined text-[14px]">workspaces</span>
{mockStatusBar.worktreeToggle}
</span>
<span className="text-[#8F482F] font-bold hover:bg-[#F4F4F0] px-2 py-0.5 rounded transition-colors cursor-pointer flex items-center gap-1">
<span className="text-[var(--color-brand)] font-bold hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded transition-colors cursor-pointer flex items-center gap-1">
<span className="material-symbols-outlined text-[14px]">toggle_on</span>
{mockStatusBar.localSwitch}
</span>

View File

@ -9,11 +9,11 @@ export function ScheduledTasksList() {
const task2 = tasks[2]!
return (
<div className="bg-[#FAF9F5] text-[#1B1C1A] flex min-h-screen overflow-hidden font-[Inter,sans-serif]">
<div className="bg-[var(--color-background)] text-[var(--color-text-primary)] flex min-h-screen overflow-hidden font-[Inter,sans-serif]">
{/* SideNavBar */}
<aside className="fixed left-0 top-0 h-full w-[280px] bg-[#F4F4F0] flex flex-col p-4 gap-2 z-40">
<aside className="fixed left-0 top-0 h-full w-[280px] bg-[var(--color-surface-container-low)] flex flex-col p-4 gap-2 z-40">
<div className="mb-6 px-2 flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-[#AD5F45] flex items-center justify-center">
<div className="w-8 h-8 rounded-lg bg-[var(--color-primary-container)] flex items-center justify-center">
<span
className="material-symbols-outlined text-white"
style={{ fontVariationSettings: "'FILL' 1" }}
@ -22,16 +22,16 @@ export function ScheduledTasksList() {
</span>
</div>
<div>
<h2 className="font-[Manrope,sans-serif] text-sm font-bold text-[#1B1C1A] uppercase tracking-tighter">{t('sidebar.allProjects')}</h2>
<p className="text-xs text-[#87736D] font-medium">{t('scheduledPage.activeSession')}</p>
<h2 className="font-[Manrope,sans-serif] text-sm font-bold text-[var(--color-text-primary)] uppercase tracking-tighter">{t('sidebar.allProjects')}</h2>
<p className="text-xs text-[var(--color-text-tertiary)] font-medium">{t('scheduledPage.activeSession')}</p>
</div>
</div>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">add</span>
{t('sidebar.newSession')}
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full bg-[#FAF9F5] text-[#1B1C1A] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[#8F482F] before:rounded-full font-medium text-sm duration-200 ease-in-out">
<button className="flex items-center gap-3 px-3 py-2 w-full bg-[var(--color-background)] text-[var(--color-text-primary)] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[var(--color-brand)] before:rounded-full font-medium text-sm duration-200 ease-in-out">
<span
className="material-symbols-outlined"
style={{ fontVariationSettings: "'FILL' 1" }}
@ -40,32 +40,32 @@ export function ScheduledTasksList() {
</span>
{t('sidebar.scheduled')}
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">history</span>
{t('sidebar.timeGroup.today')}
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">event_note</span>
{t('sidebar.timeGroup.last7days')}
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">archive</span>
{t('sidebar.timeGroup.older')}
</button>
<div className="mt-auto pt-4 flex flex-col gap-2">
<div className="px-2 py-4">
<button className="w-full bg-[#E9E8E4] text-[#1B1C1A] font-[Manrope,sans-serif] text-xs font-bold py-2 rounded-lg flex items-center justify-center gap-2 hover:bg-[#E3E2DF] transition-colors">
<button className="w-full bg-[var(--color-surface-container-high)] text-[var(--color-text-primary)] font-[Manrope,sans-serif] text-xs font-bold py-2 rounded-lg flex items-center justify-center gap-2 hover:bg-[var(--color-surface-container-highest)] transition-colors">
<span className="material-symbols-outlined text-[1rem]">search</span>
{t('sidebar.searchPlaceholder')}
</button>
</div>
<div className="h-[1px] bg-[#DAC1BA]/20 mx-2 mb-2"></div>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<div className="h-[1px] bg-[var(--color-border)]/20 mx-2 mb-2"></div>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">computer</span>
{t('scheduledPage.localMode')}
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[#87736D] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">cloud</span>
{t('scheduledPage.remoteMode')}
</button>
@ -75,25 +75,25 @@ export function ScheduledTasksList() {
{/* Main Content Area */}
<main className="flex-1 flex flex-col ml-[280px] min-w-0 h-screen">
{/* TopAppBar */}
<header className="bg-[#FAF9F5] h-12 w-full flex justify-between items-center px-6 z-30">
<header className="bg-[var(--color-background)] h-12 w-full flex justify-between items-center px-6 z-30">
<div className="flex items-center gap-8">
<div className="font-[Manrope,sans-serif] font-bold text-[#1B1C1A] uppercase tracking-tighter text-sm">Claude Code Companion</div>
<div className="font-[Manrope,sans-serif] font-bold text-[var(--color-text-primary)] uppercase tracking-tighter text-sm">Claude Code Companion</div>
<nav className="flex items-center gap-6 font-[Manrope,sans-serif] font-semibold tracking-wide text-sm">
<a className="text-[#87736D] hover:text-[#8F482F] transition-colors" href="#">{t('titlebar.code')}</a>
<a className="text-[#87736D] hover:text-[#8F482F] transition-colors" href="#">{t('titlebar.terminal')}</a>
<a className="text-[#1B1C1A] border-b-2 border-[#8F482F] pb-1" href="#">{t('titlebar.history')}</a>
<a className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors" href="#">{t('titlebar.code')}</a>
<a className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors" href="#">{t('titlebar.terminal')}</a>
<a className="text-[var(--color-text-primary)] border-b-2 border-[var(--color-brand)] pb-1" href="#">{t('titlebar.history')}</a>
</nav>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<button className="p-1 text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer active:opacity-70">
<button className="p-1 text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer active:opacity-70">
<span className="material-symbols-outlined text-[1rem]">arrow_back_ios</span>
</button>
<button className="p-1 text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer active:opacity-70">
<button className="p-1 text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer active:opacity-70">
<span className="material-symbols-outlined text-[1rem]">arrow_forward_ios</span>
</button>
</div>
<button className="font-[Manrope,sans-serif] font-semibold tracking-wide text-sm text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer active:opacity-70 flex items-center gap-1">
<button className="font-[Manrope,sans-serif] font-semibold tracking-wide text-sm text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer active:opacity-70 flex items-center gap-1">
<span className="material-symbols-outlined text-[1.1rem]">settings</span>
{t('sidebar.settings')}
</button>
@ -101,18 +101,18 @@ export function ScheduledTasksList() {
</header>
{/* Separation Line */}
<div className="bg-[#F4F4F0] h-[1px] w-full"></div>
<div className="bg-[var(--color-surface-container-low)] h-[1px] w-full"></div>
{/* Scrollable Content */}
<section className="flex-1 overflow-y-auto p-12 bg-[#FAF9F5]">
<section className="flex-1 overflow-y-auto p-12 bg-[var(--color-background)]">
<div className="max-w-5xl mx-auto">
{/* Page Header */}
<div className="flex justify-between items-end mb-12">
<div className="space-y-1">
<h1 className="font-[Manrope,sans-serif] text-3xl font-bold tracking-tight text-[#1B1C1A]">{t('scheduledPage.title')}</h1>
<p className="text-[#87736D] text-sm">{t('scheduledPage.subtitle')}</p>
<h1 className="font-[Manrope,sans-serif] text-3xl font-bold tracking-tight text-[var(--color-text-primary)]">{t('scheduledPage.title')}</h1>
<p className="text-[var(--color-text-tertiary)] text-sm">{t('scheduledPage.subtitle')}</p>
</div>
<button className="bg-[#8F482F] hover:bg-[#AD5F45] text-white px-5 py-2.5 rounded-lg flex items-center gap-2 transition-all shadow-sm font-medium text-sm">
<button className="bg-[var(--color-brand)] hover:bg-[var(--color-primary-container)] text-white px-5 py-2.5 rounded-lg flex items-center gap-2 transition-all shadow-sm font-medium text-sm">
<span className="material-symbols-outlined text-[1.1rem]">add_task</span>
{t('tasks.createNew')}
</button>
@ -121,70 +121,70 @@ export function ScheduledTasksList() {
{/* Bento-style Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
{/* Total Tasks */}
<div className="bg-[#F4F4F0] p-6 rounded-xl border border-[#DAC1BA]/10">
<div className="bg-[var(--color-surface-container-low)] p-6 rounded-xl border border-[var(--color-border)]/10">
<div className="flex items-center justify-between mb-4">
<span className="text-xs font-bold uppercase tracking-widest text-[#87736D]">{t('tasks.totalTasks')}</span>
<span className="material-symbols-outlined text-[#8F482F]">analytics</span>
<span className="text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)]">{t('tasks.totalTasks')}</span>
<span className="material-symbols-outlined text-[var(--color-brand)]">analytics</span>
</div>
<div className="text-4xl font-[Manrope,sans-serif] font-extrabold text-[#1B1C1A]">{stats.totalTasks}</div>
<div className="mt-2 flex items-center gap-1 text-[10px] text-[#4F6237] font-bold bg-[#677B4E]/20 px-2 py-0.5 rounded-full w-fit">
<div className="text-4xl font-[Manrope,sans-serif] font-extrabold text-[var(--color-text-primary)]">{stats.totalTasks}</div>
<div className="mt-2 flex items-center gap-1 text-[10px] text-[var(--color-success)] font-bold bg-[var(--color-success)]/20 px-2 py-0.5 rounded-full w-fit">
<span className="material-symbols-outlined text-[10px]">trending_up</span>
{t('scheduledPage.thisMonth', { count: '+2' })}
</div>
</div>
{/* Next Run */}
<div className="bg-[#F4F4F0] p-6 rounded-xl border border-[#DAC1BA]/10">
<div className="bg-[var(--color-surface-container-low)] p-6 rounded-xl border border-[var(--color-border)]/10">
<div className="flex items-center justify-between mb-4">
<span className="text-xs font-bold uppercase tracking-widest text-[#87736D]">{t('scheduledPage.nextRun')}</span>
<span className="material-symbols-outlined text-[#2D628F]">schedule</span>
<span className="text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)]">{t('scheduledPage.nextRun')}</span>
<span className="material-symbols-outlined text-[var(--color-secondary)]">schedule</span>
</div>
<div className="text-xl font-[Manrope,sans-serif] font-bold text-[#1B1C1A]">{stats.nextRun.name}</div>
<p className="text-sm font-[JetBrains_Mono,monospace] text-[#2D628F] mt-1">{stats.nextRun.time}</p>
<div className="text-xl font-[Manrope,sans-serif] font-bold text-[var(--color-text-primary)]">{stats.nextRun.name}</div>
<p className="text-sm font-[JetBrains_Mono,monospace] text-[var(--color-secondary)] mt-1">{stats.nextRun.time}</p>
</div>
{/* System Health */}
<div className="bg-[#F4F4F0] p-6 rounded-xl border border-[#DAC1BA]/10">
<div className="bg-[var(--color-surface-container-low)] p-6 rounded-xl border border-[var(--color-border)]/10">
<div className="flex items-center justify-between mb-4">
<span className="text-xs font-bold uppercase tracking-widest text-[#87736D]">{t('scheduledPage.systemHealth')}</span>
<span className="material-symbols-outlined text-[#4F6237]">check_circle</span>
<span className="text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)]">{t('scheduledPage.systemHealth')}</span>
<span className="material-symbols-outlined text-[var(--color-success)]">check_circle</span>
</div>
<div className="text-4xl font-[Manrope,sans-serif] font-extrabold text-[#1B1C1A]">{stats.systemHealth}%</div>
<p className="text-xs text-[#87736D] mt-2 font-medium">{stats.healthPeriod}</p>
<div className="text-4xl font-[Manrope,sans-serif] font-extrabold text-[var(--color-text-primary)]">{stats.systemHealth}%</div>
<p className="text-xs text-[var(--color-text-tertiary)] mt-2 font-medium">{stats.healthPeriod}</p>
</div>
</div>
{/* Operational Tasks Table */}
<div className="bg-white rounded-xl overflow-hidden border border-[#DAC1BA]/20 shadow-[0_4px_20px_rgba(27,28,26,0.04)]">
<div className="bg-[var(--color-surface-container-lowest)] rounded-xl overflow-hidden border border-[var(--color-border)]/20 shadow-[0_4px_20px_rgba(27,28,26,0.04)]">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-[#F4F4F0]/50">
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[#DAC1BA] border-b border-[#DAC1BA]/10">{t('scheduledPage.colTaskName')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[#DAC1BA] border-b border-[#DAC1BA]/10">{t('scheduledPage.colFrequency')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[#DAC1BA] border-b border-[#DAC1BA]/10">{t('scheduledPage.colLastResult')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[#DAC1BA] border-b border-[#DAC1BA]/10">{t('scheduledPage.colNextExecution')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[#DAC1BA] border-b border-[#DAC1BA]/10 text-right">{t('scheduledPage.colActions')}</th>
<tr className="bg-[var(--color-surface-container-low)]/50">
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)] border-b border-[var(--color-border)]/10">{t('scheduledPage.colTaskName')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)] border-b border-[var(--color-border)]/10">{t('scheduledPage.colFrequency')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)] border-b border-[var(--color-border)]/10">{t('scheduledPage.colLastResult')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)] border-b border-[var(--color-border)]/10">{t('scheduledPage.colNextExecution')}</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-[var(--color-text-tertiary)] border-b border-[var(--color-border)]/10 text-right">{t('scheduledPage.colActions')}</th>
</tr>
</thead>
<tbody className="divide-y divide-[#DAC1BA]/5">
<tbody className="divide-y divide-[var(--color-border)]/5">
{/* Task Row 1 - Nightly linting */}
<tr className="group hover:bg-[#F4F4F0]/30 transition-colors">
<tr className="group hover:bg-[var(--color-surface-container-low)]/30 transition-colors">
<td className="px-6 py-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-[#FFDBD0] text-[#8F482F] rounded-lg">
<div className="p-2 bg-[var(--color-primary-fixed)] text-[var(--color-brand)] rounded-lg">
<span className="material-symbols-outlined text-[1.2rem]">code_blocks</span>
</div>
<div>
<div className="font-[Manrope,sans-serif] font-bold text-[#1B1C1A] text-sm">{task0.name}</div>
<div className="text-xs text-[#87736D] font-medium">Root: /projects/companion/src</div>
<div className="font-[Manrope,sans-serif] font-bold text-[var(--color-text-primary)] text-sm">{task0.name}</div>
<div className="text-xs text-[var(--color-text-tertiary)] font-medium">Root: /projects/companion/src</div>
</div>
</div>
</td>
<td className="px-6 py-5">
<span className="px-2.5 py-1 bg-[#E9E8E4] rounded-full text-xs font-semibold text-[#54433E]">{task0.frequency}</span>
<span className="px-2.5 py-1 bg-[var(--color-surface-container-high)] rounded-full text-xs font-semibold text-[var(--color-text-secondary)]">{task0.frequency}</span>
</td>
<td className="px-6 py-5">
<div className="flex items-center gap-1.5 text-[#4F6237] text-xs font-bold">
<div className="flex items-center gap-1.5 text-[var(--color-success)] text-xs font-bold">
<span
className="material-symbols-outlined text-[1rem]"
style={{ fontVariationSettings: "'FILL' 1" }}
@ -195,17 +195,17 @@ export function ScheduledTasksList() {
</div>
</td>
<td className="px-6 py-5">
<div className="font-[JetBrains_Mono,monospace] text-sm font-medium text-[#2D628F]">{task0.nextExecution}</div>
<div className="font-[JetBrains_Mono,monospace] text-sm font-medium text-[var(--color-secondary)]">{task0.nextExecution}</div>
</td>
<td className="px-6 py-5 text-right">
<div className="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button className="p-2 text-[#87736D] hover:text-[#8F482F] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">edit</span>
</button>
<button className="p-2 text-[#87736D] hover:text-[#BA1A1A] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-error)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">delete</span>
</button>
<button className="p-2 text-[#87736D] hover:text-[#1B1C1A] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">more_vert</span>
</button>
</div>
@ -213,23 +213,23 @@ export function ScheduledTasksList() {
</tr>
{/* Task Row 2 - Clean up temp files */}
<tr className="group hover:bg-[#F4F4F0]/30 transition-colors">
<tr className="group hover:bg-[var(--color-surface-container-low)]/30 transition-colors">
<td className="px-6 py-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-[#CFE5FF] text-[#094A76] rounded-lg">
<div className="p-2 bg-[var(--color-secondary-container)] text-[var(--color-secondary)] rounded-lg">
<span className="material-symbols-outlined text-[1.2rem]">cleaning_services</span>
</div>
<div>
<div className="font-[Manrope,sans-serif] font-bold text-[#1B1C1A] text-sm">{task1.name}</div>
<div className="text-xs text-[#87736D] font-medium">{task1.description}</div>
<div className="font-[Manrope,sans-serif] font-bold text-[var(--color-text-primary)] text-sm">{task1.name}</div>
<div className="text-xs text-[var(--color-text-tertiary)] font-medium">{task1.description}</div>
</div>
</div>
</td>
<td className="px-6 py-5">
<span className="px-2.5 py-1 bg-[#E9E8E4] rounded-full text-xs font-semibold text-[#54433E]">{task1.frequency}</span>
<span className="px-2.5 py-1 bg-[var(--color-surface-container-high)] rounded-full text-xs font-semibold text-[var(--color-text-secondary)]">{task1.frequency}</span>
</td>
<td className="px-6 py-5">
<div className="flex items-center gap-1.5 text-[#4F6237] text-xs font-bold">
<div className="flex items-center gap-1.5 text-[var(--color-success)] text-xs font-bold">
<span
className="material-symbols-outlined text-[1rem]"
style={{ fontVariationSettings: "'FILL' 1" }}
@ -240,17 +240,17 @@ export function ScheduledTasksList() {
</div>
</td>
<td className="px-6 py-5">
<div className="font-[JetBrains_Mono,monospace] text-sm font-medium text-[#2D628F]">{task1.nextExecution}</div>
<div className="font-[JetBrains_Mono,monospace] text-sm font-medium text-[var(--color-secondary)]">{task1.nextExecution}</div>
</td>
<td className="px-6 py-5 text-right">
<div className="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button className="p-2 text-[#87736D] hover:text-[#8F482F] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">edit</span>
</button>
<button className="p-2 text-[#87736D] hover:text-[#BA1A1A] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-error)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">delete</span>
</button>
<button className="p-2 text-[#87736D] hover:text-[#1B1C1A] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">more_vert</span>
</button>
</div>
@ -258,23 +258,23 @@ export function ScheduledTasksList() {
</tr>
{/* Task Row 3 - Database Vacuum */}
<tr className="group hover:bg-[#F4F4F0]/30 transition-colors">
<tr className="group hover:bg-[var(--color-surface-container-low)]/30 transition-colors">
<td className="px-6 py-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-[#D4EAB4] text-[#3B4C24] rounded-lg">
<div className="p-2 bg-[var(--color-tertiary-container)] text-[var(--color-tertiary)] rounded-lg">
<span className="material-symbols-outlined text-[1.2rem]">database</span>
</div>
<div>
<div className="font-[Manrope,sans-serif] font-bold text-[#1B1C1A] text-sm">{task2.name}</div>
<div className="text-xs text-[#87736D] font-medium">{task2.description}</div>
<div className="font-[Manrope,sans-serif] font-bold text-[var(--color-text-primary)] text-sm">{task2.name}</div>
<div className="text-xs text-[var(--color-text-tertiary)] font-medium">{task2.description}</div>
</div>
</div>
</td>
<td className="px-6 py-5">
<span className="px-2.5 py-1 bg-[#E9E8E4] rounded-full text-xs font-semibold text-[#54433E]">Monthly</span>
<span className="px-2.5 py-1 bg-[var(--color-surface-container-high)] rounded-full text-xs font-semibold text-[var(--color-text-secondary)]">Monthly</span>
</td>
<td className="px-6 py-5">
<div className="flex items-center gap-1.5 text-[#BA1A1A] text-xs font-bold">
<div className="flex items-center gap-1.5 text-[var(--color-error)] text-xs font-bold">
<span
className="material-symbols-outlined text-[1rem]"
style={{ fontVariationSettings: "'FILL' 1" }}
@ -285,17 +285,17 @@ export function ScheduledTasksList() {
</div>
</td>
<td className="px-6 py-5">
<div className="font-[JetBrains_Mono,monospace] text-sm font-medium text-[#2D628F]">{task2.nextExecution}</div>
<div className="font-[JetBrains_Mono,monospace] text-sm font-medium text-[var(--color-secondary)]">{task2.nextExecution}</div>
</td>
<td className="px-6 py-5 text-right">
<div className="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button className="p-2 text-[#87736D] hover:text-[#8F482F] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">edit</span>
</button>
<button className="p-2 text-[#87736D] hover:text-[#BA1A1A] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-error)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">delete</span>
</button>
<button className="p-2 text-[#87736D] hover:text-[#1B1C1A] transition-colors">
<button className="p-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] transition-colors">
<span className="material-symbols-outlined text-[1.1rem]">more_vert</span>
</button>
</div>
@ -305,12 +305,12 @@ export function ScheduledTasksList() {
</table>
{/* End of list placeholder */}
<div className="p-12 text-center border-t border-[#DAC1BA]/10">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-[#F4F4F0] mb-4">
<span className="material-symbols-outlined text-[#87736D]">history_toggle_off</span>
<div className="p-12 text-center border-t border-[var(--color-border)]/10">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-[var(--color-surface-container-low)] mb-4">
<span className="material-symbols-outlined text-[var(--color-text-tertiary)]">history_toggle_off</span>
</div>
<h3 className="font-[Manrope,sans-serif] font-bold text-[#1B1C1A] text-base">{t('scheduledPage.endOfList')}</h3>
<p className="text-sm text-[#87736D] max-w-xs mx-auto mt-1">{t('scheduledPage.pausedTasks')}</p>
<h3 className="font-[Manrope,sans-serif] font-bold text-[var(--color-text-primary)] text-base">{t('scheduledPage.endOfList')}</h3>
<p className="text-sm text-[var(--color-text-tertiary)] max-w-xs mx-auto mt-1">{t('scheduledPage.pausedTasks')}</p>
</div>
</div>
@ -318,61 +318,61 @@ export function ScheduledTasksList() {
<div className="mt-12 flex flex-col md:flex-row gap-8 items-start">
{/* Recent Output Logs */}
<div className="flex-1 space-y-6">
<h2 className="font-[Manrope,sans-serif] text-lg font-bold text-[#1B1C1A]">{t('scheduledPage.recentLogs')}</h2>
<div className="bg-[#DBDAD6] rounded-xl p-6 font-[JetBrains_Mono,monospace] text-[13px] leading-relaxed text-[#54433E] overflow-x-auto shadow-inner">
<h2 className="font-[Manrope,sans-serif] text-lg font-bold text-[var(--color-text-primary)]">{t('scheduledPage.recentLogs')}</h2>
<div className="bg-[var(--color-surface-container-high)] rounded-xl p-6 font-[JetBrains_Mono,monospace] text-[13px] leading-relaxed text-[var(--color-text-secondary)] overflow-x-auto shadow-inner">
<div className="flex gap-4 opacity-50 mb-1">
<span className="w-32 shrink-0">2023-11-10 23:01</span>
<span className="text-[#4F6237]">[INFO]</span>
<span className="text-[var(--color-success)]">[INFO]</span>
<span>Nightly linting started for repository: companion-main</span>
</div>
<div className="flex gap-4 mb-1">
<span className="w-32 shrink-0">2023-11-10 23:04</span>
<span className="text-[#4F6237]">[INFO]</span>
<span className="text-[var(--color-success)]">[INFO]</span>
<span>Processed 1,422 files. No critical issues found.</span>
</div>
<div className="flex gap-4 mb-1">
<span className="w-32 shrink-0">2023-11-10 23:04</span>
<span className="text-[#094A76]">[WARN]</span>
<span className="text-[var(--color-secondary)]">[WARN]</span>
<span className="italic">Found 12 deprecated calls in /legacy/utils.js</span>
</div>
<div className="flex gap-4 mb-1">
<span className="w-32 shrink-0">2023-11-10 23:05</span>
<span className="text-[#4F6237]">[INFO]</span>
<span className="text-[var(--color-success)]">[INFO]</span>
<span>Task completed successfully in 242.4s.</span>
</div>
<div className="mt-4 pt-4 border-t border-[#DAC1BA]/20 flex items-center justify-between">
<div className="mt-4 pt-4 border-t border-[var(--color-border)]/20 flex items-center justify-between">
<span className="text-[11px] uppercase tracking-tighter opacity-50">Log stream: active</span>
<button className="text-[#8F482F] font-bold text-xs hover:underline">{t('scheduledPage.viewArtifacts')}</button>
<button className="text-[var(--color-brand)] font-bold text-xs hover:underline">{t('scheduledPage.viewArtifacts')}</button>
</div>
</div>
</div>
{/* Resource Allocation Panel */}
<div className="w-full md:w-80 shrink-0">
<div className="bg-[#AD5F45]/10 p-6 rounded-xl border border-[#8F482F]/10">
<h3 className="font-[Manrope,sans-serif] font-bold text-[#8F482F] text-sm mb-3">{t('scheduledPage.resourceAllocation')}</h3>
<div className="bg-[var(--color-primary-container)]/10 p-6 rounded-xl border border-[var(--color-brand)]/10">
<h3 className="font-[Manrope,sans-serif] font-bold text-[var(--color-brand)] text-sm mb-3">{t('scheduledPage.resourceAllocation')}</h3>
<div className="space-y-4">
<div className="space-y-1">
<div className="flex justify-between text-[11px] font-bold text-[#87736D] uppercase tracking-wider">
<div className="flex justify-between text-[11px] font-bold text-[var(--color-text-tertiary)] uppercase tracking-wider">
<span>{t('scheduledPage.cpuCapacity')}</span>
<span>42%</span>
</div>
<div className="w-full h-1 bg-[#DAC1BA]/30 rounded-full overflow-hidden">
<div className="h-full bg-[#8F482F]" style={{ width: '42%' }}></div>
<div className="w-full h-1 bg-[var(--color-border)]/30 rounded-full overflow-hidden">
<div className="h-full bg-[var(--color-brand)]" style={{ width: '42%' }}></div>
</div>
</div>
<div className="space-y-1">
<div className="flex justify-between text-[11px] font-bold text-[#87736D] uppercase tracking-wider">
<div className="flex justify-between text-[11px] font-bold text-[var(--color-text-tertiary)] uppercase tracking-wider">
<span>{t('scheduledPage.memoryLoad')}</span>
<span>68%</span>
</div>
<div className="w-full h-1 bg-[#DAC1BA]/30 rounded-full overflow-hidden">
<div className="h-full bg-[#2D628F]" style={{ width: '68%' }}></div>
<div className="w-full h-1 bg-[var(--color-border)]/30 rounded-full overflow-hidden">
<div className="h-full bg-[var(--color-secondary)]" style={{ width: '68%' }}></div>
</div>
</div>
</div>
<div className="mt-6">
<div className="w-full h-24 rounded-lg bg-gradient-to-br from-[#FFDBD0] via-[#FFB59D]/40 to-[#DAC1BA]/20"></div>
<div className="w-full h-24 rounded-lg bg-gradient-to-br from-[var(--color-surface-container-lowest)] via-[var(--color-surface-container-low)] to-[var(--color-border)]/20"></div>
</div>
</div>
</div>
@ -381,30 +381,30 @@ export function ScheduledTasksList() {
</section>
{/* Footer */}
<footer className="bg-[#FAF9F5] border-t border-[#87736D]/20 fixed bottom-0 left-0 w-full h-8 flex items-center justify-between px-4 z-50">
<footer className="bg-[var(--color-background)] border-t border-[var(--color-border)]/20 fixed bottom-0 left-0 w-full h-8 flex items-center justify-between px-4 z-50">
<div className="flex items-center gap-4">
<span className="font-[Inter,sans-serif] text-xs tracking-tight text-[#87736D]">{mockStatusBar.user} &bull; {mockStatusBar.username} &bull; {mockStatusBar.plan}</span>
<div className="h-3 w-[1px] bg-[#87736D]/30"></div>
<span className="font-[Inter,sans-serif] text-xs tracking-tight text-[var(--color-text-tertiary)]">{mockStatusBar.user} &bull; {mockStatusBar.username} &bull; {mockStatusBar.plan}</span>
<div className="h-3 w-[1px] bg-[var(--color-text-tertiary)]/30"></div>
<div className="flex items-center gap-2">
<span
className="material-symbols-outlined text-[10px] text-[#4F6237]"
className="material-symbols-outlined text-[10px] text-[var(--color-success)]"
style={{ fontVariationSettings: "'FILL' 1" }}
>
fiber_manual_record
</span>
<span className="font-[Inter,sans-serif] text-xs tracking-tight text-[#1B1C1A]">{t('scheduledPage.connectedLocal')}</span>
<span className="font-[Inter,sans-serif] text-xs tracking-tight text-[var(--color-text-primary)]">{t('scheduledPage.connectedLocal')}</span>
</div>
</div>
<div className="flex items-center gap-6">
<button className="font-[Inter,sans-serif] text-xs tracking-tight text-[#87736D] hover:bg-[#F4F4F0] px-2 py-0.5 rounded transition-colors flex items-center gap-1">
<button className="font-[Inter,sans-serif] text-xs tracking-tight text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded transition-colors flex items-center gap-1">
<span className="material-symbols-outlined text-[12px]">account_tree</span>
{mockStatusBar.branch}
</button>
<button className="font-[Inter,sans-serif] text-xs tracking-tight text-[#87736D] hover:bg-[#F4F4F0] px-2 py-0.5 rounded transition-colors flex items-center gap-1">
<button className="font-[Inter,sans-serif] text-xs tracking-tight text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded transition-colors flex items-center gap-1">
<span className="material-symbols-outlined text-[12px]">layers</span>
{mockStatusBar.worktreeToggle}
</button>
<button className="font-[Inter,sans-serif] text-xs tracking-tight text-[#8F482F] font-bold hover:bg-[#F4F4F0] px-2 py-0.5 rounded transition-colors flex items-center gap-1">
<button className="font-[Inter,sans-serif] text-xs tracking-tight text-[var(--color-brand)] font-bold hover:bg-[var(--color-surface-container-low)] px-2 py-0.5 rounded transition-colors flex items-center gap-1">
<span className="material-symbols-outlined text-[12px]">toggle_on</span>
{mockStatusBar.localSwitch}
</button>

View File

@ -39,41 +39,41 @@ export default function SessionControls() {
return (
<div className="h-screen w-screen bg-background text-on-surface font-body selection:bg-primary-fixed overflow-hidden relative">
{/* ─── TopAppBar ─────────────────────────────────────────── */}
<header className="bg-[#FAF9F5] font-headline font-semibold tracking-wide text-sm fixed top-0 left-0 right-0 flex justify-between items-center px-6 h-12 z-40">
<header className="bg-[var(--color-background)] font-headline font-semibold tracking-wide text-sm fixed top-0 left-0 right-0 flex justify-between items-center px-6 h-12 z-40">
<div className="flex items-center gap-6">
<span className="text-sm font-bold text-[#1B1C1A] uppercase tracking-tighter">
<span className="text-sm font-bold text-[var(--color-text-primary)] uppercase tracking-tighter">
Claude Code Companion
</span>
<nav className="hidden md:flex gap-4">
<a className="text-[#1B1C1A] border-b-2 border-[#8F482F] pb-1 cursor-pointer active:opacity-70">
<a className="text-[var(--color-text-primary)] border-b-2 border-[var(--color-brand)] pb-1 cursor-pointer active:opacity-70">
Code
</a>
<a className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer active:opacity-70">
<a className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer active:opacity-70">
Terminal
</a>
<a className="text-[#87736D] hover:text-[#8F482F] transition-colors cursor-pointer active:opacity-70">
<a className="text-[var(--color-text-tertiary)] hover:text-[var(--color-brand)] transition-colors cursor-pointer active:opacity-70">
History
</a>
</nav>
</div>
<div className="flex items-center gap-3">
<span className="material-symbols-outlined text-[#87736D] cursor-pointer">
<span className="material-symbols-outlined text-[var(--color-text-tertiary)] cursor-pointer">
arrow_back_ios
</span>
<span className="material-symbols-outlined text-[#87736D] cursor-pointer">
<span className="material-symbols-outlined text-[var(--color-text-tertiary)] cursor-pointer">
arrow_forward_ios
</span>
<button className="ml-2 px-3 py-1 bg-surface-container-high rounded text-[#8F482F] hover:bg-surface-container-highest transition-colors">
<button className="ml-2 px-3 py-1 bg-surface-container-high rounded text-[var(--color-brand)] hover:bg-surface-container-highest transition-colors">
Settings
</button>
</div>
</header>
{/* Separator line */}
<div className="bg-[#F4F4F0] h-[1px] w-full fixed top-12 z-40" />
<div className="bg-[var(--color-surface-container-low)] h-[1px] w-full fixed top-12 z-40" />
{/* ─── SideNavBar ────────────────────────────────────────── */}
<aside className="bg-[#F4F4F0] font-body text-sm font-medium fixed left-0 top-0 h-full w-[280px] hidden md:flex flex-col p-4 gap-2 pt-16 z-30">
<aside className="bg-[var(--color-surface-container-low)] font-body text-sm font-medium fixed left-0 top-0 h-full w-[280px] hidden md:flex flex-col p-4 gap-2 pt-16 z-30">
{/* Project header */}
<div className="px-2 mb-4">
<div className="flex items-center gap-3 mb-1">
@ -92,23 +92,23 @@ export default function SessionControls() {
</div>
{/* Nav items */}
<button className="w-full text-left p-2.5 bg-[#FAF9F5] text-[#1B1C1A] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[#8F482F] before:rounded-full before:top-1/2 before:-translate-y-1/2 transition-all duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined text-[#8F482F]">add</span>
<button className="w-full text-left p-2.5 bg-[var(--color-background)] text-[var(--color-text-primary)] rounded-lg relative before:content-[''] before:absolute before:left-[-8px] before:w-1 before:h-4 before:bg-[var(--color-brand)] before:rounded-full before:top-1/2 before:-translate-y-1/2 transition-all duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined text-[var(--color-brand)]">add</span>
<span>New session</span>
</button>
<button className="w-full text-left p-2.5 text-[#87736D] hover:bg-[#EBEBE6] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3">
<button className="w-full text-left p-2.5 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">calendar_today</span>
<span>Scheduled</span>
</button>
<button className="w-full text-left p-2.5 text-[#87736D] hover:bg-[#EBEBE6] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3" data-count={mockSessions.today.length}>
<button className="w-full text-left p-2.5 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3" data-count={mockSessions.today.length}>
<span className="material-symbols-outlined">history</span>
<span>Today</span>
</button>
<button className="w-full text-left p-2.5 text-[#87736D] hover:bg-[#EBEBE6] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3" data-count={mockSessions.previous7Days.length}>
<button className="w-full text-left p-2.5 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3" data-count={mockSessions.previous7Days.length}>
<span className="material-symbols-outlined">event_note</span>
<span>Previous 7 Days</span>
</button>
<button className="w-full text-left p-2.5 text-[#87736D] hover:bg-[#EBEBE6] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3" data-count={mockSessions.older.length}>
<button className="w-full text-left p-2.5 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-hover)] rounded-lg transition-all duration-200 ease-in-out flex items-center gap-3" data-count={mockSessions.older.length}>
<span className="material-symbols-outlined">archive</span>
<span>Older</span>
</button>
@ -215,7 +215,7 @@ export default function SessionControls() {
{/* ── Permissions Dropdown ─────────────────────────────── */}
{showPermissions && (
<div
className="w-80 rounded-xl border border-[#E8E6DC] overflow-hidden flex flex-col"
className="w-80 rounded-xl border border-[var(--color-border)] overflow-hidden flex flex-col"
style={{
background: 'rgba(255, 255, 255, 0.85)',
backdropFilter: 'blur(20px)',
@ -223,7 +223,7 @@ export default function SessionControls() {
'0 4px 20px rgba(27, 28, 26, 0.04), 0 12px 40px rgba(27, 28, 26, 0.08)',
}}
>
<div className="px-4 py-3 bg-surface-container-low border-b border-[#E8E6DC]">
<div className="px-4 py-3 bg-surface-container-low border-b border-[var(--color-border)]">
<span className="text-[10px] font-bold uppercase tracking-widest text-outline">
Execution Permissions
</span>
@ -280,7 +280,7 @@ export default function SessionControls() {
{/* ── Model & Effort Dropdown ─────────────────────────── */}
{showModelConfig && (
<div
className="w-64 rounded-xl border border-[#E8E6DC] overflow-hidden flex flex-col"
className="w-64 rounded-xl border border-[var(--color-border)] overflow-hidden flex flex-col"
style={{
background: 'rgba(255, 255, 255, 0.85)',
backdropFilter: 'blur(20px)',
@ -288,7 +288,7 @@ export default function SessionControls() {
'0 4px 20px rgba(27, 28, 26, 0.04), 0 12px 40px rgba(27, 28, 26, 0.08)',
}}
>
<div className="px-4 py-3 bg-surface-container-low border-b border-[#E8E6DC]">
<div className="px-4 py-3 bg-surface-container-low border-b border-[var(--color-border)]">
<span className="text-[10px] font-bold uppercase tracking-widest text-outline">
Model Configuration
</span>
@ -335,7 +335,7 @@ export default function SessionControls() {
</div>
{/* Divider */}
<div className="mx-4 h-[1px] bg-[#E8E6DC]" />
<div className="mx-4 h-[1px] bg-[var(--color-border)]" />
{/* Effort levels */}
<div className="p-2">
@ -391,7 +391,7 @@ export default function SessionControls() {
{/* Permission pill */}
<button
onClick={() => setShowPermissions((v) => !v)}
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-surface-container-low border border-[#E8E6DC] text-xs font-medium hover:bg-surface-container-high transition-all"
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-surface-container-low border border-[var(--color-border)] text-xs font-medium hover:bg-surface-container-high transition-all"
>
<span className="material-symbols-outlined text-base">
{permissionIcons[selectedPermission]?.icon || 'verified_user'}
@ -403,7 +403,7 @@ export default function SessionControls() {
{/* Model pill */}
<button
onClick={() => setShowModelConfig((v) => !v)}
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-surface-container-low border border-[#E8E6DC] text-xs font-medium hover:bg-surface-container-high transition-all"
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-surface-container-low border border-[var(--color-border)] text-xs font-medium hover:bg-surface-container-high transition-all"
>
<span className="material-symbols-outlined text-base">
{modelIcons[selectedModel] || 'smart_toy'}
@ -427,7 +427,7 @@ export default function SessionControls() {
</div>
{/* ─── Footer / Status Bar ───────────────────────────────── */}
<footer className="bg-[#FAF9F5] font-body text-xs tracking-tight fixed bottom-0 left-0 w-full h-8 border-t border-[#87736D]/20 flex items-center justify-between px-4 z-[60]">
<footer className="bg-[var(--color-background)] font-body text-xs tracking-tight fixed bottom-0 left-0 w-full h-8 border-t border-[var(--color-border)]/20 flex items-center justify-between px-4 z-[60]">
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5">
<div className="w-4 h-4 rounded-full bg-primary-fixed flex items-center justify-center">
@ -444,13 +444,13 @@ export default function SessionControls() {
</div>
</div>
<div className="flex items-center gap-4">
<button className="text-primary font-bold hover:bg-[#F4F4F0] transition-colors px-2 py-0.5 rounded">
<button className="text-primary font-bold hover:bg-[var(--color-surface-container-low)] transition-colors px-2 py-0.5 rounded">
{mockStatusBar.branch}
</button>
<button className="text-[#87736D] hover:bg-[#F4F4F0] transition-colors px-2 py-0.5 rounded">
<button className="text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] transition-colors px-2 py-0.5 rounded">
{mockStatusBar.worktreeToggle}
</button>
<button className="text-[#87736D] hover:bg-[#F4F4F0] transition-colors px-2 py-0.5 rounded">
<button className="text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] transition-colors px-2 py-0.5 rounded">
{mockStatusBar.localSwitch}
</button>
</div>

View File

@ -1445,6 +1445,7 @@ function GeneralSettings() {
const THEMES: Array<{ value: ThemeMode; label: string }> = [
{ value: 'light', label: t('settings.general.appearance.light') },
{ value: 'dark', label: t('settings.general.appearance.dark') },
{ value: 'white', label: t('settings.general.appearance.white') },
]
const WEB_SEARCH_MODES: Array<{ value: WebSearchMode; label: string }> = [
@ -1517,6 +1518,7 @@ function GeneralSettings() {
<button
key={value}
onClick={() => void setTheme(value)}
aria-pressed={theme === value}
className={`flex-1 py-2 text-xs font-semibold rounded-lg border transition-all ${
theme === value
? 'bg-[image:var(--gradient-btn-primary)] text-[var(--color-btn-primary-fg)] border-transparent shadow-[var(--shadow-button-primary)]'

View File

@ -179,6 +179,63 @@ describe('settingsStore desktop notification persistence', () => {
})
})
describe('settingsStore theme persistence', () => {
beforeEach(() => {
vi.resetModules()
vi.clearAllMocks()
window.localStorage.clear()
document.documentElement.removeAttribute('data-theme')
document.documentElement.style.colorScheme = ''
})
it('hydrates the pure white theme from user settings', async () => {
vi.doMock('../api/settings', () => ({
settingsApi: {
getUser: vi.fn().mockResolvedValue({ theme: 'white' }),
updateUser: vi.fn(),
getPermissionMode: vi.fn().mockResolvedValue({ mode: 'default' }),
setPermissionMode: vi.fn(),
getCliLauncherStatus: vi.fn(),
},
}))
vi.doMock('../api/models', () => ({
modelsApi: {
list: vi.fn().mockResolvedValue({ models: [] }),
getCurrent: vi.fn().mockResolvedValue({ model: null }),
setCurrent: vi.fn(),
getEffort: vi.fn().mockResolvedValue({ level: 'medium' }),
setEffort: vi.fn(),
},
}))
vi.doMock('../api/h5Access', () => ({
h5AccessApi: {
get: vi.fn().mockResolvedValue({
settings: {
enabled: false,
tokenPreview: null,
allowedOrigins: [],
publicBaseUrl: null,
},
}),
enable: vi.fn(),
disable: vi.fn(),
regenerate: vi.fn(),
update: vi.fn(),
},
}))
const { useSettingsStore } = await import('./settingsStore')
const { useUIStore } = await import('./uiStore')
await useSettingsStore.getState().fetchAll()
expect(useSettingsStore.getState().theme).toBe('white')
expect(useUIStore.getState().theme).toBe('white')
expect(document.documentElement.getAttribute('data-theme')).toBe('white')
expect(document.documentElement.style.colorScheme).toBe('light')
})
})
describe('settingsStore H5 access behavior', () => {
beforeEach(() => {
vi.resetModules()

View File

@ -3,7 +3,7 @@ import { ApiError } from '../api/client'
import { settingsApi } from '../api/settings'
import { modelsApi } from '../api/models'
import { h5AccessApi } from '../api/h5Access'
import type { H5AccessSettings, PermissionMode, EffortLevel, ModelInfo, ThemeMode, WebSearchSettings } from '../types/settings'
import { isThemeMode, type H5AccessSettings, type PermissionMode, type EffortLevel, type ModelInfo, type ThemeMode, type WebSearchSettings } from '../types/settings'
import type { Locale } from '../i18n'
import { useUIStore } from './uiStore'
@ -94,7 +94,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
settingsApi.getUser(),
loadH5AccessSettings(previousH5Access),
])
const theme = userSettings.theme === 'dark' ? 'dark' : 'light'
const theme = isThemeMode(userSettings.theme) ? userSettings.theme : 'light'
useUIStore.getState().setTheme(theme)
set({
permissionMode: mode,

View File

@ -0,0 +1,37 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
describe('uiStore theme handling', () => {
beforeEach(() => {
vi.resetModules()
window.localStorage.clear()
document.documentElement.removeAttribute('data-theme')
document.documentElement.style.colorScheme = ''
})
it('hydrates and applies the pure white theme as a light color scheme', async () => {
window.localStorage.setItem('cc-haha-theme', 'white')
const { initializeTheme, useUIStore } = await import('./uiStore')
expect(useUIStore.getState().theme).toBe('white')
initializeTheme()
expect(document.documentElement.getAttribute('data-theme')).toBe('white')
expect(document.documentElement.style.colorScheme).toBe('light')
})
it('cycles through light, dark, and pure white themes', async () => {
const { useUIStore } = await import('./uiStore')
useUIStore.getState().toggleTheme()
expect(useUIStore.getState().theme).toBe('dark')
expect(document.documentElement.style.colorScheme).toBe('dark')
useUIStore.getState().toggleTheme()
expect(useUIStore.getState().theme).toBe('white')
expect(document.documentElement.style.colorScheme).toBe('light')
useUIStore.getState().toggleTheme()
expect(useUIStore.getState().theme).toBe('light')
expect(document.documentElement.style.colorScheme).toBe('light')
})
})

View File

@ -1,12 +1,12 @@
import { create } from 'zustand'
import type { ThemeMode } from '../types/settings'
import { isThemeMode, THEME_MODES, type ThemeMode } from '../types/settings'
const THEME_STORAGE_KEY = 'cc-haha-theme'
function getStoredTheme(): ThemeMode {
try {
const stored = localStorage.getItem(THEME_STORAGE_KEY)
if (stored === 'light' || stored === 'dark') return stored
if (isThemeMode(stored)) return stored
} catch { /* localStorage unavailable */ }
return 'light'
}
@ -14,7 +14,7 @@ function getStoredTheme(): ThemeMode {
export function applyTheme(theme: ThemeMode) {
if (typeof document === 'undefined') return
document.documentElement.setAttribute('data-theme', theme)
document.documentElement.style.colorScheme = theme
document.documentElement.style.colorScheme = theme === 'dark' ? 'dark' : 'light'
}
export function initializeTheme() {
@ -84,7 +84,8 @@ export const useUIStore = create<UIStore>((set) => ({
toggleTheme: () => {
set((state) => {
const next = state.theme === 'light' ? 'dark' : 'light'
const currentIndex = THEME_MODES.indexOf(state.theme)
const next = THEME_MODES[(currentIndex + 1) % THEME_MODES.length] ?? 'light'
applyTheme(next)
try { localStorage.setItem(THEME_STORAGE_KEY, next) } catch { /* noop */ }
return { theme: next }

View File

@ -208,7 +208,7 @@
/* Border aliases */
--color-border: var(--color-outline-variant);
--color-border-focus: var(--color-primary);
--color-border-focus: #8B98A7;
--color-border-separator: var(--color-outline-variant);
/* Text aliases */
@ -295,6 +295,154 @@
--radius-full: 9999px;
}
[data-theme="white"] {
color-scheme: light;
--color-primary: #8F482F;
--color-primary-container: #AD5F45;
--color-primary-fixed: #FFDBD0;
--color-primary-fixed-dim: #FFB59D;
--color-on-primary: #FFFFFF;
--color-on-surface: #111827;
--color-on-surface-variant: #4B5563;
--color-surface: #FFFFFF;
--color-surface-bright: #FFFFFF;
--color-surface-dim: #EFF3F6;
--color-surface-container: #F6F8FA;
--color-surface-container-low: #F9FAFB;
--color-surface-container-high: #EEF2F6;
--color-surface-container-highest: #E6EBF0;
--color-surface-container-lowest: #FFFFFF;
--color-surface-variant: #EDF1F5;
--color-background: #FFFFFF;
--color-outline: #8B98A7;
--color-outline-variant: #DDE3EA;
--color-secondary: #2D628F;
--color-secondary-container: #D9ECFF;
--color-tertiary: #4F6237;
--color-tertiary-container: #E2F1D1;
--color-error: #B42318;
--color-error-container: #FEE4E2;
--color-on-error-container: #7A271A;
--color-inverse-surface: #111827;
--color-inverse-on-surface: #F9FAFB;
--color-inverse-primary: #FFB59D;
--color-success: #087E4F;
--color-warning: #B54708;
--color-surface-sidebar: #F1F4F7;
--color-surface-hover: #F2F5F8;
--color-surface-selected: #E9EEF3;
--color-model-option-selected-bg: #FFF0EA;
--color-model-option-selected-border: rgba(143, 72, 47, 0.2);
--color-inspector-surface: #FFFFFF;
--color-inspector-panel: #F7F9FB;
--color-inspector-chip: #EEF3F6;
--color-inspector-border: #DDE3EA;
--color-inspector-accent: #8F482F;
--color-inspector-accent-hover: #6F3420;
--color-inspector-accent-secondary: #0F5C8F;
--color-inspector-text: #111827;
--color-inspector-heading: #0F172A;
--color-inspector-muted: #64748B;
--color-inspector-muted-strong: #475569;
--color-inspector-success: #067647;
--color-inspector-success-bg: #DCFAE6;
--color-inspector-danger: #B42318;
--color-inspector-danger-bg: #FEE4E2;
--color-inspector-danger-surface: #FFFFFF;
--color-inspector-danger-border: #DDE3EA;
--color-inspector-capacity: #94A3B8;
--shadow-inspector: 0 28px 80px rgba(15, 23, 42, 0.14);
--color-switch-checked-bg: #90C1F7;
--color-switch-thumb: #FFFFFF;
--color-sidebar-filter-bg: #FFFFFF;
--color-sidebar-filter-border: rgba(203, 213, 225, 0.92);
--color-sidebar-filter-icon-bg: transparent;
--color-sidebar-search-bg: rgba(255, 255, 255, 0.9);
--color-sidebar-search-border: rgba(203, 213, 225, 0.88);
--color-sidebar-item-hover: rgba(255, 255, 255, 0.78);
--color-sidebar-item-active: rgba(255, 255, 255, 0.94);
--color-sidebar-item-active-border: rgba(143, 72, 47, 0.16);
--sidebar-panel-bg-image: none;
--color-border: #DDE3EA;
--color-border-focus: var(--color-primary);
--color-border-separator: #E5EAF0;
--color-text-primary: var(--color-on-surface);
--color-text-secondary: var(--color-on-surface-variant);
--color-text-tertiary: #7B8794;
--color-surface-info: #F6F8FA;
--color-brand: var(--color-primary);
--color-text-accent: var(--color-secondary);
--shadow-dropdown: 0 8px 24px rgba(15, 23, 42, 0.06), 0 24px 60px rgba(15, 23, 42, 0.08);
--shadow-sidebar-filter: 0 1px 2px rgba(15, 23, 42, 0.04);
--shadow-focus-ring: 0 0 0 1px rgba(139, 152, 167, 0.42), 0 0 0 4px rgba(221, 227, 234, 0.56);
--shadow-error-ring: 0 0 0 1px rgba(180, 35, 24, 0.22), 0 0 0 4px rgba(254, 228, 226, 0.86);
--shadow-button-primary: 0 10px 24px rgba(143, 72, 47, 0.18);
--color-btn-primary-fg: var(--color-on-primary);
--gradient-btn-primary: linear-gradient(135deg, var(--color-primary), var(--color-primary-container));
--gradient-btn-primary-hover: linear-gradient(135deg, var(--color-primary-container), var(--color-primary));
--color-surface-user-msg: #F1F4F7;
--color-overlay-scrim: rgba(15, 23, 42, 0.42);
--color-surface-glass: rgba(255, 255, 255, 0.94);
--color-surface-glass-border: rgba(203, 213, 225, 0.72);
--color-code-bg: #FAFBFC;
--color-code-fg: #1F2937;
--color-code-comment: #6B7280;
--color-code-string: #16794C;
--color-code-keyword: #B42318;
--color-code-function: #1D4ED8;
--color-code-number: #0F766E;
--color-code-property: #92400E;
--color-code-type: #6D28D9;
--color-code-parameter: #334155;
--color-code-punctuation: #64748B;
--color-code-inserted: #067647;
--color-code-deleted: #B42318;
--color-diff-added-bg: #ECFDF3;
--color-diff-added-word: #ABEFC6;
--color-diff-added-gutter: #D1FADF;
--color-diff-added-text: #067647;
--color-diff-removed-bg: #FEF3F2;
--color-diff-removed-word: #FECDCA;
--color-diff-removed-gutter: #FEE4E2;
--color-diff-removed-text: #B42318;
--color-diff-highlight-bg: #FFFAEB;
--color-diff-highlight-gutter: #FEF0C7;
--color-diff-title-bg: #F6F8FA;
--color-diff-title-color: #667085;
--color-diff-title-border: #DDE3EA;
--color-terminal-header: #1F2937;
--color-terminal-bg: #111827;
--color-terminal-border: #0F172A;
--color-terminal-fg: #E5E7EB;
--color-terminal-muted: #9CA3AF;
--color-terminal-accent: #32D583;
--color-terminal-danger: #F97066;
--color-terminal-warning: #FDB022;
--color-window-close-hover: #E5484D;
--color-selection-bg: rgba(255, 219, 208, 0.9);
--color-selection-fg: #390C00;
}
[data-theme="dark"] {
color-scheme: dark;

View File

@ -3,7 +3,13 @@
export type PermissionMode = 'default' | 'acceptEdits' | 'plan' | 'bypassPermissions' | 'dontAsk'
export type EffortLevel = 'low' | 'medium' | 'high' | 'max'
export type ThemeMode = 'light' | 'dark'
export const THEME_MODES = ['light', 'dark', 'white'] as const
export type ThemeMode = (typeof THEME_MODES)[number]
export function isThemeMode(value: unknown): value is ThemeMode {
return typeof value === 'string' && (THEME_MODES as readonly string[]).includes(value)
}
export type WebSearchMode = 'auto' | 'anthropic' | 'tavily' | 'brave' | 'disabled'
export type WebSearchSettings = {

View File

@ -204,6 +204,14 @@ describe('SettingsService', () => {
expect(settings.model).toBe('claude-opus-4-7')
})
it('should write and read the pure white theme setting', async () => {
const svc = new SettingsService()
await svc.updateUserSettings({ theme: 'white' })
const settings = await svc.getUserSettings()
expect(settings.theme).toBe('white')
})
it('should merge settings on update (shallow merge)', async () => {
const svc = new SettingsService()
await svc.updateUserSettings({ theme: 'dark' })