From 1950c4cc3c11b221eb36189ff79e62a1bba260e4 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Thu, 6 Aug 2026 15:12:43 +0200 Subject: [PATCH] Fix system theme pair integration --- src-tauri/src/commands.rs | 39 ++++++++++++++++++++++-- src-tauri/src/types.rs | 15 ++++++++- src/lib/components/AppLayout.svelte | 25 ++------------- src/lib/components/CommandPalette.svelte | 3 +- src/lib/components/NoteWindow.svelte | 19 +----------- src/lib/components/SettingsPanel.svelte | 14 ++++++--- src/lib/stores/app.ts | 7 ++++- src/routes/+page.svelte | 3 +- 8 files changed, 73 insertions(+), 52 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index b16754d..2500e07 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -332,7 +332,11 @@ pub fn set_theme(state: State<'_, AppState>, theme: String) -> Result<(), String } #[tauri::command] -pub fn set_system_themes(state: State<'_, AppState>, light: String, dark: String) -> Result<(), String> { +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; @@ -363,12 +367,41 @@ pub fn save_custom_theme(state: State<'_, AppState>, theme: crate::types::Custom #[tauri::command] pub fn delete_custom_theme(state: State<'_, AppState>, id: String) -> Result<(), String> { let mut config = state.config.lock().map_err(|e| e.to_string())?; + clear_custom_theme_references(&mut config, &id); + save_app_config(&config)?; + Ok(()) +} + +fn clear_custom_theme_references(config: &mut AppConfig, id: &str) { config.custom_themes.retain(|t| t.id != id); if config.theme == id { config.theme = "system".to_string(); } - save_app_config(&config)?; - Ok(()) + if config.system_light_theme == id { + config.system_light_theme = "light".to_string(); + } + if config.system_dark_theme == id { + config.system_dark_theme = "dark".to_string(); + } +} + +#[cfg(test)] +mod custom_theme_reference_tests { + use super::*; + + #[test] + fn deleting_custom_theme_resets_system_pair_references() { + let mut config = AppConfig::default(); + config.theme = "custom-work".to_string(); + config.system_light_theme = "custom-work".to_string(); + config.system_dark_theme = "custom-work".to_string(); + + clear_custom_theme_references(&mut config, "custom-work"); + + assert_eq!(config.theme, "system"); + assert_eq!(config.system_light_theme, "light"); + assert_eq!(config.system_dark_theme, "dark"); + } } #[tauri::command] diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index 4d43b2c..89902f5 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -505,7 +505,7 @@ pub struct TaskItem { #[cfg(test)] mod startup_view_tests { - use super::StartupView; + use super::{AppConfig, StartupView}; #[test] fn serializes_supported_startup_views() { @@ -526,4 +526,17 @@ mod startup_view_tests { StartupView::All ); } + + #[test] + fn existing_configs_default_system_theme_pair() { + let mut value = serde_json::to_value(AppConfig::default()).unwrap(); + let object = value.as_object_mut().unwrap(); + object.remove("system_light_theme"); + object.remove("system_dark_theme"); + + let config: AppConfig = serde_json::from_value(value).unwrap(); + + assert_eq!(config.system_light_theme, "light"); + assert_eq!(config.system_dark_theme, "dark"); + } } diff --git a/src/lib/components/AppLayout.svelte b/src/lib/components/AppLayout.svelte index 6d195f7..1e444b6 100644 --- a/src/lib/components/AppLayout.svelte +++ b/src/lib/components/AppLayout.svelte @@ -22,6 +22,7 @@ showCommandPalette, theme, resolvedTheme, + customThemes, focusMode, readOnly, activeNote, @@ -508,7 +509,8 @@ createAndFocusNote(); return; case 'toggle-theme': { - const isDark = darkThemes.includes($resolvedTheme); + const customTheme = $customThemes.find(theme => theme.id === $resolvedTheme); + const isDark = darkThemes.includes($resolvedTheme) || (customTheme?.is_dark ?? false); const next = isDark ? 'light' : 'dark'; $theme = next; setTheme(next); @@ -566,23 +568,6 @@ } } - function applyTheme(t: string) { - const namedThemes = ['solarized-light', 'solarized-dark', 'catppuccin', 'nord', 'tokyo-night', 'github-light', 'github-dark', 'dracula', 'blueberry', 'forest-green', 'gruvbox', 'midnight-tide', 'cherry-blossom', 'synthwave', 'ember', 'moonlit', 'light-coffee', 'dark-coffee', 'cotton-candy', 'crimson', 'cloud', 'peach', 'material-dark', 'material-light', 'monokai', 'rose-pine', 'everforest', 'horizon', 'cyberpunk', 'black', 'one-dark']; - const root = document.documentElement; - root.classList.remove('dark'); - root.removeAttribute('data-theme'); - if (namedThemes.includes(t)) { - root.setAttribute('data-theme', t); - if (darkThemes.includes(t)) root.classList.add('dark'); - } else if (t === 'dark') { - root.classList.add('dark'); - } - } - - $effect(() => { - applyTheme($theme); - }); - $effect(() => { $collapsedNotebooks; persistState(); @@ -664,10 +649,6 @@ prefetchPromise = readNote(lastNotePath).catch(() => null); } - // 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 e7f148b..4b68c00 100644 --- a/src/lib/components/CommandPalette.svelte +++ b/src/lib/components/CommandPalette.svelte @@ -1,5 +1,5 @@