feat: per-account All Mail folder selection

Replaces the global allMailFolderIds (string[] | null) with a per-account
Record<accountId, string[]>, so each account chooses which of its own folders
the "All Mail" view merges. A missing entry = "not configured" (defaults to
every no-role folder); an explicit [] = "no folders".

- settings-store: type/default -> Record (default {}); persist version 4 -> 5,
  migration drops the legacy global list (the active account isn't known at
  migrate time); onRehydrate + importSettings coerce/ignore any non-record
  (legacy global string[] | null) shape. isPlainRecord() guard.
- email-store.resolveAllMailJmapIds: reads the entry for the account the view is
  scoped to (viewingAccountId ?? activeAccountId); undefined -> all no-role,
  [] -> none.
- layout-settings: read/write the active account's entry; when more than one
  account is logged in, an italic hint names the account the selection applies
  to (settings.appearance.all_mail.account_hint, 19 locales; de/ro translated).
- Test: stores/__tests__/settings-store-all-mail.test.ts (per-account
  independence, explicit-empty vs not-configured, importSettings legacy guard).
This commit is contained in:
Stefan Hildebrandt
2026-06-24 15:52:09 +02:00
committed by Linus Rath
parent 5c2f206c74
commit 751f3c1685
23 changed files with 133 additions and 12 deletions
+21 -5
View File
@@ -121,24 +121,37 @@ export function LayoutSettings() {
const { toolbarPosition, showToolbarLabels, hideAccountSwitcher, showRailAccountList, enableUnifiedMailbox, includeGroupInUnified, enableAllMailView, allMailFolderIds, colorfulSidebarIcons, mailLayout, proInterface, updateSetting } = useSettingsStore();
const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore();
const accounts = useAccountStore(s => s.accounts);
const activeAccountId = useAccountStore(s => s.activeAccountId);
const mailboxes = useEmailStore(s => s.mailboxes);
const hasGroupInboxes = useMemo(() => mailboxes.some(m => m.isShared), [mailboxes]);
const allMailViewAllowed = isFeatureEnabled('allMailViewEnabled');
// Own (non-shared) folders and the current All Mail selection. `null` =
// never configured, which defaults to all non-special (no-role) folders.
// Own (non-shared) folders and the active account's All Mail selection. The
// selection is per account: a missing entry = never configured, which
// defaults to all no-role folders; an explicit [] = no folders.
const ownMailboxes = useMemo(() => mailboxes.filter(m => !m.isShared), [mailboxes]);
const currentAllMailEntry = activeAccountId ? allMailFolderIds[activeAccountId] : undefined;
const allMailSelected = new Set(
allMailFolderIds === null
currentAllMailEntry === undefined
? ownMailboxes.filter(m => !m.role).map(m => m.id)
: allMailFolderIds
: currentAllMailEntry
);
const toggleAllMailFolder = (id: string) => {
if (!activeAccountId) return;
const next = new Set(allMailSelected);
if (next.has(id)) next.delete(id);
else next.add(id);
updateSetting('allMailFolderIds', ownMailboxes.filter(m => next.has(m.id)).map(m => m.id));
updateSetting('allMailFolderIds', {
...allMailFolderIds,
[activeAccountId]: ownMailboxes.filter(m => next.has(m.id)).map(m => m.id),
});
};
// Name the account the selection applies to, but only when more than one is
// logged in (otherwise it's unambiguous).
const activeAccount = accounts.find(a => a.id === activeAccountId);
const allMailAccountHint = accounts.length > 1 && activeAccount
? t('all_mail.account_hint', { account: activeAccount.displayName || activeAccount.email })
: null;
return (
<SettingsSection title={t('title')} description={t('description')}>
@@ -244,6 +257,9 @@ export function LayoutSettings() {
<div>
<div className="text-sm font-medium text-foreground">{t('all_mail.folders_label')}</div>
<div className="text-xs text-muted-foreground">{t('all_mail.folders_description')}</div>
{allMailAccountHint && (
<div className="text-xs italic text-muted-foreground mt-0.5">{allMailAccountHint}</div>
)}
</div>
{ownMailboxes.length === 0 ? (
<p className="text-xs text-muted-foreground">{t('all_mail.no_folders')}</p>