feat(balance): show a "catching up — funds are safe" strip while syncing

A syncing node skips the balance RPC, so the balance card paints a bare
0.00000000 / $-.-- that reads as lost funds after an update (a documented
support trap). Add one shared notice at the top of the Balance tab — above
every layout — shown only for the normal catching-up case (connected, network
tip known, still verifying, not a rescan/witness rebuild). Offline, no-peers,
a busy node and rescans stay the node-status banner's job, so the two never
stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 16:48:33 -05:00
co-authored by Claude Opus 4.8
parent 643ac7daef
commit 4a8d07edce
2 changed files with 34 additions and 0 deletions
+33
View File
@@ -205,6 +205,39 @@ void RenderBalanceTab(App* app)
} }
} }
// Shared "still catching up" strip, above whichever layout renders, so a syncing node's 0 / stale
// balance is never mistaken for lost funds (the documented "my coins vanished after an update"
// trap). Scoped to the NORMAL catching-up case — connected, a network tip is known, still verifying
// blocks, and not in a rescan / witness rebuild. Offline, no-peers, a busy node and rescans are
// already surfaced by the node-status banner, so this never stacks a second banner on them.
{
namespace m = material;
const auto& st = app->state();
if (st.connected && st.sync.tip_known && st.sync.syncing &&
!st.sync.building_witnesses && !st.sync.rescanning) {
ImFont* ico = m::Type().iconSmall();
ImFont* txt = m::Type().body2();
const float padX = ui::Layout::spacingMd();
const float padY = ui::Layout::spacingSm();
const float availW = ImGui::GetContentRegionAvail().x;
char line[192];
snprintf(line, sizeof(line), TR("balance_catchup_notice"),
static_cast<float>(st.sync.verification_progress) * 100.0f);
const float iconW = ico->LegacySize + padX * 0.5f;
const float wrapW = std::max(1.0f, availW - padX * 2.0f - iconW);
ImVec2 tsz = txt->CalcTextSizeA(txt->LegacySize, FLT_MAX, wrapW, line);
const float h = std::max(ico->LegacySize, tsz.y) + padY * 2.0f;
ImVec2 p0 = ImGui::GetCursorScreenPos();
ImDrawList* dl = ImGui::GetWindowDrawList();
dl->AddRectFilled(p0, ImVec2(p0.x + availW, p0.y + h),
m::WithAlphaF(m::Warning(), 0.13f), ui::Layout::glassRounding());
dl->AddText(ico, ico->LegacySize, ImVec2(p0.x + padX, p0.y + padY), m::Warning(), ICON_MD_SYNC);
dl->AddText(txt, txt->LegacySize, ImVec2(p0.x + padX + iconW, p0.y + padY),
m::OnSurfaceMedium(), line, nullptr, wrapW);
ImGui::Dummy(ImVec2(availW, h + ui::Layout::spacingSm()));
}
}
// Dispatch by string ID // Dispatch by string ID
for (const auto& entry : s_layoutDispatch) { for (const auto& entry : s_layoutDispatch) {
if (layoutId == entry.id) { if (layoutId == entry.id) {
+1
View File
@@ -1237,6 +1237,7 @@ void I18n::loadBuiltinEnglish()
strings_["balance_shielded_fmt"] = "Shielded: %.8f"; strings_["balance_shielded_fmt"] = "Shielded: %.8f";
strings_["total_balance_label"] = "Total Balance"; strings_["total_balance_label"] = "Total Balance";
strings_["balance_syncing_pct"] = "Syncing %.1f%%"; strings_["balance_syncing_pct"] = "Syncing %.1f%%";
strings_["balance_catchup_notice"] = "Catching up to the network (%.0f%%) — your balance will finish updating when sync completes. Your funds are safe.";
strings_["balance_mining_rate"] = "Mining %s"; strings_["balance_mining_rate"] = "Mining %s";
strings_["balance_layout_switched"] = "Layout: %s"; strings_["balance_layout_switched"] = "Layout: %s";
strings_["byte_count_fmt"] = "%zu / %zu bytes"; strings_["byte_count_fmt"] = "%zu / %zu bytes";