feat(seed,console): persistent re-nudging seed backup; confirm destructive console commands

- Seed backup: the one-time 12s toast that marked itself "reminded" the instant
  it fired (so a funded, unbacked wallet was never nudged again) is replaced by
  an actionable alert with a "Back up now" link that opens the backup dialog and
  persists in the bell panel. "Reminded" now means the user actually viewed the
  phrase (set from the backup dialog), so the nudge re-fires each session until
  they do, and escalates to a warning once a balance exists.
- Console: fund-moving / key-revealing commands (dumpprivkey, z_exportkey,
  z_sendmany, z_mergetoaddress, …) now require a confirming re-entry when typed
  or PASTED, honoring the command reference's `destructive` flag that was
  previously enforced only by the reference dialog's Run button. (Output
  masking of key dumps in the scrollback is still a follow-up.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 16:46:09 -05:00
co-authored by Claude Opus 4.8
parent e27d88eca4
commit 643ac7daef
6 changed files with 61 additions and 6 deletions
+20 -6
View File
@@ -5833,7 +5833,8 @@ void App::maybeRemindSeedBackup()
if (capture_mode_) return; // no live ops during a UI sweep
if (window_background_) return; // when someone can see it
if (lite_wallet_) return; // lite has its own seed UX
if (!settings_ || settings_->getSeedBackupReminded()) return;
if (!settings_ || settings_->getSeedBackupReminded()) return; // already backed up (saw the phrase)
if (seed_backup_nudged_this_session_) return; // already nudged/classified this run
if (!state_.connected || !state_.encryption_state_known) return;
if (state_.isLocked()) return; // wait until unlocked to read it
if (seed_backup_reminder_in_flight_) return;
@@ -5844,16 +5845,29 @@ void App::maybeRemindSeedBackup()
exportSeedPhrase([this](bool ok, bool noMnemonic, const std::string& /*phrase*/,
const std::string& /*error*/) {
seed_backup_reminder_in_flight_ = false;
// Only mark "reminded" once we have a definitive answer, so a transient failure retries
// next session rather than silently suppressing the reminder forever.
if (ok || noMnemonic) {
if (ok) {
// The wallet HAS a mnemonic and the user hasn't backed it up yet. Don't mark "reminded"
// here — that permanent flag now means "the user has seen the phrase" (set from the backup
// dialog), so leaving it clear re-nudges next session until they actually do. Nudge once
// per session as an ACTIONABLE alert (survives in the bell panel with a "Back up now" link),
// not a 12s toast that's gone before it's read. Escalate to a warning once funds exist.
seed_backup_nudged_this_session_ = true;
const bool hasFunds = state_.totalBalance > 0.0;
ui::Notifications::instance().action(
TR("seed_backup_reminder"),
hasFunds ? ui::NotificationType::Warning : ui::NotificationType::Info,
[this]() { showSeedBackupDialog(); },
TR("seed_backup_reminder_action"),
hasFunds ? 15.0f : 12.0f);
} else if (noMnemonic) {
// A legacy (non-mnemonic) wallet has nothing to back up this way — suppress for good.
seed_backup_nudged_this_session_ = true;
settings_->setSeedBackupReminded(true);
settings_->save();
} else {
// Inconclusive (busy/old daemon) — retry later this session rather than suppressing forever.
seed_backup_reminder_retry_at_ = std::chrono::steady_clock::now() + std::chrono::minutes(5);
}
if (ok)
ui::Notifications::instance().info(TR("seed_backup_reminder"), 12.0f);
});
}