GitHub Issue Draft: Render Transaction Details With Partial Data While Full Details Load
Problem
Opening transaction details currently shows a full-page loading state until transactionDetails(txId) returns. That blocks the whole screen even though the selected wallet screen already has enough transaction summary data to show the main details layout immediately: sent/received state, pending/confirmed state, amount, fiat snapshot, label, and confirmation date or last-seen metadata.
The result is that a tap from the transaction list feels slower than it needs to. The user should land on the normal transaction details screen immediately, with inline loading indicators only for fields that require the full TransactionDetails object.
Current Flow
Android:
SelectedWalletScreen renders transactions from manager.loadState.
- A transaction row pushes
Route.TransactionDetails(walletId, txId).
TransactionDetailsContainer resolves the wallet manager, checks manager.transactionDetailsCache[txId], and shows FullPageLoadingView() until full details exist.
TransactionDetailsScreen requires a full TransactionDetails.
iOS:
SelectedWalletScreen renders transactions from manager.loadState.
- A transaction row navigates to
Route.transactionDetails(id: walletId, txId: txId).
TransactionsDetailScreen.TransactionDetailsLoader checks manager.transactionDetails[txId] and shows FullPageLoadingView until full details exist.
TransactionDetailsView requires a full TransactionDetails.
Rust:
- Summary transactions come from
WalletLoadState.scanning(txns) or WalletLoadState.loaded(txns).
- Full details are built by
RustWalletManager.transactionDetails(txId), which does BDK tx lookup, label DB lookup, fee/address/index/change/RBF derivation, and then returns TransactionDetails.
Data Available Without Loading Full Details
From the selected wallet screen and wallet manager:
walletId
txId
walletMetadata
- wallet name
- selected unit
- fiat/BTC primary-secondary preference
- sensitive visibility
detailsExpanded
- wallet type
- network/origin metadata if exposed through
WalletMetadata
balancePresentation, app theme state, and normal screen chrome inputs
loadState
loading
scanning([Transaction])
loaded([Transaction])
scanStatus and ledgerState
unsignedTransactions
- cached full details, if already loaded
- Android:
manager.transactionDetailsCache[txId]
- iOS:
manager.transactionDetails[txId]
From a summary Transaction.Confirmed:
txid
block_height
confirmed_at
sent_and_received
- direction: incoming/outgoing
- amount
- optional current fiat amount snapshot
- labels
From a summary Transaction.Unconfirmed:
txid
last_seen
sent_and_received
- direction: incoming/outgoing
- amount
- optional current fiat amount snapshot
- labels
Data Not Available Until Full Details Load
These should render as inline loading, hidden disabled controls, or placeholders until full details are available:
- destination/receive address
- copy-address action
- fee
- fee rate, if surfaced
- sent amount excluding fee
- input indexes
- output indexes
- change address
- RBF signaling flag
- full transaction URL if it depends on
TransactionDetails.transactionUrl()
- label edit/save/delete if the label manager still requires full
TransactionDetails
- historical fiat value
- full current fiat calculation if the summary fiat snapshot is absent
- confirmation count, because it requires current block height
Proposed UX
Render the normal transaction details screen immediately when a summary transaction is available.
Immediate content:
- back button and normal details background
- header icon/rings using summary direction and confirmed/pending state
- title:
- confirmed outgoing:
Transaction Sent
- confirmed incoming:
Transaction Received
- unconfirmed:
Transaction Pending
- label display from summary labels
- confirmed status copy and date from
confirmed_at
- pending status copy from current UI
- primary amount using
sent_and_received.amount() and wallet metadata
- secondary fiat amount from summary fiat snapshot when available
- sent/received/sending/receiving capsule
- show/hide details button
Inline loading content:
- show small spinner or skeleton text where address would appear
- show spinner for confirmations until
numberOfConfirmations returns
- show spinner for block number only if summary is unavailable; confirmed summary already has
block_height
- show spinner for fee, recipient receives, total spent fiat, historical fiat
- disable copy-address until address is loaded
- disable label editing until full details is loaded, or allow display-only summary label first
- disable
View in Explorer until the URL is available, unless we add a summary-safe URL builder from txid + network
Fallback behavior:
- If no summary transaction is available and no full details are cached, keep the current full-page loading state.
- If full details loading fails after the partial screen is shown, keep the summary screen visible and show an inline/banner error with retry instead of replacing everything with an error page.
Data Model Plan
Add a platform-level view model for transaction details data instead of making the UI depend directly on TransactionDetails.
Suggested shape:
TransactionDetailsPresentation
- txId
- direction
- state
- primaryAmount
- secondaryFiatAmount?
- label?
- confirmedAt?
- blockNumber?
- lastSeen?
- fullDetails?
- fullDetailsLoadState
- confirmations?
- confirmationsLoadState
- fiatLoadStates
Build it from:
- cached full details, if present
- otherwise matching summary transaction from
manager.loadState
- otherwise no partial data, current full-page loading fallback
Do not initially change the UniFFI Route.TransactionDetails(id, txId) shape unless needed. The least invasive approach is:
- In the details container, after resolving
WalletManager, search manager.loadState and manager.unsignedTransactions for txId.
- If found, render partial presentation immediately.
- Continue loading full details in the background.
If this is not reliable enough across route resets or future deep links, add a transient manager cache instead of expanding the Rust route:
selectedTransactionSummary: Map<TxId, Transaction> or equivalent
- populate it on transaction row tap
- clear/replace opportunistically when transaction lists update
Changing the Rust route to carry a summary object should be a later option because it touches generated bindings and every route call site.
Implementation Steps
-
Add helper to find summary transaction by txId.
- Android: helper on
WalletManager or local helper near TransactionDetailsContainer.
- iOS: helper on
WalletManager or local helper in TransactionsDetailScreen.
-
Introduce a details presentation state.
- Supports
summaryOnly, fullDetailsLoaded, loadingFullDetails, and loadFailed.
- Keeps full details optional.
- Exposes common fields without requiring UI code to branch everywhere.
-
Refactor transaction details screen inputs.
- Current: screen requires
TransactionDetails.
- New: screen accepts presentation state and optional full details.
- Header/main amount/status use summary fields.
- Expanded rows read from full details when available.
-
Replace full-page loading on summary hit.
- If summary exists, render normal screen immediately.
- Start
manager.transactionDetails(txId) in the background.
- When it completes, update existing details cache and presentation state.
-
Keep current full-page loading fallback.
- If details are not cached and summary is not found, preserve current behavior.
-
Adjust label behavior.
- Display summary label immediately.
- Gate add/edit/delete behind full details unless
LabelManager gets an API that can update only the transaction label by txId.
-
Adjust explorer button behavior.
- Prefer adding a summary-safe URL builder from txid + wallet network if the network is already available.
- Otherwise keep the button disabled/loading until full details loads.
-
Preserve refresh and polling behavior.
- Pull-to-refresh should request full details and update presentation state.
- Confirmation polling should only run once there is enough confirmed transaction data.
- For confirmed summaries, use summary
block_height to fetch confirmations even before full details arrives.
-
Add focused tests.
- Summary transaction maps to sent/received/pending title and amount correctly.
- Details container renders partial state when cache miss plus summary hit.
- Details container falls back to full-page loading when cache miss plus summary miss.
- Full details replaces partial placeholders when loaded.
- Failed full-details load keeps partial content and exposes retry/error state.
Acceptance Criteria
- Tapping a transaction with an available summary shows the normal details screen immediately.
- Sent/received/pending title, amount, label display, and status capsule are visible before full details load.
- Fields that require full details show inline loading placeholders and fill in when data arrives.
- No full-page loading screen is shown for transaction-list taps when the summary is available.
- Current behavior is preserved for direct/cold details routes where no summary or cached full details exists.
- Label editing does not operate on incomplete data.
- Android and iOS follow the same state model and visual behavior.
Open Questions
- Should
View in Explorer be enabled from summary data by adding a txid + network URL helper, or wait for full details?
- Should label editing get a lighter API for transaction-label-only updates, or remain disabled until full details loads?
- Should the summary be looked up from
loadState only, or should transaction row taps write a transient selected-summary cache for more reliable navigation transitions?
- Should
unsignedTransactions participate in this partial-details flow, or should this issue only cover broadcast transactions?
GitHub Issue Draft: Render Transaction Details With Partial Data While Full Details Load
Problem
Opening transaction details currently shows a full-page loading state until
transactionDetails(txId)returns. That blocks the whole screen even though the selected wallet screen already has enough transaction summary data to show the main details layout immediately: sent/received state, pending/confirmed state, amount, fiat snapshot, label, and confirmation date or last-seen metadata.The result is that a tap from the transaction list feels slower than it needs to. The user should land on the normal transaction details screen immediately, with inline loading indicators only for fields that require the full
TransactionDetailsobject.Current Flow
Android:
SelectedWalletScreenrenders transactions frommanager.loadState.Route.TransactionDetails(walletId, txId).TransactionDetailsContainerresolves the wallet manager, checksmanager.transactionDetailsCache[txId], and showsFullPageLoadingView()until full details exist.TransactionDetailsScreenrequires a fullTransactionDetails.iOS:
SelectedWalletScreenrenders transactions frommanager.loadState.Route.transactionDetails(id: walletId, txId: txId).TransactionsDetailScreen.TransactionDetailsLoaderchecksmanager.transactionDetails[txId]and showsFullPageLoadingViewuntil full details exist.TransactionDetailsViewrequires a fullTransactionDetails.Rust:
WalletLoadState.scanning(txns)orWalletLoadState.loaded(txns).RustWalletManager.transactionDetails(txId), which does BDK tx lookup, label DB lookup, fee/address/index/change/RBF derivation, and then returnsTransactionDetails.Data Available Without Loading Full Details
From the selected wallet screen and wallet manager:
walletIdtxIdwalletMetadatadetailsExpandedWalletMetadatabalancePresentation, app theme state, and normal screen chrome inputsloadStateloadingscanning([Transaction])loaded([Transaction])scanStatusandledgerStateunsignedTransactionsmanager.transactionDetailsCache[txId]manager.transactionDetails[txId]From a summary
Transaction.Confirmed:txidblock_heightconfirmed_atsent_and_receivedFrom a summary
Transaction.Unconfirmed:txidlast_seensent_and_receivedData Not Available Until Full Details Load
These should render as inline loading, hidden disabled controls, or placeholders until full details are available:
TransactionDetails.transactionUrl()TransactionDetailsProposed UX
Render the normal transaction details screen immediately when a summary transaction is available.
Immediate content:
Transaction SentTransaction ReceivedTransaction Pendingconfirmed_atsent_and_received.amount()and wallet metadataInline loading content:
numberOfConfirmationsreturnsblock_heightView in Exploreruntil the URL is available, unless we add a summary-safe URL builder from txid + networkFallback behavior:
Data Model Plan
Add a platform-level view model for transaction details data instead of making the UI depend directly on
TransactionDetails.Suggested shape:
Build it from:
manager.loadStateDo not initially change the UniFFI
Route.TransactionDetails(id, txId)shape unless needed. The least invasive approach is:WalletManager, searchmanager.loadStateandmanager.unsignedTransactionsfortxId.If this is not reliable enough across route resets or future deep links, add a transient manager cache instead of expanding the Rust route:
selectedTransactionSummary: Map<TxId, Transaction>or equivalentChanging the Rust route to carry a summary object should be a later option because it touches generated bindings and every route call site.
Implementation Steps
Add helper to find summary transaction by
txId.WalletManageror local helper nearTransactionDetailsContainer.WalletManageror local helper inTransactionsDetailScreen.Introduce a details presentation state.
summaryOnly,fullDetailsLoaded,loadingFullDetails, andloadFailed.Refactor transaction details screen inputs.
TransactionDetails.Replace full-page loading on summary hit.
manager.transactionDetails(txId)in the background.Keep current full-page loading fallback.
Adjust label behavior.
LabelManagergets an API that can update only the transaction label bytxId.Adjust explorer button behavior.
Preserve refresh and polling behavior.
block_heightto fetch confirmations even before full details arrives.Add focused tests.
Acceptance Criteria
Open Questions
View in Explorerbe enabled from summary data by adding a txid + network URL helper, or wait for full details?loadStateonly, or should transaction row taps write a transient selected-summary cache for more reliable navigation transitions?unsignedTransactionsparticipate in this partial-details flow, or should this issue only cover broadcast transactions?