From f2cf94c7c0a4a90f0d9588fe42fe567bf21c93ab Mon Sep 17 00:00:00 2001 From: Devanshukoli Date: Wed, 26 Aug 2026 18:22:51 +0530 Subject: [PATCH 1/2] fix: persist appearance theme only after save Theme picks now preview in place and revert when leaving Appearance or Settings, so unsaved choices no longer stick across tabs. Save Changes is what commits the theme. Co-authored-by: Cursor --- src/components/SettingsView.tsx | 33 ++++++++--- src/lib/theme.test.ts | 101 ++++++++++++++++++++++++++++++++ src/lib/theme.ts | 24 ++++++-- 3 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 src/lib/theme.test.ts diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx index ef9c62a..2c2632d 100644 --- a/src/components/SettingsView.tsx +++ b/src/components/SettingsView.tsx @@ -24,7 +24,7 @@ import { } from 'lucide-react'; import { UserProfile } from '../types'; import { fetchWithAuth } from '../lib/auth'; -import { applyTheme, getStoredTheme } from '../lib/theme'; +import { applyTheme, getStoredTheme, previewTheme, revertThemePreview, ThemeMode } from '../lib/theme'; import { logoutUser } from '../lib/auth'; interface SettingsViewProps { @@ -52,7 +52,7 @@ export default function SettingsView({ user, onUpdateUser }: SettingsViewProps) const [email, setEmail] = useState(user?.email); // Appearance - const [appearance, setAppearance] = useState<'light' | 'dark' | 'system'>(() => user?.appearance || getStoredTheme()); + const [appearance, setAppearance] = useState(() => user?.appearance || getStoredTheme()); // Security & 2FA State const [currentPassword, setCurrentPassword] = useState(''); @@ -246,8 +246,8 @@ export default function SettingsView({ user, onUpdateUser }: SettingsViewProps) if (user) { setName(user.name || ''); setEmail(user.email || ''); - setAppearance(user.appearance || 'system'); - applyTheme(user.appearance || 'system'); + setAppearance(user.appearance || getStoredTheme()); + applyTheme(user.appearance || getStoredTheme()); setTwoFactorEnabled(user.twoFactorEnabled || false); setGeminiKey(user.apiKeys?.gemini || ''); setOpenaiKey(user.apiKeys?.openai || ''); @@ -260,10 +260,16 @@ export default function SettingsView({ user, onUpdateUser }: SettingsViewProps) setAllowTelemetry(user.privacy?.allowTelemetry ?? true); setSearchHistoryCleared(user.privacy?.searchHistoryCleared ?? false); } else { - applyTheme(getStoredTheme()); + revertThemePreview(); } }, [user]); + useEffect(() => { + return () => { + revertThemePreview(); + }; + }, []); + const handleExportData = async () => { setIsExporting(true); setExportSuccess(null); @@ -356,9 +362,19 @@ export default function SettingsView({ user, onUpdateUser }: SettingsViewProps) } }; - const handleAppearanceChange = (mode: 'light' | 'dark' | 'system') => { + const committedAppearance: ThemeMode = user?.appearance || getStoredTheme(); + + const handleSettingsTabChange = (id: typeof activeTab) => { + if (activeTab === 'appearance' && id !== 'appearance') { + setAppearance(committedAppearance); + previewTheme(committedAppearance); + } + setActiveTab(id); + }; + + const handleAppearanceChange = (mode: ThemeMode) => { setAppearance(mode); - applyTheme(mode); + previewTheme(mode); }; const handleChangePassword = async (e: React.FormEvent) => { @@ -543,6 +559,7 @@ export default function SettingsView({ user, onUpdateUser }: SettingsViewProps) } }); + applyTheme(appearance); setSavedSuccess(true); setTimeout(() => setSavedSuccess(false), 3000); } catch (err: any) { @@ -598,7 +615,7 @@ export default function SettingsView({ user, onUpdateUser }: SettingsViewProps) return (