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 */ } } catch { /* session unavailable */ }
let primaryEmail: string | undefined; let primaryEmail: string | undefined;
try { try {
const { primaryIdentity } = loadIdentities(await client.getIdentities(), client.getUsername()); // Pure resolution (no setIdentities side effect): this guard runs while the
primaryEmail = primaryIdentity?.email; // 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 */ } } catch { /* identities unavailable */ }
return buildServerIdentifiers(sessionUser, primaryEmail, serverUrl); return buildServerIdentifiers(sessionUser, primaryEmail, serverUrl);
} }
@@ -1350,24 +1353,30 @@ export const useAuthStore = create<AuthState>()(
const targetAccount = accountStore.getAccountById(accountId); const targetAccount = accountStore.getAccountById(accountId);
if (!targetAccount) return; if (!targetAccount) return;
// Null out the client immediately so the page doesn't fire data-loading // A switch between two already-connected accounts is done seamlessly:
// effects with the old client while stores are being cleared. // the current account's client and mail stay on screen while we verify
set({ isLoading: true, client: null, isRateLimited: false, rateLimitUntil: null }); // the target session, then the store state is swapped in one synchronous
// batch (further below). Nulling the client / raising isLoading here
// Snapshot current account // would trip the page's full-screen loading gate and blank the whole app
if (state.activeAccountId) { // mid-switch, so we only do that when the target must be re-connected
snapshotAccount(state.activeAccountId); // over the network (nothing worth keeping on screen while we wait).
}
// Clear current stores
clearAllStores();
useSettingsStore.getState().disableSync();
// Get or create client for target account
let targetClient = clients.get(accountId); let targetClient = clients.get(accountId);
const wasConnected = !!targetClient;
let targetRestoreRateLimited = false; let targetRestoreRateLimited = false;
if (!targetClient) { 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 // Client not connected - try to restore
try { try {
if (targetAccount.authMode === 'oauth') { if (targetAccount.authMode === 'oauth') {
@@ -1497,6 +1506,18 @@ export const useAuthStore = create<AuthState>()(
accountStore.updateAccount(accountId, { serverIdentifiers: connectedCandidates }); 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 // Restore cached state or fetch fresh
const restored = restoreAccount(accountId); const restored = restoreAccount(accountId);
accountStore.setActiveAccount(accountId); accountStore.setActiveAccount(accountId);