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:
@@ -246,15 +246,16 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("send_sending_from"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Auto-select the address with the largest balance on first load
|
||||
if (!s_auto_selected && app->isConnected() && !state.addresses.empty()) {
|
||||
int bestIdx = bestSpendableAddressIndex(state.addresses);
|
||||
if (bestIdx >= 0) {
|
||||
s_selected_from_idx = bestIdx;
|
||||
snprintf(s_from_address, sizeof(s_from_address), "%s",
|
||||
state.addresses[bestIdx].address.c_str());
|
||||
}
|
||||
s_auto_selected = true;
|
||||
// s_from_address is the single source of truth (it is what gets debited). Auto-select the address with the
|
||||
// largest balance on first load ONLY when no source is set yet - never over a "Send from this address"
|
||||
// pre-fill from another tab - and re-derive the list index from the string every frame so it can't desync
|
||||
// when the address list changes.
|
||||
{
|
||||
const auto sel = resolveSourceSelection(state.addresses, s_from_address, s_auto_selected, app->isConnected());
|
||||
if (sel.address != s_from_address)
|
||||
snprintf(s_from_address, sizeof(s_from_address), "%s", sel.address.c_str());
|
||||
s_auto_selected = sel.autoLatched;
|
||||
s_selected_from_idx = sel.index;
|
||||
}
|
||||
|
||||
// Build preview string for selected address
|
||||
@@ -271,6 +272,11 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
tag, trunc.c_str(),
|
||||
FormatSourceBalance(addr.balance, addr.spendableBalance).c_str());
|
||||
s_source_preview = buf;
|
||||
} else if (s_from_address[0] != '\0') {
|
||||
// A pre-filled source that the dropdown list doesn't carry (zero-balance / view-only / list still
|
||||
// loading): show the address that WILL be debited rather than the "Select a source" placeholder.
|
||||
s_source_preview = util::truncateMiddle(s_from_address,
|
||||
static_cast<int>(std::max(S.drawElement("tabs.send", "addr-preview-trunc-min").size, width / S.drawElement("tabs.send", "addr-preview-trunc-divisor").size)));
|
||||
} else {
|
||||
s_source_preview = TR("send_select_source");
|
||||
}
|
||||
@@ -1696,6 +1702,7 @@ void SetSendFromAddress(const std::string& address)
|
||||
strncpy(s_from_address, address.c_str(), sizeof(s_from_address) - 1);
|
||||
s_from_address[sizeof(s_from_address) - 1] = '\0';
|
||||
s_selected_from_idx = -1;
|
||||
s_auto_selected = true; // an explicit choice: the first-visit auto-pick must never replace it
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
Reference in New Issue
Block a user