feat(diagnostics): persistent alert history with a status-bar bell (Foundation QoL)
Toasts fade in 1-4s, so anything that scrolled past was gone. Notifications now retains every pushed alert in a capped (100) ring buffer with a wall-clock epoch (AlertRecord) — separate from the 5-item live-toast deque — plus a monotonic total_pushed_ counter. A bell in the status-bar right cluster opens an upward popup listing recent alerts newest-first: severity icon + colour (reusing the toast palette), the message, and a relative age (formatTimeAgoShort), with a Clear-all action. An unread dot on the bell, coloured by the most-severe unseen alert, marks alerts that arrived since the panel was last opened — driven by totalPushed() deltas so it survives capping/clearing. Thread note: every push is on the UI thread (RPC results run as main-thread MainCb callbacks), matching this class's existing lock-free model; documented as a no-raw-worker-thread invariant. New i18n keys (alerts_*). Build-clean; ctest 1/1 (adds testNotificationHistory: retention, order, cap, monotonic counter, clear). Closes the QoL bundle and the Foundation tier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
#include "ui/windows/mining_tab_helpers.h"
|
||||
#include "ui/node_status_banner.h"
|
||||
#include "ui/staleness_badge.h"
|
||||
#include "ui/notifications.h"
|
||||
#include "util/address_validation.h"
|
||||
#include "util/amount_format.h"
|
||||
#include "util/payment_uri.h"
|
||||
@@ -2655,6 +2656,37 @@ void testStalenessBadge()
|
||||
}
|
||||
}
|
||||
|
||||
void testNotificationHistory()
|
||||
{
|
||||
using dragonx::ui::Notifications;
|
||||
using dragonx::ui::NotificationType;
|
||||
auto& n = Notifications::instance();
|
||||
n.clearHistory();
|
||||
auto base = n.totalPushed(); // monotonic counter is NOT reset by clearHistory()
|
||||
|
||||
n.push("first", NotificationType::Info, 5.0f);
|
||||
n.push("second", NotificationType::Error, 5.0f);
|
||||
EXPECT_EQ((int)n.history().size(), 2);
|
||||
EXPECT_TRUE(n.hasHistory());
|
||||
// Oldest first, newest last; type + wall-clock stamp retained.
|
||||
EXPECT_EQ(n.history().front().message, std::string("first"));
|
||||
EXPECT_EQ(n.history().back().message, std::string("second"));
|
||||
EXPECT_TRUE(n.history().back().type == NotificationType::Error);
|
||||
EXPECT_TRUE(n.history().back().epoch > 0);
|
||||
EXPECT_EQ((int)(n.totalPushed() - base), 2);
|
||||
|
||||
// Cap at 100: push past it → size caps, oldest entries drop, totalPushed keeps counting.
|
||||
for (int i = 0; i < 150; ++i) n.push("bulk", NotificationType::Info, 5.0f);
|
||||
EXPECT_EQ((int)n.history().size(), 100);
|
||||
EXPECT_EQ((int)(n.totalPushed() - base), 152);
|
||||
EXPECT_EQ(n.history().front().message, std::string("bulk")); // the two originals fell off
|
||||
|
||||
n.clearHistory();
|
||||
EXPECT_TRUE(!n.hasHistory());
|
||||
EXPECT_EQ((int)n.history().size(), 0);
|
||||
EXPECT_EQ((int)(n.totalPushed() - base), 152); // clearing doesn't rewind the counter
|
||||
}
|
||||
|
||||
void testLoggerFileSink()
|
||||
{
|
||||
using dragonx::util::Logger;
|
||||
@@ -7044,6 +7076,7 @@ int main()
|
||||
testConsoleSecretRedaction();
|
||||
testNodeStatusBanner();
|
||||
testStalenessBadge();
|
||||
testNotificationHistory();
|
||||
testLoggerFileSink();
|
||||
testDaemonLifecycleExecution();
|
||||
testDaemonLifecycleAdapters();
|
||||
|
||||
Reference in New Issue
Block a user