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
+30
View File
@@ -1537,6 +1537,25 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec)
focus_input_pending_ = false;
}
namespace {
// Is `firstLower` (a lowercased command name) flagged destructive in the command reference? The
// `destructive` flag was previously consulted only by the reference dialog's Run button; this lets the
// typed/pasted path honor it too. Scans the full-node or lite command set per the active executor.
bool IsDestructiveConsoleCommand(const std::string& firstLower, bool fullNode)
{
const auto& cats = fullNode ? consoleCommandCategories() : liteConsoleCommandCategories();
for (const auto& cat : cats) {
for (int i = 0; i < cat.count; ++i) {
std::string n = cat.commands[i].name ? cat.commands[i].name : "";
std::transform(n.begin(), n.end(), n.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
if (n == firstLower) return cat.commands[i].destructive;
}
}
return false;
}
} // namespace
bool ConsoleTab::submitConsoleCommand(ConsoleCommandExecutor& exec, const std::string& cmd)
{
if (cmd.empty()) return false;
@@ -1561,6 +1580,8 @@ bool ConsoleTab::submitConsoleCommand(ConsoleCommandExecutor& exec, const std::s
// pending state. Member (not a function-local static) so clear() can also cancel it — otherwise a
// toolbar/context-menu clear between the two 'stop's would leave a stale arm and skip the warning.
if (first != "stop") stop_confirm_pending_ = false;
// A pending destructive-command confirmation is armed on the EXACT line; any different input cancels it.
if (destructive_confirm_cmd_ != cmd) destructive_confirm_cmd_.clear();
auto add = [this](const std::string& l, ConsoleChannel c) { addLine(l, c); };
if (first == "clear" || first == "cls") {
// View-only clear — NEVER forwarded (the lite backend's `clear` wipes tx history).
@@ -1586,7 +1607,15 @@ bool ConsoleTab::submitConsoleCommand(ConsoleCommandExecutor& exec, const std::s
// Full node connects to a daemon; the lite backend opens a wallet — word the error per variant.
addLine(exec.hasRpcReference() ? TR("console_not_connected") : TR("console_not_connected_lite"),
ConsoleChannel::Error);
} else if (IsDestructiveConsoleCommand(first, exec.hasRpcReference()) &&
destructive_confirm_cmd_ != cmd) {
// Fund-moving / key-revealing commands (dumpprivkey, z_sendmany, z_mergetoaddress, …) get the
// same two-step confirm as 'stop' when typed or PASTED directly — previously the destructive
// flag was honored only by the reference dialog, so a pasted command ran instantly.
destructive_confirm_cmd_ = cmd;
addLine(TR("console_destructive_confirm"), ConsoleChannel::Warning);
} else {
destructive_confirm_cmd_.clear();
exec.submit(cmd);
}
return true;
@@ -2185,6 +2214,7 @@ void ConsoleTab::clear()
wrap_cache_.reset();
vis_flat_valid_ = false;
stop_confirm_pending_ = false; // a pending 'stop' confirmation is cancelled by clearing
destructive_confirm_cmd_.clear(); // likewise cancel a pending destructive-command confirmation
addLine(TR("console_cleared"), ConsoleChannel::Info);
}