[LWM] feat(pay-card): add a Balance screen to the devtool - #21448
[LWM] feat(pay-card): add a Balance screen to the devtool#21448philipptpunkt wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The bindings currently surface a misleading total: 0/no-error state when resolveCounterValue is absent, contradicting the stated “avoid wrong zero” intent and should be made explicit (e.g., error/partial state).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a new “Balance” view to the Pay Card devtool (mobile-first) to display the raw linked-wallet total returned by the balance calculation (unformatted), along with partial-total/error states, and wires host-side countervalue resolution into the devtool bindings.
Changes:
- Introduces a native Balance screen in
@devtools/pay-card, plus navigation from the Pay Card devtool entry list. - Extends the devtool props contract and bindings to expose linked-wallet totals via
useCardLinkedWallets, lazily fetched when the screen opens. - Wires the mobile host to provide a
resolveCounterValueimplementation (and adjusts the DevTools integration test to account for async settling).
File summaries
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds workspace links for new devtool/bindings dependencies (@features/flow-pay-card-wallets, @shared/api-services). |
| devtools/pay-card/src/usePayCardViewModel.test.ts | Updates test props builder to include new balance props contract. |
| devtools/pay-card/src/usePayCardViewModel.native.test.ts | Updates native test props builder to include new balance props contract. |
| devtools/pay-card/src/types.ts | Adds PayCardBalanceProps and extends PayCardToolProps with balance. |
| devtools/pay-card/src/pay-card/PayCard.web.test.tsx | Updates web test props to include balance (even though UI remains web-noop). |
| devtools/pay-card/src/pay-card/PayCard.native.tsx | Adds screen switching and entry point for the new Balance screen; calls balance.load() on open. |
| devtools/pay-card/src/pay-card/PayCard.native.test.tsx | Adds tests covering load-on-open, refresh behavior, and partial-total messaging. |
| devtools/pay-card/src/components/Balance/Balance.web.tsx | Adds web stub for Balance screen (native-only behavior for now). |
| devtools/pay-card/src/components/Balance/Balance.native.tsx | Implements native Balance UI with total, linked wallet count, refresh, partial/error states. |
| devtools/bindings/src/usePayCardToolProps.ts | Wires useCardLinkedWallets into bindings and exposes new balance props with lazy fetching. |
| devtools/bindings/src/usePayCardToolProps.test.tsx | Adds cardApi reducer+middleware to the test store to support Card RTK Query usage. |
| devtools/bindings/package.json | Adds @features/flow-pay-card-wallets dependency and @shared/api-services devDependency for tests. |
| apps/ledger-live-mobile/src/mvvm/features/DevTools/screens/DevToolsScreen/useDevToolsScreenViewModel.ts | Passes host resolveCounterValue into usePayCardToolProps for mobile. |
| apps/ledger-live-mobile/src/mvvm/features/DevTools/integrations/DevToolsScreen.integration.test.tsx | Uses the last DevTools render call to avoid flakiness from async query settling. |
| .changeset/pay-card-devtools-balance.md | Declares version bumps for devtool packages and the mobile app. |
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 14/15 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const balance = useMemo( | ||
| () => ({ | ||
| total: linkedWallets.total, | ||
| isPartialTotal: linkedWallets.isPartialTotal, | ||
| walletCount: linkedWallets.wallets.length, | ||
| isFetching: linkedWallets.isFetching, | ||
| error: linkedWallets.isError ? "The wallet endpoints answered with an error." : undefined, | ||
| load: loadWallets, | ||
| refresh: refreshWallets, | ||
| }), | ||
| [linkedWallets, loadWallets, refreshWallets], | ||
| ); |
Web Tools Build Status
|
Mobile Bundle Checks
Desktop Bundle Checks✅ Previous issues have all been fixed. |
Rsdoctor Bundle Diff AnalysisFound 7 projects in monorepo, 7 projects with changes. 📊 Quick Summary
📋 Detailed Reports (Click to expand)📁 desktop-mainPath:
📁 desktop-preloaderPath:
📁 desktop-rendererPath:
📁 desktop-webviewDappPreloaderPath:
📁 desktop-webviewPreloaderPath:
📁 desktop-workersPath:
📁 mobilePath:
Generated by Rsdoctor GitHub Action |
110a583 to
ec60bce
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The change is well-scoped, preserves lazy-fetch behavior, updates consumers/tests coherently, and includes an appropriate changeset.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
ec60bce to
95b1001
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The Balance screen currently displays misleading “null” explanations that don’t match the actual states produced by the underlying join/pricing logic.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 14/15 changed files
- Comments generated: 2
- Review effort level: Lite
| function Wallet({ wallet }: { readonly wallet: PayCardBalanceWallet }) { | ||
| return ( | ||
| <Box lx={BLOCK_LX}> | ||
| <Text typography="body2">{`${wallet.priority}. ${wallet.currency} / ${wallet.network}`}</Text> | ||
| <Field label="balance" value={wallet.balance ?? "null — no internal wallet matched"} /> | ||
| <Field | ||
| label="counterValue" | ||
| value={ | ||
| wallet.counterValue === null | ||
| ? "null — not priced, left out of the total" | ||
| : String(wallet.counterValue) | ||
| } | ||
| /> | ||
| <Field label="id" value={wallet.id} /> | ||
| <Field label="address" value={wallet.address} /> | ||
| </Box> | ||
| ); | ||
| } |
| /** `null` when no internal wallet matched this link. */ | ||
| readonly balance: string | null; | ||
| /** `null` when the balance could not be priced. Those are left out of the total. */ | ||
| readonly counterValue: number | null; |
bc54a09 to
7b9c907
Compare
95b1001 to
218ab25
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The Balance screen can display a misleading 0 total before wallets are actually loaded/priced because total is always populated from a defaulted calculation even when queries are skipped.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (1)
devtools/bindings/src/usePayCardToolProps.ts:197
balance.totalis always set fromuseCardLinkedWallets().total, which defaults to0when the queries are skipped/uninitialized. That means the Balance screen can momentarily show a real-looking0even before any wallets were fetched/priced, which undermines the intent of rendering the raw value (includingundefined) to distinguish “not loaded” from “zero”.
const balance = useMemo(
() => ({
total: linkedWallets.total,
isPartialTotal: linkedWallets.isPartialTotal,
wallets: linkedWallets.wallets,
isFetching: linkedWallets.isFetching,
errors,
load: loadWallets,
refresh: refreshWallets,
}),
[linkedWallets, errors, loadWallets, refreshWallets],
);
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
7b9c907 to
5a63cad
Compare
218ab25 to
a4e58a4
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The changes are self-contained, follow the stated lazy-fetching/pricing-resolver design, and include targeted unit/integration test updates plus a changeset.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
5a63cad to
3cded4d
Compare
a4e58a4 to
9aad82b
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The changes are cohesive, MVVM-compatible on mobile, include a changeset, and add/adjust targeted tests for the new Balance devtool behavior.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
3cded4d to
a7701d0
Compare
9aad82b to
f8d18cd
Compare
f8d18cd to
6c9487f
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The Balance screen/binding currently risks displaying misleading “0”/misattributed null states, which undermines the devtool’s debugging purpose.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (2)
devtools/pay-card/src/components/Balance/Balance.native.tsx:44
counterValue === nullcan mean either “no internal wallet matched” (balance is null) or “could not price” (no rate / mapping). The current message always claims a pricing/mapping issue, which is incorrect for wallets withbalance: nulland can mislead debugging.
<Field
label="counterValue"
value={
wallet.counterValue === null
? "null — no currency matched this ticker, or no rate for it"
devtools/bindings/src/usePayCardToolProps.ts:227
- When
resolveCounterValueis omitted, wallet queries are skipped, but the Balance props still surfacetotal: 0(from combining empty caches). That conflicts with the option doc (“report no balance rather than a wrong zero”) and makes “uninitialized / disabled” indistinguishable from a real 0 balance. Consider returningundefineduntil pricing is available (and while the first load is in progress).
total: linkedWallets.total,
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
The linked-wallet total has no UI that shows the number itself, so a wrong total cannot be told apart from a wrong formatter. The devtool shows what the calculation returned. - Opening the screen requests the wallets: the queries are skipped until then, so the tool does not fetch a cardholder's wallets to render a list of flags. - The total is rendered raw, in the counter-value currency's smallest unit. Formatting it would hide the unit bug this screen exists to catch. - A partial total says so. A wallet the rates could not price is left out of the sum rather than counted as zero, which otherwise reads as a complete total. - Pricing needs the app's rates and currency settings, so the host passes its resolver in, the same way the card visual gets one.
6c9487f to
fe47991
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The bindings currently expose a misleading 0 balance total while queries are skipped/unavailable, conflicting with the intended “no balance rather than wrong zero” behavior.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (1)
devtools/bindings/src/usePayCardToolProps.ts:230
balance.totalis always set tolinkedWallets.total(which defaults to0when queries are skipped or before data arrives). This contradicts the stated intent of “report no balance rather than a wrong zero” whenresolveCounterValueis absent / wallets haven’t been requested yet, and makes the Balance screen briefly show0as a real total.
const balance = useMemo(
() => ({
total: linkedWallets.total,
isPartialTotal: linkedWallets.isPartialTotal,
wallets: linkedWallets.wallets,
isFetching: linkedWallets.isFetching,
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
|



Stack (managed by stac-man)
The linked-wallet total has no UI that shows the number itself, so a wrong
total cannot be told apart from a wrong formatter. The devtool shows what the
calculation returned.
so the tool does not fetch a cardholder's wallets to render a list of flags.
Formatting it would hide the unit bug this screen exists to catch.
sum rather than counted as zero, which otherwise reads as a complete total.
resolver in, the same way the card visual gets one.