fix(identity): sync default sender identity per account (#507)

The default sender identity (`preferredPrimaryId`) lived only in the
browser-local `identity-storage` store and was never written to the synced
settings, so the choice was lost on clearing site data / switching browsers and
never appeared in exported settings.

Persist it in the synced settings store, keyed **per account**
(`preferredIdentityIds: Record<accountId, identityId>`), mirroring the existing
per-account `allMailFolderIds`. Per-account keying is required because JMAP
identity ids are account-scoped and would otherwise collide across accounts /
the unified mailbox.

This supersedes the earlier username-keyed fix that had landed on main: the
username-keyed map, `loadIdentities()` fallback write, and the
`applyPreferredIdentityOrdering` store action (plus its settings-store hook)
are removed so a single account-keyed mechanism remains.

- settings-store: `preferredIdentityIds` (accountId -> identityId) in state,
  defaults, export, import (non-record guard), rehydrate coercion, v6 migration.
- auth-store: `applyPreferredIdentity(accountId?)` reorders the active account's
  identities once synced settings load, and performs the one-time migration of
  the pre-#507 browser-local default into the synced map (keyed by accountId).
  Invoked from every `loadFromServer().finally()` (login / OAuth / SSO / switch
  / restore). `loadIdentities()` now only applies the local fallback ordering.
- identity-manager-modal: the star action writes the choice by `activeAccountId`.
- identity-store: `preferredPrimaryId` kept as a local (sync-off) fallback.
- tests: per-account independence, export/import round-trip, import guard, and
  applyPreferredIdentity reorder / active-account gating / local-default
  migration.
This commit is contained in:
Stefan Hildebrandt
2026-07-13 21:21:34 +02:00
committed by Linus Rath
parent 20d02214df
commit 01e5cd69cf
6 changed files with 249 additions and 68 deletions
+8 -1
View File
@@ -123,7 +123,14 @@ export const useIdentityStore = create<IdentityStore>()(
}),
{
name: 'identity-storage',
// Only persist sub-addressing data, not identities (they're server-side)
// Only persist sub-addressing data, not identities (they're server-side).
// The default sender identity (`preferredPrimaryId`) is the per-account
// value for the *active* account; it is kept here purely as a local
// fallback so the choice survives a reload when settings sync is off.
// The durable, cross-device, exportable source of truth is the synced
// settings store, keyed per account (`preferredIdentityIds`), which is
// re-applied via applyPreferredIdentity() once server settings load and
// overrides this value per account (issue #507).
partialize: (state) => ({
subAddress: state.subAddress,
preferredPrimaryId: state.preferredPrimaryId,