From e61c66dc262e02840fdd468ab1e2d796c6c504a6 Mon Sep 17 00:00:00 2001 From: Hadrian Chio Date: Wed, 5 Aug 2026 09:09:57 +0200 Subject: [PATCH] Add light and dark theme pair for the system appearance setting --- src-tauri/src/commands.rs | 9 ++ src-tauri/src/lib.rs | 1 + src-tauri/src/types.rs | 16 +++ src/lib/api.ts | 4 + src/lib/components/AppLayout.svelte | 13 ++- src/lib/components/CommandPalette.svelte | 6 +- src/lib/components/NoteWindow.svelte | 6 +- src/lib/components/SettingsPanel.svelte | 122 ++++++++++++++++++++--- src/lib/stores/app.ts | 18 ++++ src/lib/types.ts | 2 + src/routes/+layout.svelte | 8 +- src/routes/+page.svelte | 14 ++- 12 files changed, 183 insertions(+), 36 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 05c350b..b16754d 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -331,6 +331,15 @@ pub fn set_theme(state: State<'_, AppState>, theme: String) -> Result<(), String Ok(()) } +#[tauri::command] +pub fn set_system_themes(state: State<'_, AppState>, light: String, dark: String) -> Result<(), String> { + let mut config = state.config.lock().map_err(|e| e.to_string())?; + config.system_light_theme = light; + config.system_dark_theme = dark; + save_app_config(&config)?; + Ok(()) +} + #[tauri::command] pub fn set_accent_color(state: State<'_, AppState>, color: String) -> Result<(), String> { let mut config = state.config.lock().map_err(|e| e.to_string())?; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 9343d2a..17a15a4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -130,6 +130,7 @@ pub fn run() { commands::remove_vault, commands::get_app_config, commands::set_theme, + commands::set_system_themes, commands::set_accent_color, commands::save_custom_theme, commands::delete_custom_theme, diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index 1dc1691..4d43b2c 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -114,6 +114,12 @@ pub struct AppConfig { #[serde(default)] pub active_bookmark_id: Option, pub theme: String, + /// Themes used when `theme` is "system": the frontend picks one by the OS color scheme. + /// Default to the plain "light"/"dark" schemes so existing configs keep their behavior. + #[serde(default = "default_system_light_theme")] + pub system_light_theme: String, + #[serde(default = "default_system_dark_theme")] + pub system_dark_theme: String, #[serde(default)] pub accent_color: Option, #[serde(default)] @@ -269,6 +275,14 @@ fn default_ai_model() -> String { "claude-sonnet-4-6".to_string() } +fn default_system_light_theme() -> String { + "light".to_string() +} + +fn default_system_dark_theme() -> String { + "dark".to_string() +} + impl Default for AppConfig { fn default() -> Self { Self { @@ -276,6 +290,8 @@ impl Default for AppConfig { active_vault: None, active_bookmark_id: None, theme: "system".to_string(), + system_light_theme: default_system_light_theme(), + system_dark_theme: default_system_dark_theme(), accent_color: None, font_size: None, font_family: None, diff --git a/src/lib/api.ts b/src/lib/api.ts index f94c15b..5991cba 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -43,6 +43,10 @@ export async function setTheme(theme: string): Promise { return invoke("set_theme", { theme }); } +export async function setSystemThemes(light: string, dark: string): Promise { + return invoke("set_system_themes", { light, dark }); +} + export async function setAccentColor(color: string): Promise { return invoke("set_accent_color", { color }); } diff --git a/src/lib/components/AppLayout.svelte b/src/lib/components/AppLayout.svelte index c84b7e2..6d195f7 100644 --- a/src/lib/components/AppLayout.svelte +++ b/src/lib/components/AppLayout.svelte @@ -21,6 +21,7 @@ showSearch, showCommandPalette, theme, + resolvedTheme, focusMode, readOnly, activeNote, @@ -507,7 +508,7 @@ createAndFocusNote(); return; case 'toggle-theme': { - const isDark = $theme === 'dark' || ($theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); + const isDark = darkThemes.includes($resolvedTheme); const next = isDark ? 'light' : 'dark'; $theme = next; setTheme(next); @@ -573,7 +574,7 @@ if (namedThemes.includes(t)) { root.setAttribute('data-theme', t); if (darkThemes.includes(t)) root.classList.add('dark'); - } else if (t === 'dark' || (t === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) { + } else if (t === 'dark') { root.classList.add('dark'); } } @@ -663,11 +664,9 @@ prefetchPromise = readNote(lastNotePath).catch(() => null); } - applyTheme($theme); - - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { - if ($theme === 'system') applyTheme('system'); - }); + // One-off apply to avoid a flash before the layout's reactive effect runs; OS appearance + // changes are picked up by resolvedTheme, so no media-query listener is needed here. + applyTheme($resolvedTheme); // Run sidebar and note list refresh in parallel await Promise.all([sidebar?.refresh(), noteList?.refresh()]); diff --git a/src/lib/components/CommandPalette.svelte b/src/lib/components/CommandPalette.svelte index 75289bf..e7f148b 100644 --- a/src/lib/components/CommandPalette.svelte +++ b/src/lib/components/CommandPalette.svelte @@ -1,5 +1,5 @@