feat(ui): say "Node busy" instead of "Launching dragonxd"; honest quit copy

A node rebuilding note witnesses for an hour was shown as "Starting
DragonX node... Launching dragonxd", which invited closing it.

- Overlay, a new NodeBusy banner and the status bar now say what the node
  is doing (rebuilding witnesses / catching up on blocks / up but not
  answering), with the elapsed time and Phase-1 block progress. A rebuild
  during startup warm-up replaces the stale "-28 Activating best chain"
  text, and the overlay re-applies its text once a second instead of only
  on a connect attempt (30s apart while the node is busy).
- Quit prompt: elapsed time, "Keep node running & quit" is the default,
  "Stop anyway & quit" becomes "Stop node & wait" (it no longer kills).
- A reinstall skipped because the node is still running now notifies.
- Status bar: don't repeat the warm-up status on the right, and don't
  print "Connected" there in non-English locales.
- i18n: 31 new keys in all 8 languages; 10 dead force-quit/old-modal keys
  removed; CJK subset rebuilt for the new glyphs.
- Tests: chooseDaemonStatus, NodeBusy banner priority, and every new key
  present in all 8 JSONs with matching printf conversions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 10:26:32 -05:00
co-authored by Claude Opus 5.5
parent 0f25c67600
commit 1215ded1a0
17 changed files with 822 additions and 133 deletions
+23
View File
@@ -1077,6 +1077,7 @@ private:
// Set by the deleteBlockchainData worker (item count); the main loop surfaces a completion toast
// and resets it to -1. Atomic because the worker thread writes it and the UI thread reads/clears it.
std::atomic<int> pending_delete_result_{-1};
std::atomic<bool> pending_reinstall_node_busy_{false}; // reinstall gave up: the old node never exited
// Encryption state check timeout
float encryption_check_timer_ = 0.0f;
@@ -1186,6 +1187,17 @@ private:
void abortRpcBusyCanary();
void feedDaemonBusy(const std::string& chunk);
void resetDaemonBusy();
std::chrono::steady_clock::time_point busy_first_seen_{}; // when the tracker last turned busy (UI thread)
// What to tell the user while the node is busy (overlay, banner, quit prompt). busy=false: it isn't.
struct NodeBusyText {
bool busy = false;
bool rebuilding = false;
std::string title; // e.g. "Node busy: rebuilding note witnesses — do not close"
std::string detail; // running time, what it's doing, progress when the node reports any
std::string elapsed; // "12:03"
std::string compact; // one line for the banner: "Running for 12:03 · Reading blocks: 45438 / 62583"
};
NodeBusyText nodeBusyText() const;
// Export/Import state
char export_result_[256] = {0}; // SECRET exported key — fixed buffer so it can be sodium_memzero'd
@@ -1574,6 +1586,17 @@ private:
// `reachableButBusy` is true when the probe connected but got no RPC reply (a timeout),
// false when the daemon is merely launching (not bound yet). Returns the status title.
std::string applyDaemonInitStatus(bool reachableButBusy);
// The overlay text applyDaemonInitStatus last produced. It runs only on a connect failure, which a busy
// node backs off to every 30s, so update() re-applies it once a second (the elapsed time and the
// node's own progress tick) for as long as nothing else has replaced the status.
std::string init_status_title_;
bool init_status_busy_ = false;
double status_refresh_at_ = 0.0;
// The warm-up overlay text, from the node's last -28 message — unless its own log says it is rebuilding
// witnesses: a startup rebuild runs under "Activating best chain...", which hides the step users must
// not interrupt. update() re-applies it once a second too (see init_status_title_).
void applyWarmupText(const std::string& rpcMessage);
std::string warmup_rpc_message_;
// Tear down a connection that died mid-session (daemon crash / restart / dropped
// socket) so update()'s reconnect branch re-enters tryConnect(). Unlike onDisconnected
// alone, this also rpc_->disconnect()s so rpc_->isConnected() actually flips to false.