feat: dashboard theme system with live switching

Add a theme engine for the web dashboard that mirrors the CLI skin
engine philosophy — pure data, no code changes needed for new themes.

Frontend:
- ThemeProvider context that loads active theme from backend on mount
  and applies CSS variable overrides to document.documentElement
- ThemeSwitcher dropdown component in the header (next to language
  switcher) with instant preview on click
- 6 built-in themes: Hermes Teal (default), Midnight, Ember, Mono,
  Cyberpunk, Rosé — each defines all 21 color tokens + overlay settings
- Theme types, presets, and context in web/src/themes/

Backend:
- GET /api/dashboard/themes — returns available themes + active name
- PUT /api/dashboard/theme — persists selection to config.yaml
- User custom themes discoverable from ~/.hermes/dashboard-themes/*.yaml
- Theme list endpoint added to public API paths (no auth needed)

Config:
- dashboard.theme key in DEFAULT_CONFIG (default: 'default')
- Schema override for select dropdown in config page
- Category merged into 'display' tab in config UI

i18n: theme switcher strings added for en + zh.
This commit is contained in:
Teknium 2026-04-15 20:11:51 -07:00 committed by Teknium
parent 9a9b8cd1e4
commit 3f6c4346ac
13 changed files with 681 additions and 1 deletions

View file

@ -182,6 +182,16 @@ export const api = {
},
);
},
// Dashboard themes
getThemes: () =>
fetchJSON<ThemeListResponse>("/api/dashboard/themes"),
setTheme: (name: string) =>
fetchJSON<{ ok: boolean; theme: string }>("/api/dashboard/theme", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
}),
};
export interface PlatformStatus {
@ -415,3 +425,10 @@ export interface OAuthPollResponse {
error_message?: string | null;
expires_at?: number | null;
}
// ── Dashboard theme types ──────────────────────────────────────────────
export interface ThemeListResponse {
themes: Array<{ name: string; label: string; description: string }>;
active: string;
}