diff --git a/client/src/services/appearancePrefs.test.ts b/client/src/services/appearancePrefs.test.ts index 87c8912..ba95856 100644 --- a/client/src/services/appearancePrefs.test.ts +++ b/client/src/services/appearancePrefs.test.ts @@ -69,4 +69,28 @@ describe('appearancePrefs', () => { await saveAppearancePrefsToServer('ocean', 'light') expect(mockedApiJson).not.toHaveBeenCalled() }) + + it('syncAppearancePrefs skips server sync when userId does not match active session', async () => { + localStorage.setItem('active_userid', 'session-user') + setThemePreference('other-user', 'ocean') + mockedApiJson.mockResolvedValue({ + theme: 'material', + colorScheme: 'dark', + persisted: true + }) + + await syncAppearancePrefs('other-user') + + expect(mockedApiJson).not.toHaveBeenCalled() + expect(localStorage.getItem('user_pref_theme_other-user')).toBe('ocean') + }) + + it('syncAppearancePrefs skips server sync when active session is missing', async () => { + setThemePreference(USER_ID, 'ocean') + + await syncAppearancePrefs(USER_ID) + + expect(mockedApiJson).not.toHaveBeenCalled() + expect(localStorage.getItem(`user_pref_theme_${USER_ID}`)).toBe('ocean') + }) }) diff --git a/client/src/services/appearancePrefs.ts b/client/src/services/appearancePrefs.ts index 4a24261..e55f87f 100644 --- a/client/src/services/appearancePrefs.ts +++ b/client/src/services/appearancePrefs.ts @@ -23,16 +23,30 @@ function hasLocalAppearancePrefs(userId: string): boolean { ) } -export async function fetchAppearancePrefs(): Promise { - if (!getActiveUserId()) { +function resolveSyncedUserId(userId?: string | null): string | null { + const id = userId?.trim() || getActiveUserId()?.trim() || null + if (!id) return null + + const activeId = getActiveUserId()?.trim() || null + if (!activeId || activeId !== id) return null + + return id +} + +export async function fetchAppearancePrefs(userId?: string | null): Promise { + if (!resolveSyncedUserId(userId)) { return { theme: 'auto', colorScheme: 'auto', persisted: false } } return apiJson(API_BASE) } -export async function saveAppearancePrefsToServer(theme: string, colorScheme: string): Promise { - if (!getActiveUserId()) return +export async function saveAppearancePrefsToServer( + theme: string, + colorScheme: string, + userId?: string | null +): Promise { + if (!resolveSyncedUserId(userId)) return await apiJson(API_BASE, { method: 'PUT', @@ -42,17 +56,17 @@ export async function saveAppearancePrefsToServer(theme: string, colorScheme: st /** Merge server-stored appearance with local cache (server wins after cache wipe). */ export async function syncAppearancePrefs(userId?: string | null): Promise { - const id = userId?.trim() || getActiveUserId() + const id = resolveSyncedUserId(userId) if (!id) return try { - const server = await fetchAppearancePrefs() + const server = await fetchAppearancePrefs(id) if (server.persisted) { setThemePreference(id, server.theme) setColorSchemePreference(id, server.colorScheme) } else if (hasLocalAppearancePrefs(id)) { - await saveAppearancePrefsToServer(getThemePreference(id), getColorSchemePreference(id)) + await saveAppearancePrefsToServer(getThemePreference(id), getColorSchemePreference(id), id) } } catch (err) { console.warn('Failed to sync appearance preferences:', err)