From aabc95768cad107392c11fa453ba243e7c94d5b1 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Thu, 5 Nov 2026 12:15:21 +0700 Subject: [PATCH] feat(appearance): Sunset Cloud, Terminal, Night Sea and Sakura colour themes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four named themes from ai-chat-themes (MIT; colours only — no wallpapers, fonts or custom CSS): three dark ones beside Darcula / Apple Dark / Orca Dark and Sakura beside the light ones. Each theme's background, text and accent colour become the full token ramp, held to the existing contrast bar (text and fg-muted >= 4.5:1 on every surface, accent on its /15 chip, white on accent-solid, status labels on their /20 chip). Old Paper was left out. The terminal took its ANSI blue from --accent, which would print blue as green (Terminal) or coral next to red (Sunset Cloud); a theme can now set --ansi-blue, which lib/terminalTheme.ts prefers. --- CLAUDE.md | 2 +- .../settings/AppearanceSettings.tsx | 4 + .../__tests__/AppearanceSettings.test.tsx | 4 + client/src/hooks/__tests__/useTheme.test.ts | 13 +++ client/src/hooks/useTheme.ts | 4 +- client/src/index.css | 100 ++++++++++++++++++ .../src/lib/__tests__/terminalTheme.test.ts | 5 + client/src/lib/terminalTheme.ts | 6 +- docs/OVERVIEW.md | 5 +- 9 files changed, 136 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0c4258ea..8b230af0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Claude Code Working Guide -_Last synced: v2.8.74 (2026-11-05) — a page published from its session's scratchpad keeps a viewable copy._ A change that makes any statement in this file, `AGENTS.md`, or a README wrong — a moved path, a new or changed extension point, a retired rule — updates that file in the same change-set and bumps this marker. **Bump = replace the version/date and a ≤ 1-line summary; never append a changelog here** — details belong in the commit message and `docs/`, because this whole file is loaded into every session. +_Last synced: v2.8.75 (2026-11-05) — four colour themes (Sunset Cloud, Terminal, Night Sea, Sakura); `--ansi-blue` for a non-blue accent._ A change that makes any statement in this file, `AGENTS.md`, or a README wrong — a moved path, a new or changed extension point, a retired rule — updates that file in the same change-set and bumps this marker. **Bump = replace the version/date and a ≤ 1-line summary; never append a changelog here** — details belong in the commit message and `docs/`, because this whole file is loaded into every session. ## Project mission - Maintain a reliable local-first dashboard for Claude Code session monitoring. diff --git a/client/src/components/settings/AppearanceSettings.tsx b/client/src/components/settings/AppearanceSettings.tsx index 130865a9..019f52ff 100644 --- a/client/src/components/settings/AppearanceSettings.tsx +++ b/client/src/components/settings/AppearanceSettings.tsx @@ -60,6 +60,10 @@ export const THEME_LABELS: Record = { canvas: "Canvas", "orca-dark": "Orca Dark", "orca-light": "Orca Light", + "sunset-cloud": "Sunset Cloud", + terminal: "Terminal", + "night-sea": "Night Sea", + sakura: "Sakura", }; const MODES: { mode: ThemeMode; Icon: typeof Moon }[] = [ diff --git a/client/src/components/settings/__tests__/AppearanceSettings.test.tsx b/client/src/components/settings/__tests__/AppearanceSettings.test.tsx index b94b4f34..cdeeefaf 100644 --- a/client/src/components/settings/__tests__/AppearanceSettings.test.tsx +++ b/client/src/components/settings/__tests__/AppearanceSettings.test.tsx @@ -53,6 +53,10 @@ describe("AppearanceSettings", () => { "Apple Light", "Canvas", "Orca Light", + "Sunset Cloud", + "Terminal", + "Night Sea", + "Sakura", ]) { expect(screen.getByText(name)).toBeInTheDocument(); } diff --git a/client/src/hooks/__tests__/useTheme.test.ts b/client/src/hooks/__tests__/useTheme.test.ts index 03b133d7..4c0be59c 100644 --- a/client/src/hooks/__tests__/useTheme.test.ts +++ b/client/src/hooks/__tests__/useTheme.test.ts @@ -103,6 +103,19 @@ describe("useTheme", () => { expect(document.documentElement.dataset.theme).toBe("orca-light"); }); + it("applies the ai-chat-themes palettes by id, each in its own mode only", () => { + const { result } = renderHook(() => useTheme()); + for (const name of ["sunset-cloud", "terminal", "night-sea"]) { + act(() => result.current.setThemeFor("dark", name)); + expect(document.documentElement.dataset.theme).toBe(name); + } + act(() => result.current.setThemeFor("dark", "sakura")); + expect(result.current.themeFor.dark).toBe("night-sea"); + act(() => result.current.setThemeFor("light", "sakura")); + act(() => result.current.toggleTheme()); + expect(document.documentElement.dataset.theme).toBe("sakura"); + }); + it("the default theme carries no data-theme attribute", () => { const { result } = renderHook(() => useTheme()); act(() => result.current.setThemeFor("dark", "darcula")); diff --git a/client/src/hooks/useTheme.ts b/client/src/hooks/useTheme.ts index 31be4c0f..35a37719 100644 --- a/client/src/hooks/useTheme.ts +++ b/client/src/hooks/useTheme.ts @@ -22,8 +22,8 @@ export type ThemeMode = Theme | "system"; /** Named themes per mode, in picker order. `default` is the stock palette. */ export const THEMES = { - dark: ["default", "darcula", "apple-dark", "orca-dark"], - light: ["default", "canvas", "apple-light", "orca-light"], + dark: ["default", "darcula", "apple-dark", "orca-dark", "sunset-cloud", "terminal", "night-sea"], + light: ["default", "canvas", "apple-light", "orca-light", "sakura"], } as const satisfies Record; const MODE_KEY = "theme"; diff --git a/client/src/index.css b/client/src/index.css index 91a46d06..18ce4c4f 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -350,6 +350,106 @@ --syn-del: 197 0 15; /* #c5000f */ } +/* Sunset Cloud, Terminal, Night Sea (dark) and Sakura (light) — the colour + * themes of ai-chat-themes (github.com/Aohkne/ai-chat-themes, MIT), which + * gives only a background, a text and an accent colour per theme. Every + * other token is derived from those three and held to the bar above: text + * ≥ 4.5:1 on a card and on its hover lift, the text accent ≥ 4.5:1 on a card + * and on its own /15 chip (Sakura's #e0567f and Sunset's #ff8a6b darkened / + * lightened to get there), white ≥ 4.5:1 on `--accent-solid`, each status + * label ≥ 4.5:1 on its /20 chip. Their fonts (a monospace UI, Georgia) are + * not taken; syntax, diff and `--kept` colours are their mode's defaults. + * `--ansi-blue` (read only by `lib/terminalTheme.ts`) keeps a terminal's + * blue blue where the accent is green or coral. */ +.dark[data-theme="sunset-cloud"] { + --surface-0: 32 24 36; /* #201824 */ + --surface-1: 48 37 52; /* #302534 */ + --surface-2: 54 43 58; /* #362b3a */ + --surface-3: 60 49 63; /* #3c313f */ + --surface-4: 67 56 69; /* #433845 */ + --surface-5: 75 63 76; /* #4b3f4c */ + --border: 76 64 76; /* #4c404c */ + --border-light: 88 76 87; /* #584c57 */ + --accent: 255 159 134; /* #ff9f86 */ + --accent-hover: 255 188 170; /* #ffbcaa */ + --accent-solid: 173 94 73; /* #ad5e49 */ + --accent-solid-hover: 149 81 63; /* #95513f */ + --fg-primary: 247 232 226; /* #f7e8e2 */ + --fg-secondary: 214 203 203; /* #d6cbcb */ + --fg-muted: 176 165 168; /* #b0a5a8 */ + --status-success: 88 220 156; /* #58dc9c */ + --status-danger: 255 176 173; /* #ffb0ad */ + --status-warning: 255 202 22; /* #ffca16 */ + /* ANSI blue in the terminal: the accent is not blue here. */ + --ansi-blue: 112 184 255; /* #70b8ff */ +} + +.dark[data-theme="terminal"] { + --surface-0: 8 13 9; /* #080d09 */ + --surface-1: 14 23 17; /* #0e1711 */ + --surface-2: 20 30 22; /* #141e16 */ + --surface-3: 25 36 28; /* #19241c */ + --surface-4: 31 44 34; /* #1f2c22 */ + --surface-5: 37 52 41; /* #253429 */ + --border: 38 54 42; /* #26362a */ + --border-light: 49 67 53; /* #314335 */ + --accent: 57 232 122; /* #39e87a */ + --accent-hover: 116 239 162; /* #74efa2 */ + --accent-solid: 32 132 70; /* #208446 */ + --accent-solid-hover: 28 114 60; /* #1c723c */ + --fg-primary: 185 246 198; /* #b9f6c6 */ + --fg-secondary: 147 196 157; /* #93c49d */ + --fg-muted: 117 154 125; /* #759a7d */ + --status-success: 61 214 140; /* #3dd68c */ + --status-danger: 255 149 146; /* #ff9592 */ + --status-warning: 255 202 22; /* #ffca16 */ + /* ANSI blue in the terminal: the accent is not blue here. */ + --ansi-blue: 112 184 255; /* #70b8ff */ +} + +.dark[data-theme="night-sea"] { + --surface-0: 11 20 33; /* #0b1421 */ + --surface-1: 20 31 49; /* #141f31 */ + --surface-2: 26 37 55; /* #1a2537 */ + --surface-3: 32 43 62; /* #202b3e */ + --surface-4: 40 50 69; /* #283245 */ + --surface-5: 47 58 76; /* #2f3a4c */ + --border: 48 59 77; /* #303b4d */ + --border-light: 60 71 89; /* #3c4759 */ + --accent: 92 175 255; /* #5cafff */ + --accent-hover: 141 199 255; /* #8dc7ff */ + --accent-solid: 62 120 176; /* #3e78b0 */ + --accent-solid-hover: 53 103 151; /* #356797 */ + --fg-primary: 220 230 250; /* #dce6fa */ + --fg-secondary: 183 192 210; /* #b7c0d2 */ + --fg-muted: 146 155 173; /* #929bad */ + --status-success: 61 214 140; /* #3dd68c */ + --status-danger: 255 155 153; /* #ff9b99 */ + --status-warning: 255 202 22; /* #ffca16 */ +} + +[data-mode="light"][data-theme="sakura"] { + --surface-0: 242 230 232; /* #f2e6e8 */ + --surface-1: 247 235 237; /* #f7ebed */ + --surface-2: 251 240 242; /* #fbf0f2 */ + --surface-3: 255 249 250; /* #fff9fa */ + --surface-4: 255 252 253; /* #fffcfd */ + --surface-5: 255 255 255; /* #ffffff */ + --border: 224 210 213; /* #e0d2d5 */ + --border-light: 237 224 227; /* #ede0e3 */ + --accent: 167 64 95; /* #a7405f */ + --accent-hover: 137 52 78; /* #89344e */ + --accent-solid: 195 75 110; /* #c34b6e */ + --accent-solid-hover: 168 65 95; /* #a8415f */ + --fg-primary: 75 42 53; /* #4b2a35 */ + --fg-secondary: 107 80 89; /* #6b5059 */ + --fg-muted: 120 97 105; /* #786169 */ + --status-success: 23 105 45; /* #17692d */ + --status-danger: 173 28 38; /* #ad1c26 */ + --status-warning: 124 82 0; /* #7c5200 */ + --shadow-card: 0 1px 2px rgb(75 42 53 / 0.06), 0 2px 6px rgb(75 42 53 / 0.07); +} + @layer base { body { font-weight: 500; diff --git a/client/src/lib/__tests__/terminalTheme.test.ts b/client/src/lib/__tests__/terminalTheme.test.ts index 399d82c5..236ddf83 100644 --- a/client/src/lib/__tests__/terminalTheme.test.ts +++ b/client/src/lib/__tests__/terminalTheme.test.ts @@ -39,6 +39,11 @@ describe("buildTerminalTheme", () => { expect(t.brightWhite).toBe("#edeef0"); }); + it("takes ANSI blue from --ansi-blue when a theme's accent is not blue", () => { + const t = buildTerminalTheme((n) => ({ ...vars, "--ansi-blue": "112 184 255" })[n] ?? ""); + expect(t.blue).toBe("#70b8ff"); + }); + it("makes each bright twin lighter than its base colour", () => { const t = buildTerminalTheme((n) => vars[n] ?? ""); const sum = (hex: string) => diff --git a/client/src/lib/terminalTheme.ts b/client/src/lib/terminalTheme.ts index 8a6935ad..84dec3aa 100644 --- a/client/src/lib/terminalTheme.ts +++ b/client/src/lib/terminalTheme.ts @@ -40,7 +40,9 @@ function lighten(hex: string | undefined, amount: number): string | undefined { /** * Build an xterm theme from a theme's CSS variables. red/green/yellow come from - * `--status-*`, blue from `--accent`, black/white from the surface/fg ramp. + * `--status-*`, blue from `--ansi-blue` when a theme sets one (its accent is + * not blue: Terminal's green, Sunset Cloud's coral) else `--accent`, + * black/white from the surface/fg ramp. * Magenta and cyan stay the classic ANSI hues: no token keeps that hue in every * theme (darcula's keyword is orange), and a program printing "magenta" should * get magenta. @@ -50,7 +52,7 @@ export function buildTerminalTheme(read: VarReader): ITheme { const red = v("--status-danger"); const green = v("--status-success"); const yellow = v("--status-warning"); - const blue = v("--accent"); + const blue = v("--ansi-blue") ?? v("--accent"); const magenta = "#c678dd"; const cyan = "#56b6c2"; return { diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md index 14373a85..df915ae1 100644 --- a/docs/OVERVIEW.md +++ b/docs/OVERVIEW.md @@ -84,8 +84,9 @@ Internal build — all rights reserved. - **Updates.** Release-based update detection with an in-UI **Update all** (marketplaces → every plugin → AgentHub last → one session reload). - **Appearance.** Settings → Appearance picks Dark / Light / System (follows the - OS live) and a theme per mode — dark: Default, Darcula, Apple Dark, Orca Dark; - light: Default, Apple Light, Canvas, Orca Light. Each mode + OS live) and a theme per mode — dark: Default, Darcula, Apple Dark, Orca Dark, + Sunset Cloud, Terminal, Night Sea; light: Default, Apple Light, Canvas, Orca + Light, Sakura (the four last from ai-chat-themes, colours only). Each mode remembers its own theme, so the sidebar's quick toggle swaps between the two. It also picks the workspace terminals' font: JetBrains Mono (default), the rounded Maple Mono, or the browser's own fixed-width