diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts index b9f359ac..f3ac7e96 100644 --- a/src/renderer/store/slices/teamSlice.ts +++ b/src/renderer/store/slices/teamSlice.ts @@ -89,6 +89,10 @@ import { pruneOptimisticMessages, upsertOptimisticTeamMessage, } from '../team/teamMessagesCache'; +import { + loadPersistedMessagesPanelMode, + savePersistedMessagesPanelMode, +} from '../team/teamMessagesPanelModePersistence'; import { clearAllPendingReplyRefreshWaits, clearPendingReplyRefreshWaits, @@ -175,6 +179,10 @@ export type { TeamMessagesCacheEntry, } from '../team/teamMessagesCache'; export { selectMemberMessagesForTeamMember, selectTeamMessages } from '../team/teamMessagesCache'; +export { + loadPersistedMessagesPanelMode, + savePersistedMessagesPanelMode, +} from '../team/teamMessagesPanelModePersistence'; export { getActiveTeamPendingReplyWaits, hasActiveTeamPendingReplyWait, @@ -2200,33 +2208,6 @@ export interface TeamSlice { // --- Per-team launch params persistence --- const LAUNCH_PARAMS_PREFIX = 'team:launchParams:'; -const MESSAGES_PANEL_MODE_STORAGE_KEY = 'team:messagesPanelMode'; -const DEFAULT_MESSAGES_PANEL_MODE: TeamMessagesPanelMode = 'sidebar'; -const VALID_MESSAGES_PANEL_MODES: ReadonlySet = new Set([ - 'sidebar', - 'inline', - 'bottom-sheet', - 'floating-composer', -]); - -export function loadPersistedMessagesPanelMode(): TeamMessagesPanelMode { - try { - const persisted = localStorage.getItem(MESSAGES_PANEL_MODE_STORAGE_KEY); - return VALID_MESSAGES_PANEL_MODES.has(persisted as TeamMessagesPanelMode) - ? (persisted as TeamMessagesPanelMode) - : DEFAULT_MESSAGES_PANEL_MODE; - } catch { - return DEFAULT_MESSAGES_PANEL_MODE; - } -} - -export function savePersistedMessagesPanelMode(mode: TeamMessagesPanelMode): void { - try { - localStorage.setItem(MESSAGES_PANEL_MODE_STORAGE_KEY, mode); - } catch { - // ignore - best-effort UI preference persistence - } -} export function getCurrentProvisioningProgressForTeam( state: Pick, diff --git a/src/renderer/store/team/teamMessagesPanelModePersistence.ts b/src/renderer/store/team/teamMessagesPanelModePersistence.ts new file mode 100644 index 00000000..4a53086b --- /dev/null +++ b/src/renderer/store/team/teamMessagesPanelModePersistence.ts @@ -0,0 +1,29 @@ +import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode'; + +const MESSAGES_PANEL_MODE_STORAGE_KEY = 'team:messagesPanelMode'; +const DEFAULT_MESSAGES_PANEL_MODE: TeamMessagesPanelMode = 'sidebar'; +const VALID_MESSAGES_PANEL_MODES: ReadonlySet = new Set([ + 'sidebar', + 'inline', + 'bottom-sheet', + 'floating-composer', +]); + +export function loadPersistedMessagesPanelMode(): TeamMessagesPanelMode { + try { + const persisted = localStorage.getItem(MESSAGES_PANEL_MODE_STORAGE_KEY); + return VALID_MESSAGES_PANEL_MODES.has(persisted as TeamMessagesPanelMode) + ? (persisted as TeamMessagesPanelMode) + : DEFAULT_MESSAGES_PANEL_MODE; + } catch { + return DEFAULT_MESSAGES_PANEL_MODE; + } +} + +export function savePersistedMessagesPanelMode(mode: TeamMessagesPanelMode): void { + try { + localStorage.setItem(MESSAGES_PANEL_MODE_STORAGE_KEY, mode); + } catch { + // ignore - best-effort UI preference persistence + } +} diff --git a/test/renderer/store/teamMessagesPanelModePersistence.test.ts b/test/renderer/store/teamMessagesPanelModePersistence.test.ts new file mode 100644 index 00000000..baea02fb --- /dev/null +++ b/test/renderer/store/teamMessagesPanelModePersistence.test.ts @@ -0,0 +1,67 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + loadPersistedMessagesPanelMode, + savePersistedMessagesPanelMode, +} from '../../../src/renderer/store/team/teamMessagesPanelModePersistence'; + +import type { TeamMessagesPanelMode } from '../../../src/renderer/types/teamMessagesPanelMode'; + +const STORAGE_KEY = 'team:messagesPanelMode'; + +describe('teamMessagesPanelModePersistence', () => { + beforeEach(() => { + window.localStorage.clear(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('defaults to sidebar when no value was persisted', () => { + expect(loadPersistedMessagesPanelMode()).toBe('sidebar'); + }); + + it('loads each supported persisted mode', () => { + const modes: TeamMessagesPanelMode[] = [ + 'sidebar', + 'inline', + 'bottom-sheet', + 'floating-composer', + ]; + + for (const mode of modes) { + window.localStorage.setItem(STORAGE_KEY, mode); + + expect(loadPersistedMessagesPanelMode()).toBe(mode); + } + }); + + it('falls back to sidebar for invalid persisted values', () => { + window.localStorage.setItem(STORAGE_KEY, 'bad-mode'); + + expect(loadPersistedMessagesPanelMode()).toBe('sidebar'); + }); + + it('persists the selected mode', () => { + savePersistedMessagesPanelMode('inline'); + + expect(window.localStorage.getItem(STORAGE_KEY)).toBe('inline'); + }); + + it('falls back to sidebar when localStorage read fails', () => { + vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => { + throw new Error('blocked'); + }); + + expect(loadPersistedMessagesPanelMode()).toBe('sidebar'); + }); + + it('ignores localStorage write failures', () => { + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + throw new Error('blocked'); + }); + + expect(() => savePersistedMessagesPanelMode('bottom-sheet')).not.toThrow(); + }); +});