feat(ux): clearer chat delivery, explorer empty/range states, honest settings label

Three next-batch UX-audit fixes:
- Chat delivery: the sending/sent glyph has higher contrast and a tooltip
  ("Sent to the network — arrives after it confirms", so a check isn't mistaken
  for "delivered"); a failed send now carries the real reason (ChatMessage::
  fail_reason, set from onChatBroadcastResult via friendlyRpcError, in-memory
  only) in the "not sent" hover tooltip.
- Explorer: a block height beyond the tip says so ("Block N is beyond the chain
  tip (M)") instead of silently snapping to the top; the empty block list shows
  Not connected / Syncing (N behind) / Loading instead of an endless "Loading".
- Settings: the misleading "Reset to Defaults" button (which only reloads the
  last-saved values) is relabeled "Discard unsaved changes" with accurate copy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 09:20:47 -05:00
co-authored by Claude Opus 4.8
parent a11130769d
commit a792a1ec0c
10 changed files with 75 additions and 15 deletions
+33 -2
View File
@@ -424,7 +424,15 @@ static void performSearch(App* app, const std::string& query) {
try { h = std::stol(query); } catch (...) { return; }
int tip = app->getWalletState().sync.blocks;
if (h < 1) h = 1;
if (tip > 0 && h > tip) h = tip;
if (tip > 0 && h > tip) {
// Say the height is beyond the tip instead of silently snapping to the top of the list.
char msg[128];
snprintf(msg, sizeof(msg), TR("explorer_beyond_tip"),
(int)std::min<long>(h, 2147483647L), tip);
s_search_error = msg;
s_search_target_height = -1; // don't re-anchor; leave the list where it is
return;
}
s_search_target_height = static_cast<int>(h);
s_recent_page = 0;
s_search_error.clear();
@@ -939,7 +947,30 @@ static void renderRecentBlocks(App* app, float availWidth) {
if (blocks.empty()) {
ImGui::SetCursorPosY(Layout::spacingMd());
ImGui::TextDisabled("%s", fuzzyMode ? TR("explorer_no_results") : TR("loading"));
// Say WHY the list is empty — offline, syncing, or a genuine search miss — instead of an
// endless "Loading" that also shows when the node is disconnected or still catching up.
const auto& st = app->getWalletState();
auto* erpc = app->rpc();
char emptyBuf[128];
const char* msg = TR("loading");
ImU32 col = OnSurfaceMedium();
if (fuzzyMode) {
msg = TR("explorer_no_results");
} else if (!st.connected || !erpc || !erpc->isConnected()) {
msg = TR("explorer_empty_not_connected"); col = Warning();
} else if (st.sync.syncing) {
const int behind = st.sync.headers - st.sync.blocks;
if (st.sync.tip_known && behind > 0) {
snprintf(emptyBuf, sizeof(emptyBuf), TR("explorer_empty_syncing"), behind);
msg = emptyBuf;
} else {
msg = TR("explorer_empty_syncing_unknown");
}
col = Warning();
}
ImGui::PushStyleColor(ImGuiCol_Text, col);
ImGui::TextUnformatted(msg);
ImGui::PopStyleColor();
} else {
for (size_t i = 0; i < blocks.size(); i++) {
const auto& row = blocks[i];