10 acceptance criteria. AC8 fails on evidence; AC2 has a scope deviation to confirm; AC1 needs a zero-wallet account.
AC1 — no activate-wallet banner; first wallet clears the empty state without reload
Untestable as written: the empty state only exists on an account with no wallets, and no zero-wallet account was available. Partial check on the populated account: no activate-wallet banner is shown.
Separately, creating a wallet (AC2) updated the list and the portfolio with zero full-page navigations — the "without a reload" behaviour is real, it just hasn't been exercised from the empty state.
AC2 — Create Wallet: country for XAF/XOF, owned currencies disabled
Pass: every currency the user already owns (NGN, USD, KES, GBP, EUR, TZS, and the newly created XOFCI) is disabled in the dropdown (aria-disabled="true").
Deviation: XOF is offered as separate per-country options (XOFCI, XOFSN, XOFBJ, XOFBF, XOFML, XOFTG) rather than a country picker after choosing XOF; there is no separate country field, and XAF is not offered at all. Whether that satisfies "asks for a country for XAF/XOF" is a design call for Temitope/Flourish.
AC3 — list is searchable; unowned currency offers creation
Searching GHS (unowned) shows "No wallets found for 'GHS'… Create a wallet to start adding funds" with a Create GHS Wallet button. Searching NGN (owned) filters to the wallet with no create offer.
AC4 — 5 wallets shown, Show More / Show Less for the rest
With 7 wallets the list shows 5 and a Show 2 More Wallets control; clicking it reveals all and the control becomes Show Less.
AC5 — selecting a wallet shows balance, actions and account details without refetching
Selecting USD updates the detail pane to USD Wallet, its balance, Add/Send/Convert and its account details. 0 full-page navigations during selection (a scoped wallet-history POST loads the transactions panel only).
AC6 — copy one row / all, and share; "Copied" only on real success
Row copy wrote the account number to the clipboard; Copy All wrote all three fields (Account holder: … / Bank Name: … / Account number: …). After copying, the button shows Copied to Clipboard. Share calls navigator.share (browser share API).
Control: with the clipboard forced to fail, the confirmation does not appear — a "Could not copy details" error is shown instead. So "Copied" is genuinely gated on success.
AC7 — Portfolio Breakdown shows each wallet's share
The breakdown lists NGN 52%, USD 48%, KES/EUR 0%, Others (GBP, TZS, XOF) 0% — sums to 100% and reflects the wallet set (the newly created XOFCI appeared under "Others"). A live balance-change update was not exercised (no deposit made).
AC8 — hide/show masks every amount
Balances render masked (*****) on load, but the eye toggle does not reveal them. Clicking it flips its own label ("Hide balances" ⇄ "Show balances") while every amount stays masked; clicking again flips back, still masked. The toggle makes no network call, so it is pure client state.
Not a data problem: GET /wallet-app/get-wallet-balance/all returns real values (NGN 1099800.0, USD 999998.0, …) which the page then masks and will not show. In one run the page loaded with balances visible and a single toggle click hid them — so Hide works, Show does not.
AC9 — last 5 recent transactions; View All filters to that currency
The selected wallet's recent transactions render (the app requests pageSize: 5), and View All Transactions opens /transactions?currency=NGN (USD → ?currency=USD). No wallet had more than 3 transactions, so the 5-row cap itself wasn't stress-tested.
AC10 — loading and error states with retry; single toast per error
Loading renders skeletons before data. Forcing the wallet calls to fail shows "We couldn't load your wallets… Try again" with a "Balances unavailable" panel; exactly one toast is shown and repeated retries do not stack toasts. Restoring the network and retrying recovers the list.