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:
50
src/app.cpp
50
src/app.cpp
@@ -69,6 +69,7 @@
|
||||
#include "ui/widgets/copy_field.h"
|
||||
#include "ui/notifications.h"
|
||||
#include "util/i18n.h"
|
||||
#include "util/connect_stall.h"
|
||||
#include "util/platform.h"
|
||||
#include "util/text_format.h"
|
||||
#include "util/payment_uri.h"
|
||||
@@ -5296,6 +5297,55 @@ void App::renderLoadingOverlay(float contentH)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// 3d. "Taking longer than expected" notice — the daemon is reachable/launching but
|
||||
// hasn't become ready within the stall threshold. The connect loop keeps retrying
|
||||
// underneath (this notice clears itself the instant it connects); it just stops the
|
||||
// user staring at a silent spinner forever. Guarded off while the daemon is in the
|
||||
// Error state — that case is owned by the crash block (3c) above.
|
||||
// -------------------------------------------------------------------
|
||||
if (connect_stall_since_ > 0.0 &&
|
||||
!(daemon_controller_ &&
|
||||
daemon_controller_->state() == daemon::EmbeddedDaemon::State::Error) &&
|
||||
util::connectHasStalled(connect_stall_since_, ImGui::GetTime(),
|
||||
loadElem("stall-timeout-sec", util::kConnectStallDefaultSeconds))) {
|
||||
curY += gap;
|
||||
ImFont* bodyFont2 = Type().body2();
|
||||
if (!bodyFont2) bodyFont2 = ImGui::GetFont();
|
||||
ImFont* capFont = Type().caption();
|
||||
if (!capFont) capFont = ImGui::GetFont();
|
||||
|
||||
// Title
|
||||
const char* title = TR("loading_stall_title");
|
||||
ImVec2 ts = bodyFont2->CalcTextSizeA(bodyFont2->LegacySize, FLT_MAX, 0.0f, title);
|
||||
dl->AddText(bodyFont2, bodyFont2->LegacySize,
|
||||
ImVec2(wp.x + cx - ts.x * 0.5f, curY),
|
||||
IM_COL32(255, 210, 90, 235), title);
|
||||
curY += ts.y + gap * 0.5f;
|
||||
|
||||
// Body (wrapped) — reassure + show elapsed seconds
|
||||
char stallBody[256];
|
||||
snprintf(stallBody, sizeof(stallBody), TR("loading_stall_body"),
|
||||
(float)(ImGui::GetTime() - connect_stall_since_));
|
||||
float wrapW = ws.x * 0.8f;
|
||||
if (wrapW > 640.0f) wrapW = 640.0f;
|
||||
ImVec2 bs = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, wrapW, stallBody);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(wp.x + cx - wrapW * 0.5f, curY),
|
||||
IM_COL32(200, 200, 200, 210), stallBody, nullptr, wrapW);
|
||||
curY += bs.y + gap * 0.5f;
|
||||
|
||||
// Actionable guidance (full-node only — lite has no daemon to restart)
|
||||
if (supportsFullNodeLifecycleActions()) {
|
||||
const char* hint = TR("loading_stall_hint");
|
||||
ImVec2 hs = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0.0f, hint);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(wp.x + cx - hs.x * 0.5f, curY),
|
||||
IM_COL32(180, 180, 180, 190), hint);
|
||||
curY += hs.y + gap;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// 4. Daemon output snippet (last few lines, if embedded)
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user