feat(ui): large-wallet nudge as a one-time toast + clickable alert

Extend the wallet-bloat warning beyond the Settings banner: when wallet.dat
first crosses 500 MB (full-node, synced), fire a one-time warning toast plus a
clickable "Consolidate notes…" entry in the bell/alerts panel that opens Merge
to Address. The persisted large_wallet_warned flag keeps it once-only and
re-arms if the file later shrinks back under the threshold.

- AlertRecord gains an optional onClick + actionHint; Notifications::action()
  pushes a toast and a clickable history entry. renderAlertHistoryPanel() now
  renders the accent action link (under the message) and measures true content
  height so wrapped messages + the link aren't clipped.
- App::maybeWarnLargeWallet() (mirrors maybeRemindSeedBackup) runs once per
  launch from update(); reuses the existing wallet_size_warn/consolidate strings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-20 18:09:36 -05:00
co-authored by Claude Opus 4.8
parent 6d26ccd0ed
commit 5daf2d83b6
6 changed files with 83 additions and 6 deletions
+29 -4
View File
@@ -878,6 +878,9 @@ void App::update()
// (a prior/unwitnessed salvage likely moved the coins into a wallet.<ts>.bak).
maybeWarnEmptyWalletWithFundedSiblings();
// One-time nudge if wallet.dat has bloated past the threshold (toast + clickable alert → consolidate).
maybeWarnLargeWallet();
// Classify the wallet's mnemonic status (once per connect) so the Migrate-to-seed button can
// glow for a legacy, pre-seed-phrase wallet.
probeWalletSeedStatus();
@@ -2421,10 +2424,19 @@ void App::renderAlertHistoryPanel()
return;
}
// Scrollable list, newest first. Height adapts to the entry count but caps so a busy session
// scrolls inside the panel instead of blowing past the popup's max height.
const float perEntry = txtF->LegacySize * 2.0f + 14.0f * dp; // message line + time line + spacing
const float listH = std::min(300.0f * dp, static_cast<float>(hist.size()) * perEntry);
// Scrollable list, newest first. Measure the TRUE content height so wrapped (multi-line) messages
// and optional action links aren't clipped by an under-estimate; cap so a busy session scrolls
// inside the panel instead of blowing past the popup's max height.
const float msgWrapW = std::max(40.0f * dp, innerW - 2.0f * padX - icoF->LegacySize - 6.0f * dp);
float contentH = 0.0f;
for (const auto& a : hist) {
const float msgH = txtF->CalcTextSizeA(txtF->LegacySize, FLT_MAX, msgWrapW, a.message.c_str()).y;
contentH += std::max(msgH, static_cast<float>(icoF->LegacySize)); // icon + wrapped message
contentH += txtF->LegacySize; // relative-age line
if (a.onClick && !a.actionHint.empty()) contentH += txtF->LegacySize; // action-link line
contentH += 8.0f * dp; // inter-entry spacing
}
const float listH = std::min(300.0f * dp, contentH);
ImGui::BeginChild("##AlertRows", ImVec2(0, listH), false);
int idx = 0;
for (auto it = hist.rbegin(); it != hist.rend(); ++it, ++idx) {
@@ -2452,6 +2464,19 @@ void App::renderAlertHistoryPanel()
ImGui::TextWrapped("%s", a.message.c_str());
ImGui::PopTextWrapPos();
ImGui::PopStyleColor();
// Optional clickable action (accent link), directly under the message so it stays prominent.
if (a.onClick && !a.actionHint.empty()) {
ImGui::SetCursorPosX(padX + icoF->LegacySize + 6.0f * dp);
ImGui::PushStyleColor(ImGuiCol_Text, m::Primary());
ImGui::TextUnformatted(a.actionHint.c_str());
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
const ImVec2 lmn = ImGui::GetItemRectMin(), lmx = ImGui::GetItemRectMax();
ImGui::GetWindowDrawList()->AddLine(ImVec2(lmn.x, lmx.y), ImVec2(lmx.x, lmx.y), m::Primary());
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
if (ImGui::IsItemClicked()) { a.onClick(); ImGui::CloseCurrentPopup(); }
}
// Relative age, dim, indented under the message.
ImGui::SetCursorPosX(padX + icoF->LegacySize + 6.0f * dp);
ImGui::PushStyleColor(ImGuiCol_Text, m::OnSurfaceDisabled());