fix(settings): scope portfolio groups by wallet identity
Market-tab portfolio groups were tied to a wallet by a hash of its address set. That hash changed whenever an address was added, so a group built for a wallet could silently drop out of view once the wallet gained a new address. - Groups are now scoped by the wallet's stable id, which is persisted and never derived from the address set, so adding an address can no longer orphan a group. - An older build's address-hash scope fails open (the group stays visible) and is migrated once per wallet per session to the stable id. A legacy group is claimed when its old hash matches, when one of its own addresses belongs to this wallet, or when only one wallet is known; anything ambiguous is left legacy and stays visible. - Scope filtering waits until the wallet's addresses are loaded, so a group never flashes an empty 0.0000 balance and Add stays disabled until the wallet is resolved. This works on Lite, which never stamps an address-update time. Behaviour: an older build opened after this change hides groups tagged with a stable "w:" scope (they are not deleted and return on the next upgrade) - worth a release note. Tests: group visibility across global, stable and legacy scopes with the wallet resolved and not; the migration rules (hash match, address overlap, single-wallet claim, ambiguous left alone, idempotent); and a save/load round trip where an address-set change cannot orphan the scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+22
-3
@@ -100,10 +100,21 @@ public:
|
||||
bool showSparkline = false; // show a price-trend sparkline (live-market bases only)
|
||||
int sparklineInterval = 4; // 0=minute 1=hour 2=day 3=week 4=month (default month: a real curve
|
||||
// from the daily series, vs the young in-session minute buffer)
|
||||
// Per-wallet visibility: "" (shown in every wallet — legacy/global) or a wallet-identity
|
||||
// hash (shown only when that wallet is active). New entries are tagged with the current
|
||||
// wallet so a portfolio built for wallet A doesn't clutter wallet B.
|
||||
// Per-wallet visibility: "" (shown in every wallet - legacy/global) or the active wallet's STABLE scope
|
||||
// id ("w:"+hex, App::activeWalletScopeId(), persisted in wallets.json, never derived from the address
|
||||
// set). Older builds stored the address-set identity hash here, which drifted whenever an address was
|
||||
// added; such a legacy scope fails open (stays visible) until reattachLegacyPortfolioScopes() moves it
|
||||
// to the stable id. New entries are tagged with the current wallet so a portfolio built for wallet A
|
||||
// doesn't clutter wallet B.
|
||||
std::string scope;
|
||||
bool isGlobal() const { return scope.empty(); }
|
||||
bool isLegacyScope() const { return !scope.empty() && scope.rfind("w:", 0) != 0; }
|
||||
bool visibleInWallet(const std::string& scopeId) const {
|
||||
if (isGlobal()) return true;
|
||||
if (scopeId.empty()) return false; // wallet not resolved yet: never another wallet's groups
|
||||
if (isLegacyScope()) return true; // fail open until migrated
|
||||
return scope == scopeId;
|
||||
}
|
||||
// Legacy dashboard-grid placement (deprecated by the row layout; kept for back-compat so an
|
||||
// older build's saved positions aren't dropped, but no longer used by the renderer).
|
||||
int gridCol = -1;
|
||||
@@ -463,6 +474,14 @@ public:
|
||||
// User-defined portfolio entries (Market tab). "All funds" is implicit, not stored here.
|
||||
const std::vector<PortfolioEntry>& getPortfolioEntries() const { return portfolio_entries_; }
|
||||
void setPortfolioEntries(const std::vector<PortfolioEntry>& entries) { portfolio_entries_ = entries; }
|
||||
// Re-attach groups stuck on a legacy (address-set-hash) scope to the stable wallet id `scopeId`.
|
||||
// A legacy group is claimed when ANY of: its scope == identityHash (the current address-set hash, exact
|
||||
// match); at least one of its own addresses is in `walletAddresses` (groups carry their addresses, so
|
||||
// this attributes correctly in multi-wallet installs); or `singleWallet` (only one known wallet, so
|
||||
// attribution is unambiguous - same rule contacts use). Anything else stays legacy (fail-open visible).
|
||||
// Idempotent. Does NOT save: the caller saves once if the return value is > 0. Returns #entries moved.
|
||||
int reattachLegacyPortfolioScopes(const std::string& scopeId, const std::string& identityHash,
|
||||
const std::set<std::string>& walletAddresses, bool singleWallet);
|
||||
|
||||
// Lite servers the user has hidden from the Network tab (kept by URL, shown via a toggle).
|
||||
const std::set<std::string>& getLiteHiddenServers() const { return lite_hidden_servers_; }
|
||||
|
||||
Reference in New Issue
Block a user