diff --git a/src/components/settings/ReadingSettings.tsx b/src/components/settings/ReadingSettings.tsx index 20b50a87..0be8f6bc 100644 --- a/src/components/settings/ReadingSettings.tsx +++ b/src/components/settings/ReadingSettings.tsx @@ -32,6 +32,7 @@ export function ReadingSettings() { const deleteAction = useSettingsStore((s) => s.deleteAction); const permanentlyDeleteJunk = useSettingsStore((s) => s.permanentlyDeleteJunk); const showPreview = useSettingsStore((s) => s.showPreview); + const clearSearchOnFolderChange = useSettingsStore((s) => s.clearSearchOnFolderChange); const disableThreading = useSettingsStore((s) => s.disableThreading); const includeGroupInUnified = useSettingsStore((s) => s.includeGroupInUnified); const autoSelectReplyIdentity = useSettingsStore((s) => s.autoSelectReplyIdentity); @@ -184,6 +185,10 @@ export function ReadingSettings() { update('permanentlyDeleteJunk', v)} /> + + update('clearSearchOnFolderChange', v)} /> + + update('showPreview', v)} /> diff --git a/src/stores/__tests__/email-store.test.ts b/src/stores/__tests__/email-store.test.ts index 236077e4..f8770064 100644 --- a/src/stores/__tests__/email-store.test.ts +++ b/src/stores/__tests__/email-store.test.ts @@ -236,6 +236,36 @@ describe('email-store', () => { expect(useEmailStore.getState().emailStates).toEqual({ 'mb-empty': 'em-state-0' }); expect(mockGetEmails).not.toHaveBeenCalled(); }); + + it('drops the search and browses the folder with "clear search when switching folders"', async () => { + useSettingsStore.getState().updateSetting('clearSearchOnFolderChange', true); + try { + useEmailStore.setState({ + currentMailboxId: 'mb-1', + mailboxes: [ + { id: 'mb-1', name: 'Inbox', isShared: false } as any, + { id: 'mb-2', name: 'Receipts', isShared: false } as any, + ], + searchQuery: 'invoice', + filters: { isUnread: true }, + emails: [{ id: 'hit' } as any], + }); + mockQueryEmails.mockResolvedValue({ ids: [], total: 0, queryState: 'q' }); + + await useEmailStore.getState().selectMailbox('mb-2'); + + const state = useEmailStore.getState(); + expect(state.searchQuery).toBe(''); + expect(state.filters).toEqual({}); + // The search hits neither stay on screen nor become Inbox's snapshot. + expect(state.emails).toEqual([]); + expect(state.mailboxSnapshots['mb-1']).toBeUndefined(); + expect(mockQueryEmails.mock.calls[0][0]).toBe('mb-2'); + expect(mockQueryEmails.mock.calls[0][1].filter).toBeUndefined(); + } finally { + useSettingsStore.getState().updateSetting('clearSearchOnFolderChange', false); + } + }); }); describe('loadMoreEmails', () => { diff --git a/src/stores/email-store.ts b/src/stores/email-store.ts index 0e6b4da6..61aebd32 100644 --- a/src/stores/email-store.ts +++ b/src/stores/email-store.ts @@ -752,6 +752,11 @@ export const useEmailStore = create()( }; } + // "Clear search when switching folders": drop the query and filters and + // browse the folder, instead of re-running the search there. + const clearSearch = !baseView && useSettingsStore.getState().clearSearchOnFolderChange; + const browse = baseView || clearSearch; + const incoming = mailboxSnapshots[mailboxId]; // Swap to the new mailbox's cached view immediately. If there's no // snapshot, fall through to the offline cache as a second-best seed; @@ -759,11 +764,11 @@ export const useEmailStore = create()( // a blank list — better than the previous flash to "Loading…". // With a search/filter active the search is kept and re-run in the new // folder (#553), so the current results stay on screen until it lands. - let seededEmails: Email[] = baseView ? incoming?.emails ?? [] : state.emails; - let seededTotal = baseView ? incoming?.total ?? 0 : state.totalEmails; - const seededQueryState = baseView ? incoming?.queryState : undefined; + let seededEmails: Email[] = browse ? incoming?.emails ?? [] : state.emails; + let seededTotal = browse ? incoming?.total ?? 0 : state.totalEmails; + const seededQueryState = browse ? incoming?.queryState : undefined; - if (baseView && seededEmails.length === 0) { + if (browse && seededEmails.length === 0) { const cacheStore = useOfflineCacheStore.getState(); if (!cacheStore.hydrated) await cacheStore.hydrate(); if (cacheStore.totalCount() > 0) { @@ -786,6 +791,7 @@ export const useEmailStore = create()( } set({ + ...(clearSearch ? { searchQuery: '', filters: {} } : {}), currentMailboxId: mailboxId, emails: seededEmails, totalEmails: seededTotal, diff --git a/src/stores/settings-store.ts b/src/stores/settings-store.ts index ce9bd25e..42579d4f 100644 --- a/src/stores/settings-store.ts +++ b/src/stores/settings-store.ts @@ -197,6 +197,9 @@ interface PersistedSettings { showAvatarsInJunk: boolean; // Show the "/ total" part of the folder counts in the drawer (#498). showFolderTotalCount: boolean; + // Opening a folder drops an active search and filters and browses it, + // instead of re-running the search there (#553, the default). + clearSearchOnFolderChange: boolean; // Unified views span every logged-in account instead of just the active // one (own + its shared/group folders). Off by default like the webmail. unifiedCrossAccount: boolean; @@ -366,6 +369,7 @@ const DEFAULT_PERSISTED: PersistedSettings = { messageListOrderScope: 'inbox', showAvatarsInJunk: false, showFolderTotalCount: true, + clearSearchOnFolderChange: false, unifiedCrossAccount: false, mailAttachmentAction: 'preview', attachmentPosition: 'beside-sender',