feat(wallets): refresh the wallets list with an account header - #2933
Merged
Merged
Conversation
Restyle the Wallets screen per node 9573-23172: an active-account header block (identicon, name, address, and four action buttons for QR, copy, stellar.expert, and rename) replaces the per-row ellipsis dropdown. Each WalletRow becomes a three-column row (identicon / name+address / balance) with "Imported" shown inline beside the address. - Copy and stellar.expert buttons add call sites for the existing account.public_key_copied / account.stellar_expert_opened metrics, with no arguments (a `source` property is deferred to a follow-up PR). - The explorer button is hidden via isCustomNetwork(networkDetails), mirroring ViewPublicKey. - Tapping a row still makes that account active and navigates to Home (spec D4) - rename/copy/QR/explorer now act on the active account only. - Removes dead `.detail-indentifier`, `.WalletRow__options-actions`, and `.Wallets__fail` CSS/attribute orphans. - Hand-added new i18n keys to en/pt since build:extension:translations is broken on master (pre-existing, out of scope here).
The per-row ellipsis dropdown (and its wallet-row-options testid) is gone; rename now acts on the active account through the header pencil button. Repoints "Renames wallets" at wallets-header-edit-name, and scopes its final assertions to the header/row locators specifically since the new name now legitimately renders in two places (and this seed phrase has several accounts sharing the row list). Also updates "Loads wallets data and token prices on Mainnet in batches": address and balance are no longer a single concatenated string, so each per-account assertion now checks a row's balance cell scoped by its address rather than matching literal "address - $balance" text.
… own styles SDS Button assigns its own className internally and then spreads the rest of its props over the element, so any className passed in by a caller replaces (rather than merges with) Button/Button--tertiary/etc. instead of augmenting them. That silently dropped every rule meant for the restyled add-wallet button, leaving it transparent, gray, and sized off its unconstrained icon/text content. Wrap the Button in a plain div instead and scope the token overrides through that wrapper, targeting `.Button` and `.Button__icon` by descendant selector so they win on specificity without touching the className prop at all. Confirmed via computed-style checks that the button now resolves to the intended lilac-02/09/11 tokens at a 34px pill height, matching the design spec.
Contributor
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-42f8ebfa615c5c16f8e1 (SDF collaborators only — install instructions in the release description) |
This was referenced Aug 4, 2026
Corrects eight deviations from Figma frame 9573-23172: - Header name and address are now one tight block; the 16px gap applies only between the identicon, that block, and the action row. - QR icon qr-code-01 -> qr-code-02, pencil icon edit-05 -> edit-01. - The divider sits inside the 24px content gutter instead of bleeding to the window edges, and is held 16px off the list below it. - Row avatars lose their border and white fill for a flat gray circle, with the identicon inset to 16px inside the 40px avatar. - The active row's selected state drops its colored border in favor of a 16px navy badge flush to the avatar's bottom-right corner. - "Imported" is separated from the address by a bullet. - The add-wallet control keeps its tint on the icon chip alone rather than behind the whole button, at medium weight. The button's copy becomes "Add wallet", which already exists as a key (it is the AddWallet sheet's own title), so this adds no new strings and retires the now-orphaned "Add a wallet". That collision is also why the e2e click moves to the add-wallet testid: the label alone would be ambiguous once the sheet it opens is on screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- The copy button now shows the same success/error toast the QR screen shows, replacing CopyText's inline "Copied!" balloon. The two surfaces do the same thing, so they should confirm it the same way. The metric moves into the handler and fires only after the clipboard write resolves, so failed copies still aren't counted. - Restores SDS's `xl` padding on the add-wallet button. Its 16px/24px text already matches the spec, so only the colors, gap and weight stay overridden; the tint remains on the icon chip alone. - Removes the wallet row's hover background. "Copied!" stays in the catalog — AccountHeader still uses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`--font-weight-medium` is defined nowhere — not in this repo and not in SDS, which names it `--sds-fw-medium`. An undefined custom property makes the whole declaration invalid, and because font-weight inherits, every element referencing it silently rendered at the body's 400. Measured in a browser before the fix, the add-wallet label, the header name, the row names and the balances were all 400; the spec calls for 500 on each. Swapping the six occurrences in these two files for `--sds-fw-medium` and `--sds-fw-regular` puts them at 500/400 as the spec's Text/MD/500, Text/SM/500 and Text/SM/400 styles require. This also means the earlier "less bold" pass on the add-wallet label was not setting medium at all, only cancelling SDS's semi-bold down to regular by accident. The same undefined token is referenced from BackButton, TransactionHeading, Notification and AmountCard among others, all with the same silent fallback. Those are left alone here. Also drops the add-wallet button's left padding so its icon chip lines up with the list's avatars in the 24px gutter, and gives the content footer an even 8px above and below, scoped with :has() so no other view's footer changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Refreshes the Wallets screen with an active-account header and redesigned wallet rows.
Changes:
- Adds account actions for QR, copy, explorer, and rename.
- Separates wallet balances from addresses and removes row menus.
- Updates translations, styling, and E2E assertions.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
Wallets/index.tsx |
Adds the account header and actions. |
Wallets/styles.scss |
Styles the refreshed layout. |
WalletRow/index.tsx |
Redesigns wallet row content and interaction. |
WalletRow/styles.scss |
Styles redesigned rows. |
locales/en/translation.json |
Updates English strings. |
locales/pt/translation.json |
Updates Portuguese strings. |
loadAccount.test.ts |
Updates wallet E2E scenarios. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Addresses Copilot review on #2933. The explorer action was gated on `!isCustomNetwork`, which only excludes STANDALONE. Experimental mode switches the active network to Futurenet (saveExperimentalFeatures), and stellar.expert has no Futurenet explorer, so the action rendered and built an unsupported /explorer/futurenet/ URL. This is long-standing logic from #616 rather than a regression here, but #2930 removed the QR screen's copy, leaving this the only caller — so it gets fixed here. Note that switching to the shared `getStellarExpertUrl` helper alone does not fix it: the helper falls through to /public for anything that isn't testnet, so an ungated Futurenet link would render a *mainnet* lookup for a Futurenet account — wrong data, silently. The gate is what matters, so this adds `isStellarExpertSupported` beside the helper to keep the two together, with unit tests pinning the Futurenet and custom-network exclusions and documenting that fallback. WalletRow gains `role="button"` and `aria-current`, matching BalanceRow, the sibling list row. The active account was previously conveyed only by the badge on its avatar, which says nothing to a screen reader. Left alone: full keyboard focusability. No row component in this codebase (BalanceRow, ProtocolRow) sets tabIndex or key handlers, so making this one a semantic button would be inconsistent without addressing that codebase-wide. Also adds e2e coverage for the copy action, asserting the clipboard contents and the toast. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
piyalbasu
reviewed
Aug 12, 2026
piyalbasu
approved these changes
Aug 12, 2026
# Conflicts: # extension/src/popup/locales/en/translation.json # extension/src/popup/locales/pt/translation.json
WalletRow's redesign removed the last caller — it used this to tint the selected row's border, which the new design replaces with a fixed navy badge. The export then had no references anywhere in the repo. `no-unused-vars` cannot see it because the symbol is exported, so it would have stayed green in CI indefinitely rather than surfacing later. Only the getColorPubKey block goes. `publicKeyToBytes` and `HSVtoRGB` stay: drawIdenticon and setFillStyle still call them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CassioMG
added a commit
that referenced
this pull request
Aug 13, 2026
#2933 was squash-merged to master as 9a7408b, so git sees no shared history with the branch merge this PR already carried — every file that PR touched conflicted even though the content was identical. Resolved to this branch's side in all three. Verified per file that master's side was purely the pre-change state and contributed nothing new: WalletRow (spinner + $0.00 fallback), its stylesheet (balance cell min-width) and the Wallets view (error state scoped to the list, Redux key fallback) each differ from master only by this branch's additions. The getColorPubKey cleanup arrives via master's squash and is intact.
CassioMG
added a commit
that referenced
this pull request
Aug 17, 2026
…ks (#2960) * feat(wallets): refresh the wallets list with an account header Restyle the Wallets screen per node 9573-23172: an active-account header block (identicon, name, address, and four action buttons for QR, copy, stellar.expert, and rename) replaces the per-row ellipsis dropdown. Each WalletRow becomes a three-column row (identicon / name+address / balance) with "Imported" shown inline beside the address. - Copy and stellar.expert buttons add call sites for the existing account.public_key_copied / account.stellar_expert_opened metrics, with no arguments (a `source` property is deferred to a follow-up PR). - The explorer button is hidden via isCustomNetwork(networkDetails), mirroring ViewPublicKey. - Tapping a row still makes that account active and navigates to Home (spec D4) - rename/copy/QR/explorer now act on the active account only. - Removes dead `.detail-indentifier`, `.WalletRow__options-actions`, and `.Wallets__fail` CSS/attribute orphans. - Hand-added new i18n keys to en/pt since build:extension:translations is broken on master (pre-existing, out of scope here). * test(wallets): rename via the account header instead of the row menu The per-row ellipsis dropdown (and its wallet-row-options testid) is gone; rename now acts on the active account through the header pencil button. Repoints "Renames wallets" at wallets-header-edit-name, and scopes its final assertions to the header/row locators specifically since the new name now legitimately renders in two places (and this seed phrase has several accounts sharing the row list). Also updates "Loads wallets data and token prices on Mainnet in batches": address and balance are no longer a single concatenated string, so each per-account assertion now checks a row's balance cell scoped by its address rather than matching literal "address - $balance" text. * fix(wallets): stop the add-wallet button's className from erasing its own styles SDS Button assigns its own className internally and then spreads the rest of its props over the element, so any className passed in by a caller replaces (rather than merges with) Button/Button--tertiary/etc. instead of augmenting them. That silently dropped every rule meant for the restyled add-wallet button, leaving it transparent, gray, and sized off its unconstrained icon/text content. Wrap the Button in a plain div instead and scope the token overrides through that wrapper, targeting `.Button` and `.Button__icon` by descendant selector so they win on specificity without touching the className prop at all. Confirmed via computed-style checks that the button now resolves to the intended lilac-02/09/11 tokens at a 34px pill height, matching the design spec. * fix(wallets): align the wallets screen with the Figma spec Corrects eight deviations from Figma frame 9573-23172: - Header name and address are now one tight block; the 16px gap applies only between the identicon, that block, and the action row. - QR icon qr-code-01 -> qr-code-02, pencil icon edit-05 -> edit-01. - The divider sits inside the 24px content gutter instead of bleeding to the window edges, and is held 16px off the list below it. - Row avatars lose their border and white fill for a flat gray circle, with the identicon inset to 16px inside the 40px avatar. - The active row's selected state drops its colored border in favor of a 16px navy badge flush to the avatar's bottom-right corner. - "Imported" is separated from the address by a bullet. - The add-wallet control keeps its tint on the icon chip alone rather than behind the whole button, at medium weight. The button's copy becomes "Add wallet", which already exists as a key (it is the AddWallet sheet's own title), so this adds no new strings and retires the now-orphaned "Add a wallet". That collision is also why the e2e click moves to the add-wallet testid: the label alone would be ambiguous once the sheet it opens is on screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(wallets): match the QR screen's copy toast, drop the row hover - The copy button now shows the same success/error toast the QR screen shows, replacing CopyText's inline "Copied!" balloon. The two surfaces do the same thing, so they should confirm it the same way. The metric moves into the handler and fires only after the clipboard write resolves, so failed copies still aren't counted. - Restores SDS's `xl` padding on the add-wallet button. Its 16px/24px text already matches the spec, so only the colors, gap and weight stay overridden; the tint remains on the icon chip alone. - Removes the wallet row's hover background. "Copied!" stays in the catalog — AccountHeader still uses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(wallets): repair the medium font weight, tighten add-wallet spacing `--font-weight-medium` is defined nowhere — not in this repo and not in SDS, which names it `--sds-fw-medium`. An undefined custom property makes the whole declaration invalid, and because font-weight inherits, every element referencing it silently rendered at the body's 400. Measured in a browser before the fix, the add-wallet label, the header name, the row names and the balances were all 400; the spec calls for 500 on each. Swapping the six occurrences in these two files for `--sds-fw-medium` and `--sds-fw-regular` puts them at 500/400 as the spec's Text/MD/500, Text/SM/500 and Text/SM/400 styles require. This also means the earlier "less bold" pass on the add-wallet label was not setting medium at all, only cancelling SDS's semi-bold down to regular by accident. The same undefined token is referenced from BackButton, TransactionHeading, Notification and AmountCard among others, all with the same silent fallback. Those are left alone here. Also drops the add-wallet button's left padding so its icon chip lines up with the list's avatars in the 24px gutter, and gives the content footer an even 8px above and below, scoped with :has() so no other view's footer changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(wallets): gate stellar.expert to supported networks, label the row Addresses Copilot review on #2933. The explorer action was gated on `!isCustomNetwork`, which only excludes STANDALONE. Experimental mode switches the active network to Futurenet (saveExperimentalFeatures), and stellar.expert has no Futurenet explorer, so the action rendered and built an unsupported /explorer/futurenet/ URL. This is long-standing logic from #616 rather than a regression here, but #2930 removed the QR screen's copy, leaving this the only caller — so it gets fixed here. Note that switching to the shared `getStellarExpertUrl` helper alone does not fix it: the helper falls through to /public for anything that isn't testnet, so an ungated Futurenet link would render a *mainnet* lookup for a Futurenet account — wrong data, silently. The gate is what matters, so this adds `isStellarExpertSupported` beside the helper to keep the two together, with unit tests pinning the Futurenet and custom-network exclusions and documenting that fallback. WalletRow gains `role="button"` and `aria-current`, matching BalanceRow, the sibling list row. The active account was previously conveyed only by the badge on its avatar, which says nothing to a screen reader. Left alone: full keyboard focusability. No row component in this codebase (BalanceRow, ProtocolRow) sets tabIndex or key handlers, so making this one a semantic button would be inconsistent without addressing that codebase-wide. Also adds e2e coverage for the copy action, asserting the clipboard contents and the toast. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(balances): spinner while pending, $0.00 when unpriced Ports the balance loading/display behavior from freighter-mobile#943. Wallets list rows A row shows a spinner while that account's own total is still pending, instead of the literal "...". Totals arrive in batches of 6, so the guard is per-account (`!accountValue && isFetchingTokenPrices`) rather than the global flag alone — rows 1-6 render real values while 7+ still spin, which is the one frame where this is observable. Once loading settles a total always renders, defaulting to $0.00 rather than an empty cell. The check is falsy rather than `== null` on purpose: the data hook writes "" for an account whose fetch threw, which is equally "no total". `accountValue` is also now optional, which it always was at runtime — Wallets/index.tsx hands `undefined` to a prop typed `string`. Home hero Shows the total whenever account data resolved, defaulting to $0.00 instead of collapsing when nothing is priced. That is every non-Mainnet network (no price feed) and any failed price fetch. No spinner: the extension awaits prices before its first success dispatch, so that window is already covered by the full-screen loader and a hero spinner would be unreachable. `hasError` still yields an empty string. There the balances themselves are unknown, a failure notification is already on screen, and `resolvedData` is null — so $0.00 would be both misleading and unsafe to compute (the `!hasError` short-circuit guards a `resolvedData!`). Token rows are untouched: unpriced tokens keep showing dashes. The assertions at Account.test.tsx:1065-1087 are the regression guard. Also adds `formatFiatAmount`, so the four sites that were assembling `$${formatAmount(roundUsdValue(x))}` share one zero. It coerces non-finite input rather than taking a default parameter, which would have covered `undefined` but let "" through as "$NaN". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(wallets): keep the screen usable when the wallets fetch fails The error branch returned the notification on its own, short-circuiting before SubviewHeader and View.Content. That left the screen with no close button, no account actions and no Add wallet — an error state the user could not navigate out of at all, short of closing the popup. The failure is now scoped to the list region, so the header, the active account block and the footer stay put. `dataState.data` is null in the error state, so the active key falls back to Redux, which still holds it from the last successful load — the chrome never needed the fetch. Also gives the Home error notifications their horizontal gutter. Home disables the content inset (`View.Content hasNoPadding`), so notifications have to bring their own margin or they run edge to edge; `.AccountView__fetch-fail` only set `margin-top`. Sides only, since `margin-top` is what animateNotification animates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(accounts): stop tagging phrase-derived accounts as "Imported" Recovering a wallet probes derivation indices 1..n and pre-loads any that are funded on Mainnet. Those were stored with `imported: true`, so every account but the first came back labelled "Imported" in the wallets list — which is what the user sees, and it is wrong: they are derived from the recovery phrase just taken, exactly like the ones `addAccount` produces from the same phrase, and that path stores them unflagged. "Imported" means an account brought in from a secret key, which the phrase cannot re-derive. Only importAccount should set the flag (plus hardware wallets, which get it from their key-id prefix in getStoredAccounts and are a separate notion). Matches mobile, which drives its label off `importedFromSecretKey`. Note this fixes new recoveries only. `getStoredAccounts` rehydrates the flag from the keystore's `extra.imported` on every unlock, so wallets recovered before this change keep the wrong label until they are recovered again. Correcting those needs a migration that rewrites the keystore, which is only possible while unlocked — deliberately not attempted here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(home): show $0.00 in the hero on error instead of hiding it Drops the last case where the total was hidden. The hero now renders a value whenever the screen does: non-Mainnet networks, a failed price fetch, and now a failed balances fetch alike. This aligns three things that had drifted apart. Mobile's hero has no error branch at all — it renders `formattedBalance`, which is a summed zero when nothing resolved, and surfaces the failure separately in the tokens list. The wallets rows already settle on $0.00 for an account whose fetch failed. Only the extension's hero still collapsed, which made the same underlying failure look different in two places. Safe without the previous `hasError` guard: `balances` already falls back to [] and `tokenPrices` to undefined, so getTotalUsd sums nothing and returns zero without touching the null `resolvedData`. The earlier guard was load-bearing only because the expression used to dereference `resolvedData!.networkDetails`, which this branch removed. The failure is still reported — the "Failed to fetch your account balances." notification is unchanged, and the token rows still show dashes rather than zeros. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(home): repair the fullscreen notice margin and the error identicon Two things that only showed up in the error state. `.AccountView__fullscreen` carried `margin-top: 24px 24px 0`. `margin-top` takes a single value, so the declaration was invalid and dropped whole — the fullscreen notice rendered flush against the window edges with no spacing at all. It now uses the same gutter as `__fetch-fail`, with a bottom margin too so it isn't glued to the content beneath it. The Home header passed `resolvedData?.publicKey || ""` to AccountHeader, and `resolvedData` is null in the error state, so the identicon was handed an empty key and rendered blank. The account *name* kept working because it reads from Redux — hence a named account with no avatar. The key now falls back to Redux as well, which also repairs the header's copy button and account-details link in that state. Same fix the wallets list already had; this is why the identicon showed there but not here. No unit assertion for the identicon: jsdom has no canvas, so createStellarIdenticon().toDataURL() returns one constant for every input, including "". A test would have passed with the bug still in place — confirmed by trying it. Verified in a real browser instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(layout): let the scrollable view fill the window in fullscreen The Home layout's vertical borders stopped partway down the window in fullscreen, leaving the sides unframed below the content. `View--scrollable` set `height: 100%`, which overrides the base `height: 100dvh`. The parent chain (a bare wrapper div, body, html) has no height of its own, so that percentage resolved to `auto` and the layout was only ever as tall as its content. In the popup this is invisible, because the base `min-height` is the popup height — the mismatch only shows once the window is taller than the content. Home is the only view with this class (Router passes isScrollableView for "/"), and it has to be able to grow past the viewport rather than clip like the fixed-height views. So: grow with the content, but never be shorter than it. `View__content` is already `flex: 1`, so it absorbs the slack and the inset borders run the full height. Measured at 900px and 1400px viewports: the layout and the lowest bordered inset now both end exactly at the window bottom, where the layout previously stopped at 602px. Popup dimensions are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(account): one funding action on the unfunded empty state "Add XLM" rendered unconditionally and Friendbot was appended below it, so every test network showed two stacked funding buttons competing for the same job. Where a friendbot exists it is how you fund the account, so it now replaces "Add XLM" rather than sitting under it, and takes the same `secondary` style — it is the primary action of this empty state, not a lesser alternative offered alongside one. Mainnet is unaffected: no friendbot there, so "Add XLM" still renders and still routes to Add Funds. Both e2e consumers stay valid — the login helper drives Friendbot on Testnet, and buyWithOnramp switches to Mainnet before clicking "Add XLM". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style(account): bump the unfunded funding button one size up md -> lg on both branches of the funding action, so the empty state's only call to action carries more weight. 32px tall becomes 40px with roomier padding; SDS keeps the 14px text across both sizes, so this grows the target without changing the type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(layout): restore the popup's fixed height floor 24b88d4 replaced the base `min-height: var(--popup--height)` with `min-height: 100dvh` to make the layout fill the window in fullscreen. That regressed the popup: with only a few tokens it opened short. `100dvh` is circular there. Chrome sizes the popup window to the document, so the floor depended on the viewport, which depended on the content, which depended on the floor; a short account settled well under 600px. The fixed `--popup--height` is what broke that cycle, and dropping it removed the only thing holding the popup open. Takes the larger of the two instead, so both cases hold: the fixed floor governs the popup, and 100dvh still wins in fullscreen where the window is taller than the popup height. Measured with a single-token account. Before, a 300px viewport produced a 460px layout; now every viewport at or below 600px produces exactly 600px, and 900px still produces 900px. The original check missed this because it measured at 360x600, where the viewport already equalled the popup height and masked the absent floor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(identicon): drop the now-unused getColorPubKey WalletRow's redesign removed the last caller — it used this to tint the selected row's border, which the new design replaces with a fixed navy badge. The export then had no references anywhere in the repo. `no-unused-vars` cannot see it because the symbol is exported, so it would have stayed green in CI indefinitely rather than surfacing later. Only the getColorPubKey block goes. `publicKeyToBytes` and `HSVtoRGB` stay: drawIdenticon and setFillStyle still call them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(i18n): prune unused keys and finish the pt translations The scanner is configured with `keepRemoved: true` and `removeUnusedKeys: false`, so it only ever adds — unused keys had built up to 92 of 870. Removed 79 of those. The other 13 are used in ways the scanner cannot see, and are kept deliberately: - The four `Authorization *` flag labels live in a bitmask lookup map and reach the UI through `t(label)` (Operations/index.tsx). - Three simulate errors are thrown as Error messages and can be rendered by ErrorBoundary's `t(errorMessage)`. - Six more sit in literal `t()` calls the parser still misses — either nested inside a template literal (ImportAccount) or called through a destructured `t` parameter (muxedAddress). That last group means "the scanner dropped it" is not sufficient on its own, so every candidate was checked against the source: 65 appear nowhere outside the catalogs, and the remaining 14 matched only as substrings of live keys, stale variants (a copy since given a trailing period), comments, or unrelated non-i18n strings. Verified by re-running the scanner in its normal additive mode: it re-added nothing, so no removed key is reachable from source. Also translated the 20 pt entries still sitting in English, following the catalog's own conventions — "flagged as" as "sinalizado como", swap as "troca"/"Trocar", trustline and token left as-is. The 19 that remain identical are brands (Blockaid, Stellar, the network names), tickers and formats (XLM, XDR, wasm), and loanwords standard in pt-BR (dApps, Feedback, Links, Status, Memo, Hash, Salt). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(i18n): drop "Hidden collectibles" again after the merge The conflict resolution on the last master merge restored this key, which 18f381f had removed as unused. It still has no source references — the only nearby matches are tests asserting a different string, "No hidden collectibles". Confirmed by re-running the scanner: it does not re-add the key. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(account): colocate the NotFundedMessage test with its component This PR added two tests under components/account/ and placed them inconsistently: WalletRow's sat beside its component, this one a level above in an account/__tests__/ directory the PR itself created. Colocation is the documented rule (testing.md: "Test files live in __tests__/ directories alongside the source they test") and was the only pattern present under account/ — the one pre-existing test there, AccountHeader's, is colocated. The new directory held exactly this file, so leaving it would have made it the precedent for that subtree. A plain move: the file imports through absolute module paths resolved by jest's moduleDirectories, and the default testMatch finds __tests__/ at any depth, so no imports and no config change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(balances): show "--" for an unknown total, "$0.00" only without a feed A missing total has two causes and they deserve different answers. Where the network prices no tokens, zero is the accurate total. Everywhere else an absent total means prices or balances could not be read, and "$0.00" asserts a balance the account may not have — so the hero and the wallets rows now show "--" there, the same thing the token rows show for a token with no price. Mainnet is the only network with a feed, so it alone can produce "--"; the others always read "$0.00", which keeps the common case consistent. WalletRow takes the network's pricing support as a prop rather than inferring it from the empty-string sentinel, so the two cases stay distinguishable at the render site. Also restores the account-data spy before asserting in the ERROR-state test. It ran after, so once that assertion changed, the failure left useGetAccountData mocked for the rest of the file and took three unrelated onboarding tests down with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(home): keep "$0.00" for an unfunded account An account holding nothing has a real total of zero, so the hero shows it even on a network that prices tokens. Without this an unfunded Mainnet account priced nothing, which read as an unavailable total and showed the dash. A failed fetch still shows the dash: funding is unknown there, so zero would be a claim rather than a fact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(formatters): extract the no-fiat-value placeholder to a constant The "--" shown when a USD amount cannot be determined was duplicated across seven files. Extracted as NO_FIAT_VALUE, next to formatFiatAmount: the two are halves of one decision — format a known amount, or state that there is none — and several call sites already import from there. Its doc records the distinction the duplicated literals could not: the placeholder means "unknown", while "$0.00" asserts a known zero, so an unfunded account or a network that prices no tokens keeps the zero. Comments describing what the user sees still say "--"; they are about the rendered output rather than the symbol. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(balance): replace the total-label ternary chain with early returns Four outcomes nested three ternaries deep did not read as the decision it is. getTotalUsdLabel states each case in order with an early return and a line on why that answer is right, which is where the zero-versus-unknown distinction actually lives. Placed beside getTotalUsd, whose result it formats, so the pair sits together and the branches are unit-testable without rendering the view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(wallets): decide the row total with getTotalUsdLabel too The rows were not applying a different policy — the decision had already been made a layer up. useGetWalletsData formats each account's total while fetching, so the row received a string and had to infer intent from which flavour of falsy arrived: "" for a failed account, undefined for one not fetched. The hook holds every input the helper wants (isFunded, tokenPrices, the computed total), so it calls getTotalUsdLabel directly and emits a finished label. Off Mainnet it fills one in for every account rather than leaving the map empty, so a missing entry now means exactly one thing: still loading. WalletRow renders what it is handed and no longer takes hasPriceFeed. Its tests drop to spinner-versus-label; which label is right is covered where that is now decided. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(swap): stop a failed price fetch hanging the token picker useGetTokenPrices reports failure by resolving with `tokenPrices: null`. That null reached heldToRecord, whose `tokenPrices: ApiTokenPrices = {}` default only fires on undefined, so it was indexed directly and threw. The throw escaped the lookup's async effect before its success dispatch, leaving the state at LOADING — and the picker counts IDLE and LOADING alike as loading, so "Swap to" span forever instead of falling back to held tokens. Guarded at every step rather than only the one that broke: - useSwapFromData normalizes the fetch result, as useGetSwapAmountData already did; its payload declares the field non-null, so the null was also a type lie. - SwapAsset normalizes at both places it feeds the lookup. The ternary there guarded the payload's type, not a null price map. - heldToRecord, balancesToHeldRecords, buildSwapSections and the lookup accept `ApiTokenPrices | null` and normalize at the leaf, so a future caller cannot reintroduce it. Present on master; not introduced by this branch. Covered by a test on balancesToHeldRecords, which fails on the null case alone if the leaf normalization is removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(balance): correct and clarify the zero-versus-unknown docs formatFiatAmount's doc still named a failed price fetch as a reason to show "$0.00". That case returns the placeholder now, so the doc pointed readers at the wrong answer. It states what the function does — any non-numeric input formats as "$0.00" — and hands the unknown case to NO_FIAT_VALUE. getTotalUsdLabel's zero branch spells out that a bare formatFiatAmount() is "$0.00", which the call alone does not show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(formatters): correct the stale note on the $0.00 fallback test Same "failed price fetch" claim the formatter's own JSDoc carried: that case renders the placeholder, and only genuinely-zero totals reach formatFiatAmount(). The earlier sweep excluded __tests__, which is how this one survived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the Home refresh project.
What changed
The Wallets screen, per Figma frame
9573-23172:New account header block above the list, representing the active account — identicon, name, truncated address, and four circular icon buttons:
account.public_key_copiedaccount.stellar_expert_openedRows now show the account's balance in its own right-hand column, and "Imported" sits inline beside the truncated address rather than on its own third line. Previously the balance was string-concatenated into the address subtitle (
"GD6X…244L - $1,149.23").Removed: the per-row ellipsis menu and everything behind it — the dropdown, its open/close state, and the click-outside listener.
wallets-list-720p.mov
Verification
yarn test:ci— 196/202 suites, 1520/1571 tests, identical to baselineyarn build:extension— cleanyarn test:e2e loadAccount.test.ts— 9 passed / 2 skipped, matching baselineyarn test:e2e allowList.test.ts— passes with zero edits (it drivesadd-wallet)🤖 Generated with Claude Code