fix(startup): surface a "taking too long" notice when the daemon won't come up

F3: the daemon connect loop retried forever with only an animated spinner when the
daemon was reachable-but-never-ready (stuck in RPC warmup / -28, or an external daemon
that never finishes init) -- no error, no guidance, no escape. It now stamps
connect_stall_since_ the moment the daemon first goes "reachable but not ready" (the
warmup branch + applyDaemonInitStatus) and clears it on connect / disconnect /
warmup-complete. A pure, unit-testable util::connectHasStalled() helper (new
util/connect_stall.h, 45s default from ui.toml [screens.loading].stall-timeout-sec)
drives a "Taking longer than expected" notice in renderLoadingOverlay(): a title, a
reassuring body with elapsed seconds, and a full-node hint to Settings > Restart Daemon
or the Console. The background retry keeps running underneath, so the notice self-clears
the instant it connects. Guarded off while the daemon is in State::Error (that case is
owned by the existing crash-count hint).

The overlay is a pure draw-list layer with no interactive widgets, so this follows the
existing crash-hint idiom (guidance text, not injected buttons); the stalled state is
computed locally in the overlay, so the only new App member is connect_stall_since_.

Adds testConnectHasStalled to test_phase4.cpp and three i18n keys to i18n.cpp (English
source of truth; the res/lang/*.json back-fill is deferred to a single
add_missing_translations.py run at the end of the batch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 11:33:26 -05:00
parent 2675b8ab93
commit eb69e491b9
8 changed files with 113 additions and 2 deletions

27
src/util/connect_stall.h Normal file
View File

@@ -0,0 +1,27 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
namespace dragonx {
namespace util {
// Default "taking longer than expected" threshold (seconds) for the daemon connect loop,
// overridable via ui.toml [screens.loading].stall-timeout-sec. Kept as a free function with
// no ImGui/App dependency so it is directly unit-testable from tests/test_phase4.cpp.
constexpr float kConnectStallDefaultSeconds = 45.0f;
// True once a daemon that is reachable-but-not-ready has stayed that way past the threshold.
// stallSince : timestamp (same clock as `now`) when the stall began; <= 0 means "not stalling".
// now : current time in the same units as stallSince.
// thresholdSec: how long to wait before considering it stalled; <= 0 disables the feature.
inline bool connectHasStalled(double stallSince, double now, float thresholdSec)
{
if (stallSince <= 0.0) return false; // not currently in a stall-tracked state
if (thresholdSec <= 0.0f) return false; // 0/negative disables the notice defensively
return (now - stallSince) >= static_cast<double>(thresholdSec);
}
} // namespace util
} // namespace dragonx

View File

@@ -1318,6 +1318,9 @@ void I18n::loadBuiltinEnglish()
strings_["sb_sapling_not_found"] = "Sapling parameters not found.";
strings_["sb_daemon_extract_failed"] = "Failed to write daemon files — check free disk space and permissions.";
strings_["sb_daemon_files_failed"] = "Failed to write daemon files to %s — check free disk space and permissions.";
strings_["loading_stall_title"] = "Taking longer than expected";
strings_["loading_stall_body"] = "The daemon has been initializing for %.0fs. This can be normal after an update or on first launch (loading the block index or rescanning) — it will connect automatically once ready.";
strings_["loading_stall_hint"] = "Still stuck? Open Settings and use Restart Daemon, or check the Console for details.";
strings_["sb_dragonxd_running"] = "dragonxd running";
strings_["sb_dragonxd_stopping"] = "Stopping dragonxd...";
strings_["sb_dragonxd_stopped"] = "dragonxd stopped";