From 529c7fe25d31dad24e35216dc71b871940803d10 Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 28 Jul 2026 05:03:47 -0400 Subject: [PATCH] feat(ui): Web5 wallet/profits render from cached resources (B4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lnd.getinfo and wallet.networking-profits become useCachedResource entries (web5.lnd-info / web5.networking-profits): revisits paint instantly from cache, errors keep last-known values, refreshes dedup. - walletConnected is now derived from the lnd-info entry (with a manual disconnect override preserving the connect/disconnect toggle). - Drop the eager wallet.ecash-balance + lnd.gettransactions loaders and their 30s polling — they fed only the hidden wallet card; the lnd-info poll remains for the connected pill until B5 moves it to WS-push. Co-Authored-By: Claude Fable 5 --- neode-ui/src/views/web5/Web5.vue | 109 ++++++++++--------------------- 1 file changed, 34 insertions(+), 75 deletions(-) diff --git a/neode-ui/src/views/web5/Web5.vue b/neode-ui/src/views/web5/Web5.vue index 729528ba..59473ae3 100644 --- a/neode-ui/src/views/web5/Web5.vue +++ b/neode-ui/src/views/web5/Web5.vue @@ -93,8 +93,9 @@ let web5AnimationDone = false import { ref, computed, onMounted, onUnmounted } from 'vue' import { useI18n } from 'vue-i18n' import { rpcClient } from '@/api/rpc-client' +import { useCachedResource } from '@/composables/useCachedResource' import { safeClipboardWrite } from './utils' -import type { ProfitsData, WalletTransaction, HwWalletDevice } from './types' +import type { ProfitsData, HwWalletDevice } from './types' import Web5QuickActions from './Web5QuickActions.vue' // import Web5Wallet from './Web5Wallet.vue' // hidden for now @@ -136,7 +137,13 @@ function showToast(text: string) { } // --- Networking Profits --- -const profitsBreakdown = ref(null) +const profitsRes = useCachedResource({ + key: 'web5.networking-profits', + fetcher: (signal) => rpcClient.call({ method: 'wallet.networking-profits', signal, dedup: true, maxRetries: 1 }), +}) +const profitsBreakdown = computed(() => + profitsRes.data.value + ?? (profitsRes.error.value ? { total_sats: 0, content_sales_sats: 0, routing_fees_sats: 0 } : null)) const networkingProfitsDisplay = computed(() => { if (!profitsBreakdown.value) return '...' const sats = profitsBreakdown.value.total_sats @@ -146,15 +153,6 @@ const networkingProfitsDisplay = computed(() => { return `\u20BF${btc.toFixed(8).replace(/0+$/, '').replace(/\.$/, '')}` }) -async function loadNetworkingProfits() { - try { - const res = await rpcClient.call({ method: 'wallet.networking-profits' }) - profitsBreakdown.value = res - } catch { - profitsBreakdown.value = { total_sats: 0, content_sales_sats: 0, routing_fees_sats: 0 } - } -} - // --- DID State --- const storedDid = ref(null) try { @@ -290,64 +288,35 @@ async function copyDidDocument() { } // --- Wallet / LND Balances --- -const walletConnected = ref(false) +// Cached: balances/transactions paint instantly on revisit and revalidate +// behind the cached value; errors keep the last-known data. +const lndInfoRes = useCachedResource<{ + balance_sats: number + channel_balance_sats: number + synced_to_chain: boolean +}>({ + key: 'web5.lnd-info', + fetcher: (signal) => rpcClient.call({ method: 'lnd.getinfo', signal, dedup: true, maxRetries: 1 }), +}) +// connectWallet() can still "disconnect" the (hidden) wallet card UI-side. +const walletManuallyDisconnected = ref(false) +const walletConnected = computed(() => + !walletManuallyDisconnected.value && lndInfoRes.data.value !== null && !lndInfoRes.error.value) const connectingWallet = ref(false) -const lndOnchainBalance = ref(0) -const lndChannelBalance = ref(0) -const walletError = ref('') -const ecashBalance = ref(0) - -// Transactions — wallet card hidden, but loadTransactions still called for QuickActions walletConnected state -const walletTransactions = ref([]) +// Ecash/transaction/balance display lives in the hidden wallet card — when it +// returns, add cached resources for wallet.ecash-balance / lnd.gettransactions +// here rather than reviving the old eager loaders. // Hardware wallets const detectedHwWallets = ref([]) -async function loadLndBalances() { - try { - const res = await rpcClient.call<{ - balance_sats: number - channel_balance_sats: number - synced_to_chain: boolean - }>({ method: 'lnd.getinfo' }) - lndOnchainBalance.value = res.balance_sats || 0 - lndChannelBalance.value = res.channel_balance_sats || 0 - walletConnected.value = true - walletError.value = '' - } catch (e) { - walletConnected.value = false - lndOnchainBalance.value = 0 - lndChannelBalance.value = 0 - walletError.value = e instanceof Error ? e.message : 'Failed to load wallet balances' - } -} - -async function loadEcashBalance() { - try { - const res = await rpcClient.call<{ balance_sats: number; token_count: number }>({ method: 'wallet.ecash-balance' }) - ecashBalance.value = res.balance_sats ?? 0 - } catch { - // Keep last-known balance on a transient failure rather than flashing 0. - } -} - -async function loadTransactions() { - try { - const res = await rpcClient.call<{ transactions: WalletTransaction[]; incoming_pending_count: number }>({ method: 'lnd.gettransactions' }) - walletTransactions.value = res.transactions || [] - walletError.value = '' - } catch (e) { - walletTransactions.value = [] - walletError.value = e instanceof Error ? e.message : 'Failed to load transactions' - } -} - async function connectWallet() { if (walletConnected.value) { - walletConnected.value = false + walletManuallyDisconnected.value = true } else { connectingWallet.value = true - await loadLndBalances() + walletManuallyDisconnected.value = false + await lndInfoRes.refresh() connectingWallet.value = false } } @@ -361,13 +330,8 @@ async function detectHardwareWallets() { } } -// function reloadBalances() { // wallet hidden -// loadLndBalances() -// loadEcashBalance() -// loadTransactions() -// } - -// Auto-refresh wallet data every 30s +// Auto-refresh wallet data every 30s while mounted (B5 will move this to +// WS-push invalidation; the store dedups overlapping refreshes). let walletRefreshInterval: ReturnType | null = null onMounted(() => { @@ -392,20 +356,15 @@ onMounted(() => { // credentialsRef.value?.loadCredentials() // hidden for now // sharedContentRef.value?.loadContentItems() // hidden for now - // Load local state data - loadEcashBalance() - loadNetworkingProfits() - loadLndBalances() - loadTransactions() + // Wallet/profits resources fetch themselves on first use (and skip the + // fetch entirely when the cached value is still fresh). detectHardwareWallets() // Shared content loaded by the component itself via expose // The SharedContent component manages its own loadContentItems walletRefreshInterval = setInterval(() => { - loadLndBalances() - loadTransactions() - loadEcashBalance() + void lndInfoRes.refresh() }, 30000) })