fix(ui): keep a chosen send-from address from auto-select overwrite

The Send tab auto-selected the highest-balance address on first load.
When another tab had already pre-filled the source with "Send from this
address", that first-load pick overwrote the user's choice, and the
dropdown's selected index could drift out of step with the address that
would actually be debited when the address list changed.

- The send-from string is now the single source of truth. The one-shot
  auto-pick only fills an empty source and never replaces one the user
  or another tab set, and the list index is re-derived from the string
  each frame so it cannot desync.
- An explicit "Send from this address" choice latches the auto-pick off,
  so the first visit cannot undo it.
- A chosen source the dropdown does not list (zero balance, view-only,
  or the list still loading) now previews the address that will be
  debited instead of the "Select a source" placeholder.

User-visible: a send-from address chosen from another tab survives the
first load and later refresh ticks.

Tests: the source resolver keeps a pre-fill on first visit, fills an
empty source with the best spendable address, keeps a source missing
from a short list with index -1, latches nothing while disconnected or
with an empty list, and re-derives a stale index from the string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 21:42:23 -05:00
co-authored by Claude Opus 4.8
parent 579accd0c6
commit d4339be94b
4 changed files with 89 additions and 9 deletions
+26
View File
@@ -43,6 +43,32 @@ int bestSpendableAddressIndex(const std::vector<AddressInfo>& addresses)
return bestIndex;
}
int addressIndexByString(const std::vector<AddressInfo>& addresses, const std::string& address)
{
if (address.empty()) return -1;
for (size_t i = 0; i < addresses.size(); ++i) {
if (addresses[i].address == address) return static_cast<int>(i);
}
return -1;
}
SourceSelection resolveSourceSelection(const std::vector<AddressInfo>& addresses, const std::string& current,
bool autoLatched, bool connected)
{
SourceSelection r;
r.address = current;
r.autoLatched = autoLatched;
r.index = addressIndexByString(addresses, current);
if (!autoLatched && connected && !addresses.empty()) {
if (current.empty()) { // only ever fill an empty source
const int best = bestSpendableAddressIndex(addresses);
if (best >= 0) { r.address = addresses[best].address; r.index = best; }
}
r.autoLatched = true; // one shot, whether or not it picked
}
return r;
}
std::string TransactionInfo::getTimeString() const
{
if (timestamp == 0) return "Unknown";