feat(ui): friendly RPC errors + pause-on-hover/ellipsis toasts
Two of the systemic error-surfacing foundations from the UX audit: - util::friendlyRpcError() maps the common raw daemon/JSON-RPC errors (warming up / -28, locked / -13, method-not-found / -32601, insufficient funds, unreachable node) to short translated guidance with a next step, falling back to the trimmed raw text. Wired into the send (money) path: the toast shows the friendly cause, the overlay keeps the raw text below it for diagnostics. The helper is reusable for the remaining raw-error sites. - Toasts now pause their auto-dismiss while hovered and ellipsize over-long text with the full message in a hover tooltip, instead of hard-clipping it mid-word (durations were already lengthened earlier). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+37
-1
@@ -9,6 +9,7 @@
|
||||
#include <fstream>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -1911,6 +1912,13 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["console_not_connected_lite"] = "Error: no wallet open";
|
||||
strings_["console_stop_confirm_node"] = "'stop' will shut down the node and disconnect the wallet. Type 'stop' again to confirm.";
|
||||
strings_["console_destructive_confirm"] = "This command can move funds or reveal secret keys. Never run a command someone else gave you. Run the exact same command again to confirm.";
|
||||
// Friendly mappings for common daemon/RPC errors (see util::friendlyRpcError).
|
||||
strings_["err_warming_up"] = "The node is still starting up. Try again in a moment.";
|
||||
strings_["err_wallet_locked"] = "Unlock your wallet first, then try again.";
|
||||
strings_["err_method_missing"] = "Your node doesn't support this action. Updating the daemon may fix it.";
|
||||
strings_["err_insufficient"] = "Not enough spendable balance to cover the amount and fee.";
|
||||
strings_["err_cant_reach"] = "Can't reach the node. Check that it's running and try again.";
|
||||
strings_["err_generic"] = "Something went wrong. Please try again.";
|
||||
strings_["console_capturing_output"] = "Capturing daemon output...";
|
||||
strings_["console_clear"] = "Clear";
|
||||
strings_["console_clear_console"] = "Clear Console";
|
||||
@@ -2935,9 +2943,37 @@ void I18n::registerLanguage(const std::string& code, const std::string& name)
|
||||
for (const auto& lang : available_languages_) {
|
||||
if (lang.first == code) return;
|
||||
}
|
||||
|
||||
|
||||
available_languages_.emplace_back(code, name);
|
||||
}
|
||||
|
||||
std::string friendlyRpcError(const std::string& raw)
|
||||
{
|
||||
// Lowercased copy for case-insensitive matching.
|
||||
std::string lc = raw;
|
||||
for (char& c : lc) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
auto has = [&](const char* needle) { return lc.find(needle) != std::string::npos; };
|
||||
|
||||
// Order matters: the most specific / most common user-facing causes first.
|
||||
if (has("-28") || has("warming up") || has("loading block") || has("initializing") || has("rewinding"))
|
||||
return tr("err_warming_up");
|
||||
if (has("-13") || has("walletpassphrase") || has("wallet is locked") || has("please enter the wallet passphrase"))
|
||||
return tr("err_wallet_locked");
|
||||
if (has("-32601") || has("method not found"))
|
||||
return tr("err_method_missing");
|
||||
if (has("insufficient") || has("not enough funds") || has("no funds"))
|
||||
return tr("err_insufficient");
|
||||
if (has("connection refused") || has("couldn't connect") || has("could not connect") ||
|
||||
has("failed to connect") || has("timed out") || has("timeout") || has("no response"))
|
||||
return tr("err_cant_reach");
|
||||
|
||||
// Nothing recognized — hand back the raw text, trimmed of surrounding whitespace so a stray
|
||||
// newline/indent from the daemon doesn't look like a formatting glitch.
|
||||
const auto b = raw.find_first_not_of(" \t\r\n");
|
||||
if (b == std::string::npos) return tr("err_generic");
|
||||
const auto e = raw.find_last_not_of(" \t\r\n");
|
||||
return raw.substr(b, e - b + 1);
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
@@ -79,6 +79,16 @@ inline const char* tr(const char* key) {
|
||||
return I18n::instance().translate(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Map a raw daemon / JSON-RPC error string to a short, friendly, TRANSLATED explanation.
|
||||
*
|
||||
* Recognizes the common cases a user actually hits — a warming-up node, a locked wallet, an RPC the
|
||||
* daemon is too old to have, insufficient funds, and an unreachable node — and returns plain guidance
|
||||
* with a next step. Falls back to the raw text (trimmed) when nothing matches, so detail is never lost.
|
||||
* Keep the raw text available elsewhere (a Details view / the console) for diagnostics.
|
||||
*/
|
||||
std::string friendlyRpcError(const std::string& raw);
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
|
||||
Reference in New Issue
Block a user