feat(themes): Built-in Solarized and One light/dark themes (#85)

Adds Solarized Light, Solarized Dark, One Light and One Dark as built-in themes across the workbench, editor, terminal and Mermaid, keeping the original palettes (MIT notices in THIRD_PARTY_LICENSES.md, mapping in docs/themes.md).

- Native window colours are read from tokens.css at build time (`theme_frame.rs`) instead of a second colour table.
- Startup restores the built-in palette before first paint.
- Editor schemes, the terminal and Mermaid refresh on switches between themes of the same kind.
- The plugin packages of the same themes are removed.
- A test requires every built-in palette theme to define each token (only listed ones may inherit from the base theme), so new tokens can't silently miss them.

Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
Walter WangтаClaude authored and GitHub committed 2026-10-27 18:53:12 +08:00
1 джерело 50308a956d
коміт 3b608355a3
48 files changed
+1267 -1351

No files matched your search

+5 -5
Переглянути файл
@@ -144,8 +144,8 @@ describe("store", () => {
it("selects and clears a plugin theme", async () => {
await refreshPlugins();
await selectPluginTheme("theme-solarized", "solarized-dark");
expect(usePlugins.getState().activeTheme?.theme.id).toBe("solarized-dark");
await selectPluginTheme("theme-harbor-night", "harbor-night");
expect(usePlugins.getState().activeTheme?.theme.id).toBe("harbor-night");
await selectPluginTheme(null, null);
expect(usePlugins.getState().activeTheme).toBeNull();
});
@@ -238,8 +238,8 @@ describe("PluginsPage", () => {
await waitFor(() => expect(screen.getByRole("checkbox", { name: "Enable Hello" })).toBeInTheDocument());
expect(screen.getByRole("button", { name: "Run Hello: Insert Date" })).toBeDisabled();
await user.click(within(list).getByText("Solarized Themes"));
await user.click(screen.getByRole("button", { name: "Apply theme Solarized Dark" }));
await user.click(within(list).getByText("Harbor Night Theme"));
await user.click(screen.getByRole("button", { name: "Apply theme Harbor Night" }));
await waitFor(() => expect(screen.getByRole("button", { name: "Use Default Theme" })).toBeInTheDocument());
await user.type(screen.getByLabelText("Filter plugins"), "toy");
@@ -309,7 +309,7 @@ describe("catalog helpers", () => {
expect(catalogState(entry, [plugin({ id: "hello", version: "0.0.9" })])).toBe("update");
expect(catalogState(entry, [plugin({ id: "hello", version: "0.1.0" })])).toBe("installed");
expect(catalogState(entry, [plugin({ id: "hello", source: "dev" })])).toBe("dev");
expect(filterCatalog(sampleCatalog(), "solarized").map((c) => c.id)).toEqual(["theme-solarized"]);
expect(filterCatalog(sampleCatalog(), "harbor").map((c) => c.id)).toEqual(["theme-harbor-night"]);
});
it("disables and re-enables all downloaded plugins, leaving dev plugins alone", async () => {
+18 -32
Переглянути файл
@@ -76,35 +76,21 @@ function ensureSubscribed() {
// ---------------------------------------------------------------------------
// Browser simulation: the bundled sample plugins, with `hello` implemented in JS.
const SOLARIZED: ActiveTheme["theme"][] = [
// templates/theme-plugin, abridged.
const SAMPLE_THEMES: ActiveTheme["theme"][] = [
{
id: "solarized-light",
name: "Solarized Light",
kind: "light",
tokens: {
"--bg-app": "#fdf6e3",
"--bg-island": "#fdf6e3",
"--bg-frame": "#eee8d5",
"--bg-elev": "#eee8d5",
"--fg": "#657b83",
"--accent": "#268bd2",
"--syn-keyword": "#859900",
"--syn-string": "#2aa198",
},
},
{
id: "solarized-dark",
name: "Solarized Dark",
id: "harbor-night",
name: "Harbor Night",
kind: "dark",
tokens: {
"--bg-app": "#002b36",
"--bg-island": "#002b36",
"--bg-frame": "#073642",
"--bg-elev": "#073642",
"--fg": "#839496",
"--accent": "#268bd2",
"--syn-keyword": "#859900",
"--syn-string": "#2aa198",
"--bg-app": "#14171c",
"--bg-island": "#14171c",
"--bg-frame": "#1f252e",
"--bg-elev": "#1b2027",
"--fg": "#c9d1dc",
"--accent": "#2b9e93",
"--syn-keyword": "#e0a46e",
"--syn-string": "#9ccc7a",
},
},
];
@@ -140,14 +126,14 @@ function samplePlugins(): PluginInfo[] {
},
{
...base,
id: "theme-solarized",
name: "Solarized Themes",
id: "theme-harbor-night",
name: "Harbor Night Theme",
version: "1.0.0",
description: "Solarized Light and Solarized Dark: Ethan Schoonover's Solarized palette.",
path: "~/Library/Application Support/ai.l0ng.openidea/plugins/theme-solarized",
description: "Example dark theme: slate surfaces, a teal accent and a warm syntax palette.",
path: "~/Library/Application Support/ai.l0ng.openidea/plugins/theme-harbor-night",
status: "static",
hasCode: false,
themes: SOLARIZED.map(({ id, name, kind }) => ({ id, name, kind })),
themes: SAMPLE_THEMES.map(({ id, name, kind }) => ({ id, name, kind })),
},
{
...base,
@@ -201,7 +187,7 @@ export function createMemoryPluginsBackend(
const activeTheme = (): ActiveTheme | null => {
if (!theme) return null;
const p = plugins.find((x) => x.id === theme?.pluginId);
const t = SOLARIZED.find((x) => x.id === theme?.themeId);
const t = SAMPLE_THEMES.find((x) => x.id === theme?.themeId);
if (!p?.enabled || !t) return null;
return { pluginId: p.id, theme: t };
};