feat(diagnostics): refresh-staleness badge on the Total Balance card (W6-2)
When the wallet is connected but the balance has quietly stopped refreshing — a busy
daemon can fail z_gettotalbalance without dropping the whole connection (only *both*
core RPCs failing 3x triggers a disconnect) — the old number sits on screen while the
node-status banner stays hidden. The Total Balance card now shows a small pill on its
status line ("Updated 2m ago", amber, escalating to red past 3 min) so the stale value
isn't silently trusted; hovering explains it and points at the node connection.
No refresh-path changes: WalletState::last_balance_update is already stamped only on a
successful fetch (network_refresh_service.cpp), so the badge reads it and computes age
against the same std::time clock via util::formatTimeAgoShort. The decision is a pure,
unit-tested helper (ui/staleness_badge.h::evaluateStalenessBadge, 45s/180s thresholds)
gated on connected so it never contradicts the banner.
Closes P2 (5/5). Build-clean; ctest 1/1 (adds testStalenessBadge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
#include "ui/windows/mining_pool_panel.h"
|
||||
#include "ui/windows/mining_tab_helpers.h"
|
||||
#include "ui/node_status_banner.h"
|
||||
#include "ui/staleness_badge.h"
|
||||
#include "util/address_validation.h"
|
||||
#include "util/amount_format.h"
|
||||
#include "util/payment_uri.h"
|
||||
@@ -2623,6 +2624,37 @@ void testNodeStatusBanner()
|
||||
}
|
||||
}
|
||||
|
||||
void testStalenessBadge()
|
||||
{
|
||||
using namespace dragonx::ui;
|
||||
const int64_t now = 1'000'000;
|
||||
|
||||
// Disconnected → banner's job, never a badge.
|
||||
EXPECT_TRUE(!evaluateStalenessBadge(now - 999, now, /*connected=*/false).show);
|
||||
// Never updated this session (0 stamp, e.g. fresh start / reset on disconnect) → nothing.
|
||||
EXPECT_TRUE(!evaluateStalenessBadge(0, now, true).show);
|
||||
// Fresh (just under the threshold) → no badge.
|
||||
EXPECT_TRUE(!evaluateStalenessBadge(now - (kStaleAfterSeconds - 1), now, true).show);
|
||||
// At the threshold → amber badge, age reported.
|
||||
{
|
||||
StalenessBadge b = evaluateStalenessBadge(now - kStaleAfterSeconds, now, true);
|
||||
EXPECT_TRUE(b.show);
|
||||
EXPECT_TRUE(b.severity == StalenessSeverity::Warning);
|
||||
EXPECT_EQ(b.seconds_old, (int64_t)kStaleAfterSeconds);
|
||||
}
|
||||
// Past the very-stale threshold → red.
|
||||
{
|
||||
StalenessBadge b = evaluateStalenessBadge(now - kVeryStaleAfterSeconds, now, true);
|
||||
EXPECT_TRUE(b.show);
|
||||
EXPECT_TRUE(b.severity == StalenessSeverity::Error);
|
||||
}
|
||||
// Clock skew (future timestamp) is clamped to age 0 → no badge, no negative age.
|
||||
{
|
||||
StalenessBadge b = evaluateStalenessBadge(now + 100, now, true);
|
||||
EXPECT_TRUE(!b.show);
|
||||
}
|
||||
}
|
||||
|
||||
void testLoggerFileSink()
|
||||
{
|
||||
using dragonx::util::Logger;
|
||||
@@ -7011,6 +7043,7 @@ int main()
|
||||
testAllowsPlaintextRemote();
|
||||
testConsoleSecretRedaction();
|
||||
testNodeStatusBanner();
|
||||
testStalenessBadge();
|
||||
testLoggerFileSink();
|
||||
testDaemonLifecycleExecution();
|
||||
testDaemonLifecycleAdapters();
|
||||
|
||||
Reference in New Issue
Block a user