Refactor: Consolidate account management to fix race conditions and state sync bugs #45
Labels
No labels
area:api
area:core
area:docs
area:infra
area:ux
dependencies
documentation
duplicate
good first issue
help wanted
invalid
question
rust
status:complete
status:partial
status:planned
type:bug
type:design
type:feature
type:infra
type:refactor
type:research
type:ux
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
icub3d/decentcom#45
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Migrated from GitHub issue icub3d/decentcom#61
Original Author: @icub3d
Original Date: 2026-04-16T19:53:58Z
Problem
Account management logic is scattered across multiple files and stores, causing race conditions during account switches. Users experience:
rehydrate()is fire-and-forget; downstream code reads stale data from the previous account.Root Cause
Account switching is a multi-step, non-atomic operation spread across 4+ files with no coordination:
App.tsx—handleSwitchAccount,handleFirstRunComplete, auto-connect effectAccountSwitcher.tsx—handleSetupComplete(duplicates App.tsx logic)appStore.ts—switchAppStoreAccount,initAppStoreForAccount(localStorage persistence)identityStore.ts—setActiveAccount(Tauri backend IPC)serverStore.ts—connect/disconnect(gateway + auth)useIdentity.ts— local React state forhasIdentity/publicKeySpecific Bugs
1. Auto-connect races with account switch (
App.tsx:71-82)When
disconnect()sets status to"disconnected", the auto-connect effect fires immediately. ButswitchAppStoreAccountmay not have finished rehydrating, soaddServerwrites the server to the wrong account's localStorage key.2.
rehydrate()not awaited (appStore.ts:121)initAppStoreForAccountcallsrehydrate()without awaiting it. Any code that reads the store before rehydration completes sees stale (previous account) data.3. Stale closures in
handleFirstRunComplete(App.tsx:113-118)Uses the
activeAccountcaptured at render time, not the current value. Afterrefresh()completes, the closure value may be stale.4. Duplicate setup-complete logic (
AccountSwitcher.tsx:53-61)AccountSwitcher.handleSetupCompleteduplicatesApp.handleFirstRunCompletewith the same stale-closure bug. Two code paths manage the same transition, making bugs harder to track.5. Disconnect-before-backend-switch ordering (
App.tsx:101-111)The localStorage key is repointed before the backend knows about the switch. If the auto-connect effect fires, it authenticates with the old account's key.
6. No verification of backend state (
identityStore.ts:49-62)setActiveAccountcalls the backend then re-fetches the account list, but never verifies the returnedactiveAccountmatches the requestedpubkey. A silent failure leaves the UI out of sync.Proposed Solution
Create a centralized
useAccountManagerhookConsolidate all account switch/create/import logic into a single hook that:
isSwitching) to prevent concurrent switches and block auto-connect.isSwitching = truesetActiveAccountrehydrate()(not fire-and-forget)isSwitching = falsegetState()— never captureactiveAccountin closures; always read from store at call time.Tasks
client/src/hooks/useAccountManager.tswith centralized switch/create/import logicisSwitchingflag to gate auto-connect effect inApp.tsxinitAppStoreForAccountawaitrehydrate()instead of fire-and-forgetactiveAccountclosures withuseIdentityStore.getState().activeAccountcallshandleSetupCompletefromAccountSwitcher.tsx; delegate touseAccountManagerhandleFirstRunComplete/handleSwitchAccountfromApp.tsx; delegate touseAccountManagersetActiveAccountresult matches requested pubkeyserverStore.connectto checkisSwitchingbefore proceedinguseAccountManagerhook (switch, create, concurrent switch rejection)Test Plan
useAccountManager.switchAccountsetsisSwitching, calls backend, awaits rehydrate, clears flaguseAccountManager.switchAccountrejects concurrent calls whileisSwitchingis trueisSwitchingis trueinitAppStoreForAccountawaits rehydrate before resolvingsetActiveAccountthrows if backend returns different pubkey than requestedFiles Affected
client/src/hooks/useAccountManager.ts(new)client/src/App.tsxclient/src/stores/appStore.tsclient/src/stores/identityStore.tsclient/src/stores/serverStore.tsclient/src/hooks/useIdentity.tsclient/src/components/accounts/AccountSwitcher.tsx