fix: derive hasWallet from the wallet list on load (closes #195)
All checks were successful
check / check (push) Successful in 30s

loadState() took hasWallet straight from storage, so any profile persisted
with the flag out of step with wallets stayed broken on every subsequent
load rather than only until the next write. The flag is now derived from
wallets.length at load time.

Deriving is only safe if no consumer wants hasWallet to mean something
other than "the wallet list is non-empty" -- an "onboarding was completed"
or "a wallet existed once" marker would be destroyed by it. So every
occurrence was enumerated at this base (git grep -n hasWallet over tracked
files; no bracket-notation, destructured or case-variant access exists).

Reads:

  src/popup/index.js:265 -- if (!state.hasWallet) gates the welcome view
  against the wallet list at popup init, immediately after loadState().
  Means "the user has a wallet"; the derivation is exactly that.

  src/popup/views/deleteWallet.js:73 -- if (!state.hasWallet) chooses the
  post-delete view. It reads the flag immediately after
  removeWalletFromState() has set it from state.wallets.length > 0, so it
  means "any wallets remain" -- identical to the derivation, and on the
  write path rather than the load path.

  src/shared/walletDelete.js:37,41,54 -- fallback address, selection reset
  and active-address reset inside removeWalletFromState(), all
  reads-after-write of the assignment at line 35
  (state.hasWallet = state.wallets.length > 0). Same expression, same
  invocation.

  src/shared/state.js:52 -- saveState() persists the in-memory value. With
  the derivation in place it now writes the derived value, so an
  inconsistent stored blob is normalized by the next ordinary save.

Writes: src/popup/views/addWallet.js:144,200,252 set it true immediately
after pushing a wallet; src/shared/walletDelete.js:35 sets it from the
remaining wallet count; src/shared/state.js:12 defaults it false alongside
wallets: []. Every writer keeps it equal to wallets.length > 0.

No consumer depends on the two disagreeing, so the derivation preserves
every read and needs no write-back on load.
This commit is contained in:
clawbot
2026-08-11 12:21:56 +00:00
parent b9bc226ae1
commit 2589473500
3 changed files with 112 additions and 1 deletions

View File

@@ -50,6 +50,10 @@ undefined identifiers, which is how
- 2026-08-11: `docs/README.md` rewritten against the code: no competitor names,
all five network destinations documented, password/Settings/Add Wallet
sections corrected ([#163](https://git.eeqj.de/sneak/AutistMask/issues/163)).
- 2026-08-11: `loadState()` now derives `hasWallet` from the wallet list instead
of trusting the persisted flag, so a profile already saved inconsistent no
longer stays broken on every load
([#195](https://git.eeqj.de/sneak/AutistMask/issues/195)).
- 2026-08-11: Wallet deletion repairs its own state — `hasWallet` follows the
remaining wallets, the selection only moves when it was deleted, and the
active-address change is broadcast to connected sites