Merge pull request #509 from hildebrandttk/feat/unified-mailbox-account-scope

Feat/unified mailbox account scope

Rework the sidebar "All accounts" into an account-bounded "Unified Mailbox"
by default, with cross-account merging as an opt-in (admin-gated) sub-option.
The standalone per-account "All Mail" virtual folder is folded into the unified
All mail / Unread / Starred entries.

Conflict resolution notes:
- stores/settings-store.ts: both main and this branch independently added a
  per-account default-identity (#507) migration at different versions (main v6,
  branch v7). Merged migration is version 7 using the refactored migrateSettings
  function; the unified-mailbox rework is guarded at `version < 7` so users who
  stopped at main's interim v6 identity bump still receive it, while the #507
  identity-map coercion stays at `version < 6` so their populated map is kept.
- stores/auth-store.ts: kept main's applyPreferredIdentity (superset with the
  pre-#507 legacy migration).
- stores/email-store.ts: removed the ALL_MAIL_MAILBOX_ID paths (folded into the
  unified views) while preserving main's plugin hooks (onSearchResults /
  onEmailsFetched); adopted advancedSearchCrossViewEmails for advanced cross-view
  search.
- components/settings/layout-settings.tsx: kept main's faviconUnreadBadge setting
  alongside the new unifiedCrossAccount toggle.
- integration/: union-merged the two independently-authored suites - branch suite
  is authoritative (matches new behavior) with main's shared-identity (#569) group
  infrastructure preserved.
- components/email/email-composer.tsx: dropped a duplicate data-testid attribute
  introduced by the auto-merge.
This commit is contained in:
Linus Rath
2026-07-16 19:57:51 +02:00
62 changed files with 2460 additions and 461 deletions
+29 -18
View File
@@ -118,19 +118,26 @@ function MailLayoutPreview({
export function LayoutSettings() {
const t = useTranslations('settings.appearance');
const tEmail = useTranslations('settings.email_behavior');
const { toolbarPosition, showToolbarLabels, hideAccountSwitcher, showRailAccountList, enableUnifiedMailbox, includeGroupInUnified, enableAllMailView, allMailFolderIds, enableCrossUnreadView, enableCrossStarredView, enableCrossAllView, colorfulSidebarIcons, tintListRowsByTag, showFolderTotalCount, faviconUnreadBadge, mailLayout, proInterface, updateSetting } = useSettingsStore();
const { toolbarPosition, showToolbarLabels, hideAccountSwitcher, showRailAccountList, enableUnifiedMailbox, includeGroupInUnified, unifiedCrossAccount, allMailFolderIds, enableCrossUnreadView, enableCrossStarredView, enableCrossAllView, colorfulSidebarIcons, tintListRowsByTag, showFolderTotalCount, faviconUnreadBadge, 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');
// Cross-account "All accounts" views, each gated independently by the admin.
const connectedAccountCount = useMemo(() => accounts.filter(a => a.isConnected).length, [accounts]);
const unifiedCrossAccountAllowed = isFeatureEnabled('unifiedCrossAccountEnabled');
// Unified Mailbox entries (All mail / Unread / Starred), each gated independently
// by the admin. Scope (single account vs. cross-account) is governed by
// `unifiedCrossAccount`; the folder picker below narrows which own folders feed them.
const crossViews = [
{ setting: 'enableCrossUnreadView', value: enableCrossUnreadView, allowed: isFeatureEnabled('crossUnreadViewEnabled'), labelKey: 'cross_unread.label', descKey: 'cross_unread.description' },
{ setting: 'enableCrossStarredView', value: enableCrossStarredView, allowed: isFeatureEnabled('crossStarredViewEnabled'), labelKey: 'cross_starred.label', descKey: 'cross_starred.description' },
{ setting: 'enableCrossAllView', value: enableCrossAllView, allowed: isFeatureEnabled('crossAllViewEnabled'), labelKey: 'cross_all.label', descKey: 'cross_all.description' },
] as const;
// The folder picker narrows the own folders included in the entries above; show
// it once the user has enabled at least one of them.
const anyCrossEnabled = enableCrossUnreadView || enableCrossStarredView || enableCrossAllView;
const anyCrossAllowed = crossViews.some(c => c.allowed);
// Own (non-shared) folders and the active account's All Mail selection. The
// selection is per account: a missing entry = never configured, which
@@ -251,6 +258,21 @@ export function LayoutSettings() {
</SettingItem>
)}
{enableUnifiedMailbox && connectedAccountCount > 1 && unifiedCrossAccountAllowed && !isSettingHidden('unifiedCrossAccount') && (
<div className="ml-4 border-l-2 border-border pl-4 -mt-2">
<SettingItem
label={t('unified_mailbox.cross_account.label')}
description={t('unified_mailbox.cross_account.description')}
locked={isSettingLocked('unifiedCrossAccount')}
>
<ToggleSwitch
checked={unifiedCrossAccount}
onChange={(v) => updateSetting('unifiedCrossAccount', v)}
/>
</SettingItem>
</div>
)}
{enableUnifiedMailbox && hasGroupInboxes && !isSettingHidden('includeGroupInUnified') && (
<div className="ms-4 border-s-2 border-border ps-4 -mt-2">
<SettingItem
@@ -266,8 +288,9 @@ export function LayoutSettings() {
</div>
)}
{enableUnifiedMailbox && crossViews.some(c => c.allowed) && (
{enableUnifiedMailbox && anyCrossAllowed && (
<div className="ms-4 border-s-2 border-border ps-4 -mt-2 space-y-2">
{crossViews.map(({ setting, value, allowed, labelKey, descKey }) => (
allowed && !isSettingHidden(setting) && (
<SettingItem
@@ -286,21 +309,9 @@ export function LayoutSettings() {
</div>
)}
{allMailViewAllowed && !isSettingHidden('enableAllMailView') && (
<SettingItem
label={t('all_mail.label')}
description={t('all_mail.description')}
locked={isSettingLocked('enableAllMailView')}
>
<ToggleSwitch
checked={enableAllMailView}
onChange={(v) => updateSetting('enableAllMailView', v)}
/>
</SettingItem>
)}
{allMailViewAllowed && enableAllMailView && (
{enableUnifiedMailbox && anyCrossAllowed && anyCrossEnabled && (
<div className="ms-4 border-s-2 border-border ps-4 -mt-2 space-y-2">
<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>