feat(lite): ObsidianDragonLite Network tab — server browser

A lite-wallet-only "Network" tab (full-node keeps the Peers tab; exactly one shows per variant)
to manage lightwalletd servers, replacing the basic selector that was in Settings.

- Card list of servers with per-server latency + status dot, DNS host + resolved IP, and an
  Official/Custom pill. Official DragonX servers get a glowing outline.
- Pick a server (Sticky) by clicking its card, or toggle "use a random server" (Random mode);
  selection applies immediately (App::rebuildLiteWallet(force=true) tears down + rebuilds the
  controller against the new server and resyncs — its dtor detaches the uninterruptible sync
  thread, so this doesn't block).
- Add custom servers; hide/unhide servers (persisted set, revealed by a "Show hidden" toggle).
- Latency/IP come from a new background probe (util/LiteServerProbe): libcurl CONNECT_ONLY does
  the TCP+TLS handshake (works for gRPC lightwalletd, no HTTP response needed), recording
  APPCONNECT_TIME as latency and CURLINFO_PRIMARY_IP. Auto-runs on tab open + a Refresh button.

Wiring: WalletUiSurface::LiteNetwork (gated !fullNodePagesAvailable) + NavPage::LiteNetwork in
the sidebar + app.cpp dispatch; settings gains a hidden-servers set; isOfficialLiteServer() added
to lite_connection_service. The Settings page lite-server selector + its plumbing are removed
(single source of truth = the tab).

Reuses the existing server model (LiteServerPreference, Sticky/Random, selectLiteServer) and UI
primitives (DrawGlassPanel, ThemeEffects glow, peers-tab ping-dot idiom). Unit-tested
(liteServerHost, isOfficialLiteServer) + an env-gated live probe (verified vs lite.dragonx.is:
online, latency, IP). Both variants + lite-backend build; suite passes; hygiene clean; GUI
smoke-launched without crash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 11:09:27 -05:00
parent afd612be7e
commit 732d892d4d
16 changed files with 589 additions and 172 deletions

View File

@@ -87,6 +87,14 @@ bool isLiteServerUrlUsable(const std::string& serverUrl)
return startsWith(normalized, "https://") || startsWith(normalized, "http://");
}
bool isOfficialLiteServer(const std::string& serverUrl)
{
const std::string url = trimCopy(serverUrl);
for (const auto& s : defaultDragonXLiteServers())
if (s.url == url) return true;
return false;
}
const char* liteServerSelectionModeName(LiteServerSelectionMode mode)
{
switch (mode) {

View File

@@ -87,6 +87,8 @@ struct LiteServerHealthCheckResult {
std::vector<LiteServerEndpoint> defaultDragonXLiteServers();
LiteConnectionSettings defaultLiteConnectionSettings();
bool isLiteServerUrlUsable(const std::string& serverUrl);
// True if the URL is one of the built-in official DragonX lite servers (gets a glowing outline).
bool isOfficialLiteServer(const std::string& serverUrl);
const char* liteServerSelectionModeName(LiteServerSelectionMode mode);
const char* liteConnectionAvailabilityName(LiteConnectionAvailability availability);
LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings);

View File

@@ -42,7 +42,8 @@ enum class WalletUiSurface {
Settings,
BootstrapDownload,
SetupWizard,
NodeSettings
NodeSettings,
LiteNetwork // lite-wallet-only server browser (replaces Peers in lite builds)
};
struct WalletCapabilities {
@@ -153,6 +154,8 @@ constexpr bool isUiSurfaceAvailable(const WalletCapabilities& capabilities,
case WalletUiSurface::Peers:
case WalletUiSurface::Explorer:
return capabilities.fullNodePagesAvailable;
case WalletUiSurface::LiteNetwork:
return !capabilities.fullNodePagesAvailable; // lite builds only
case WalletUiSurface::BootstrapDownload:
case WalletUiSurface::SetupWizard:
case WalletUiSurface::NodeSettings: