feat(recovery): redesign the wallet auto-recovery flow

Turns the "Daemon Error + raw log dump" moment into one calm, honest recovery
dialog plus a recovery-aware rescan screen. Presentation + orchestration only —
the file-safety logic in rebuildWalletDatabase()/restoreOriginalWallet() (source
selection, verify-before-swap, copy/rename-never-delete, .bak) is unchanged.

- One authoritative dialog with a phase machine Offer -> Working -> Done/Failed.
  The duplicate in-overlay recovery card, the untranslated red "Daemon Error"
  heading, and the raw daemon-log dump are gone for the recovery case (they stay
  for genuine, unrelated crashes).
- Offer is a choice-cards layout: "Repair automatically" (recommended, accent-
  tinted) vs "Restore original", side by side; the rare actions ("Show me the
  files", "Decide later") and a plain-language "What happens to my files?" sit in
  a quiet footer. When the rebuild helper is missing, it collapses to a single
  Restore card — never a dead end.
- Post-repair rescan shows a calm "Finishing your wallet repair" screen with
  elapsed time + the growing wallet size, instead of "RPC timeout / taking longer
  than expected / restart daemon"; the daemon-crash toast is suppressed and the
  detection toast is downgraded from red to info.
- Fixes a confirmed dead-end: if a repair succeeds but the restarted daemon then
  crashes for a *different* reason (block index, disk, OOM), the recovery flags
  now clear (in tryConnect + onConnected) so it surfaces as a normal daemon
  failure instead of freezing forever on a reassuring "don't restart" screen.
- Clickable "Wallet repair available" status-bar chip for re-entry.

The same app.cpp changes HiDPI-harden the surfaces the recovery flow lives on:
the status-bar and loading-overlay hand-drawn geometry are multiplied by dpiScale
(they rendered native-size and clipped at HiDPI / font_scale>1), the loading-
overlay status text wraps instead of running off both edges, and the node-status
banner floors its height to its DPI-baked font so the title can't clip off the top.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-10 22:11:26 -05:00
parent 001e85ac1a
commit 5edbe8a276
4 changed files with 604 additions and 130 deletions

View File

@@ -906,6 +906,20 @@ private:
bool wallet_auto_recovered_ = false; // a salvage happened this session
bool wallet_auto_recovered_warned_ = false; // guard: only surface it once per session
bool show_wallet_recovered_dialog_ = false; // auto-shown warning dialog
// The recovery dialog is the ONE authoritative surface: it stays open through the async rebuild/
// restore, driven Offer → Working → Done/Failed (pumpWalletRestore sets the outcome). Presentation
// only — the fund-safety file ops in rebuildWalletDatabase()/restoreOriginalWallet() are unchanged.
enum class RecoveryPhase { Offer, Working, Done, Failed };
RecoveryPhase recovery_phase_ = RecoveryPhase::Offer;
int recovery_outcome_sev_ = 0; // 0 ok / 1 warn / 2 error, set at Done/Failed
std::string recovery_outcome_msg_; // honest result string for the Done/Failed body
bool recovery_last_action_rebuild_ = false; // which handler ran (for "try the other option")
// After a successful repair the daemon restarts with a full rescan — minutes long, and it won't
// answer RPC yet. This makes the loading overlay show a calm "finishing your wallet repair" screen
// (instead of the generic "daemon stuck / RPC timeout / restart daemon" text) and suppresses the
// daemon-crash toast. Set on repair success; cleared on connect (onConnected).
bool post_recovery_rescan_ = false;
double post_recovery_rescan_since_ = 0.0; // stamped on first overlay frame (ImGui::GetTime)
// "Restore original wallet" background op: worker sets these under the mutex, pumpWalletRestore()
// (main thread) shows the result. 0 = success, 1 = warning, 2 = error.
std::mutex wallet_restore_mutex_;