fix: eliminate full-screen flash when switching accounts

This commit is contained in:
Linus Rath
2026-07-22 17:48:39 +02:00
parent 5818e60401
commit 24056e4698
+37 -16
View File
@@ -451,8 +451,11 @@ export async function connectedAccountCandidates(client: JMAPClient, serverUrl:
} catch { /* session unavailable */ }
let primaryEmail: string | undefined;
try {
const { primaryIdentity } = loadIdentities(await client.getIdentities(), client.getUsername());
primaryEmail = primaryIdentity?.email;
// Pure resolution (no setIdentities side effect): this guard runs while the
// previous account may still be on screen during a seamless switch, so it
// must not mutate the live identity store.
const sorted = sortIdentities(await client.getIdentities(), client.getUsername());
primaryEmail = sorted[0]?.email;
} catch { /* identities unavailable */ }
return buildServerIdentifiers(sessionUser, primaryEmail, serverUrl);
}
@@ -1350,24 +1353,30 @@ export const useAuthStore = create<AuthState>()(
const targetAccount = accountStore.getAccountById(accountId);
if (!targetAccount) return;
// Null out the client immediately so the page doesn't fire data-loading
// effects with the old client while stores are being cleared.
set({ isLoading: true, client: null, isRateLimited: false, rateLimitUntil: null });
// Snapshot current account
if (state.activeAccountId) {
snapshotAccount(state.activeAccountId);
}
// Clear current stores
clearAllStores();
useSettingsStore.getState().disableSync();
// Get or create client for target account
// A switch between two already-connected accounts is done seamlessly:
// the current account's client and mail stay on screen while we verify
// the target session, then the store state is swapped in one synchronous
// batch (further below). Nulling the client / raising isLoading here
// would trip the page's full-screen loading gate and blank the whole app
// mid-switch, so we only do that when the target must be re-connected
// over the network (nothing worth keeping on screen while we wait).
let targetClient = clients.get(accountId);
const wasConnected = !!targetClient;
let targetRestoreRateLimited = false;
if (!targetClient) {
// Null out the client immediately so the page doesn't fire data-loading
// effects with the old client while stores are being cleared.
set({ isLoading: true, client: null, isRateLimited: false, rateLimitUntil: null });
// Snapshot current account, then clear - there's nothing to keep on
// screen during the network round-trip.
if (state.activeAccountId) {
snapshotAccount(state.activeAccountId);
}
clearAllStores();
useSettingsStore.getState().disableSync();
// Client not connected - try to restore
try {
if (targetAccount.authMode === 'oauth') {
@@ -1497,6 +1506,18 @@ export const useAuthStore = create<AuthState>()(
accountStore.updateAccount(accountId, { serverIdentifiers: connectedCandidates });
}
// Seamless path: the outgoing account is still on screen (we deferred
// clearing it), so snapshot + clear it now - synchronously, right before
// the restore and client swap below - so the UI never blanks between the
// two accounts. The network path already snapshotted and cleared above.
if (wasConnected) {
if (state.activeAccountId) {
snapshotAccount(state.activeAccountId);
}
clearAllStores();
useSettingsStore.getState().disableSync();
}
// Restore cached state or fetch fresh
const restored = restoreAccount(accountId);
accountStore.setActiveAccount(accountId);