From 2b70ee5cd83905c50aa16ae89fbfe45ec5a2d01c Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 13 Jun 2026 09:05:37 -0500 Subject: [PATCH] feat(history): show "Loading older history (N%)" during the initial bulk load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit History streams in over many refresh cycles (the incremental shielded scan walks every z-address), so the first batch appears long before the list is complete — with no indication more is still coming. The existing loading banner deliberately goes quiet once any rows are on screen. Track whether the first full shielded scan has finished (initial_history_scan_complete_) and, until it has, surface a progress percentage (fraction of z-addresses scanned) in transactionRefreshProgressText() — which the History tab already renders as its pulsing loading indicator. Goes quiet once the first scan completes; routine per-block re-scans don't re-trigger it. Reset on a full history invalidation (rescan / session reset) so it shows again on reload. Co-Authored-By: Claude Opus 4.8 --- src/app.cpp | 14 ++++++++++++++ src/app.h | 4 ++++ src/app_network.cpp | 2 ++ src/util/i18n.cpp | 1 + 4 files changed, 21 insertions(+) diff --git a/src/app.cpp b/src/app.cpp index f074bc6..d890ebb 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -3704,6 +3704,20 @@ std::string App::transactionRefreshProgressText() const return buf; } + // Initial bulk history load: the incremental shielded scan streams older transactions in over + // many refresh cycles, so the first batch can appear long before the history is complete. Keep + // the user informed (with a %) even after rows are on screen, so they know it's still loading. + // Progress = fraction of z-addresses scanned at least once (monotonic). Goes quiet for good once + // the first full scan completes; routine per-block re-scans don't re-trigger it. + if (!initial_history_scan_complete_ && !state_.z_addresses.empty()) { + std::size_t total = state_.z_addresses.size(); + std::size_t scanned = std::min(shielded_history_scan_heights_.size(), total); + int pct = total > 0 ? static_cast((scanned * 100) / total) : 0; + if (pct > 99) pct = 99; // 100% is the "complete" state, which suppresses the banner + snprintf(buf, sizeof(buf), TR("tx_loading_history_progress"), pct); + return buf; + } + // Once history is on screen, stay quiet for routine background refreshes. The incremental // shielded-receive scan re-dirties transactions_dirty_ on EVERY new block (each block moves the // "scanned at tip" bar, invalidating prior per-address scans), so for a wallet with more diff --git a/src/app.h b/src/app.h index a00e355..f552324 100644 --- a/src/app.h +++ b/src/app.h @@ -575,6 +575,10 @@ private: static constexpr int MAX_VIEWTX_PER_CYCLE = 25; // cap z_viewtransaction calls per refresh std::size_t shielded_history_scan_cursor_ = 0; bool shielded_history_scan_pending_ = false; + // False until the first full shielded-history scan finishes. Drives the History tab's + // "Loading older history…" progress so the user knows transactions are still streaming in + // after the first batch appears; goes quiet for the routine per-block re-scans afterward. + bool initial_history_scan_complete_ = false; std::unordered_map shielded_history_scan_heights_; // P4b: z_viewtransaction result cache — avoids re-calling the RPC for diff --git a/src/app_network.cpp b/src/app_network.cpp index d9bb1b2..a7689dc 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -975,6 +975,7 @@ void App::invalidateShieldedHistoryScanProgress(bool persistCache) { shielded_history_scan_cursor_ = 0; shielded_history_scan_pending_ = false; + initial_history_scan_complete_ = false; // a full re-scan is coming — show load progress again shielded_history_scan_heights_.clear(); if (persistCache) storeTransactionHistoryCacheIfAvailable(); } @@ -1353,6 +1354,7 @@ void App::refreshTransactionData() storeTransactionHistoryCacheIfAvailable(); shielded_history_scan_cursor_ = nextShieldedScanStartIndex; shielded_history_scan_pending_ = !shieldedScanComplete; + if (shieldedScanComplete) initial_history_scan_complete_ = true; transactions_dirty_ = !shieldedScanComplete; maybeFinishTransactionSendProgress(); }; diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 9b81353..97d3d57 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -568,6 +568,7 @@ void I18n::loadBuiltinEnglish() strings_["tx_loading_scanning_shielded"] = "Scanning shielded history (%d addresses)"; strings_["tx_loading_refreshing_cached"] = "Refreshing wallet history (%d cached)"; strings_["tx_loading_fetching_transparent"] = "Fetching transparent history"; + strings_["tx_loading_history_progress"] = "Loading older history (%d%%)"; strings_["no_matching"] = "No matching transactions"; strings_["transaction_id"] = "TRANSACTION ID"; strings_["search_placeholder"] = "Search...";