Skip to content

fix: restore the Home tab the user actually left (WALLET-1360) - #1433

Open
ost-ptk wants to merge 7 commits into
release/2.7.0from
WALLET-1360-going-back-from-send-custom-tokens-moves-to-activity-tab-instead-of-token-tab
Open

fix: restore the Home tab the user actually left (WALLET-1360)#1433
ost-ptk wants to merge 7 commits into
release/2.7.0from
WALLET-1360-going-back-from-send-custom-tokens-moves-to-activity-tab-instead-of-token-tab

Conversation

@ost-ptk

@ost-ptk ost-ptk commented Jul 30, 2026

Copy link
Copy Markdown
Member

Description

Going back from a token, an NFT or a deploy could land the user on the wrong Home tab — the ticket reports Activity, but NFTs happens too, depending only on what the user did earlier.

Root cause

The active Home tab lived in the Tabs component's local useState, initialised once from location.state.activeTabId. React Router binds state to a history entry, and nothing wrote the user's tab click back into it. So:

  1. A completed send / stake, or Back from NFT or deploy details, pushed a Home entry carrying activeTabId.
  2. The user then clicked a different tab — that changed only the component's useState; the history entry kept the old value.
  3. Opening a token pushed a new entry; Back (navigate(-1)) returned to that same Home entry, and Tabs re-initialised from the stale activeTabId.

The underlying design problem is that one field served two different jobs: restore the tab the user was on, and override it to Activity after a submission. Because both wrote location.state.activeTabId, an override stuck to the history entry permanently and later impersonated a restore.

Fix

  • New HomeTabProvider / useHomeTab (src/apps/popup/hooks/use-home-tab.tsx) owns the active tab for the popup session. It is mounted inside <HashRouter> above <AppRoutes>, which matters: AppRoutes branches on showNavigationMenu and renders a separate <Routes> tree that unmounts Home.
  • Tabs is now controlled-optional and addresses tabs by name instead of position in the children array. HomePageTabsId (numeric indices that were never tied to the render order) is gone; HomePageTabName is the single source of truth. RecipientTabs keeps working uncontrolled and is unchanged.
  • The four sites that mean "send the user to Activity" now say so directly via setActiveHomeTab. The two that meant "go back" are plain navigate(-1), since the session owner already knows the tab — which made the navigateHome prop chain through DeployPlate dead, so it is deleted along with LocationState.activeTabId.
  • The tab stays ephemeral: no Redux slice, no storage.local. Closing the popup (or auto-lock) resets it to Tokens, as before.

Rider: useNavigationMenu discarded the rest of location.state

Separate pre-existing bug, fixed here because it lives in the same mechanism. The three helpers replaced the whole state object with { showNavigationMenu }. Consequences today, independent of the tab bug:

  • Deploy details renders blank after opening and closing the burger menu — it reads location.state.deploy and its route /deploys-details carries no parameter, so there is no fallback and useFetchSingleDeploy never fires.
  • NFT transfer loses its preview mid-flow — transfer-nft reads location.state.nftData.
  • Token details only degrades, recovering via the :tokenName param.

The fix spreads the existing state. Purely additive.

Second rider: the transfer success screen named a tab that does not exist

The success copy read "You can check its status in the Transactions / Deploys tab on your Wallet home page", branching on isCasper2Network. Neither tab exists — the Home tabs are Tokens, NFTs and Activity, and have been for some time.

It was also a template literal inside <Trans>, so i18next-parser never extracted it and no catalog carries a translation for it. Making the sentence static fixes the extraction too, and dropping the network branch leaves selectIsCasper2Network unused in the component.

Notes for reviewers and QA

  • Wider behaviour change than the ticket describes. Because the tab is session-scoped rather than reset by every stateless navigate(Home), every "go Home" path (header Close/Cancel/Done, contacts, account settings, rename/remove account, RateApp completion, account and network switches) now returns to the last-used tab instead of always Tokens. That is what the design asks for, but it is the thing most likely to surprise manual testing.
  • Please manually verify "Done → Activity". It has no automated coverage and is unreachable in the e2e environment: on fresh mock state askForReviewAfter is null, so Done always routes to the RateApp screen. In production any returning user who has already seen the rate prompt takes the Home branch. Repro: dismiss the rate prompt once, then send CSPR → Done should land on Activity.
  • Please retest NFT transfer with the burger menu, per the rider above.
  • Tab widths: computed to be unchanged, but worth a glance. ActiveTabContainer / TabContainer now use flex: 1 instead of width: calc(33% - 8px). These already inherited flex-grow: 1 from TabsContainer's flexGrow={1}, so with three tabs the old rule resolved to a base of 0.33·w − 8 plus an equal share of the 0.01·w + 24 free space — exactly w/3, which is what flex: 1 gives directly. The change also removes the component's undocumented three-tabs-only constraint. Note this affects the recipient tab strip (Recent / My accounts / Contacts) in the transfer flow as well as Home, so it is worth a quick look at both.
  • Follow-up worth a ticket: TransferSuccessScreen lives in src/libs/ui/components/ and is re-exported from the barrel all five apps import, but it now requires a HomeTabProvider ancestor. All current call sites are popup pages, so it is safe today; moving it under src/apps/popup/pages/ would make the coupling honest and drop it from the other four bundles.

Testing

  • npm run ci-check — passes (405 jest tests, 0 lint errors, tsc clean, coverage gates met).
  • New e2e-tests/popup/common/home-tabs.spec.ts — four scenarios, all of which failed before the fix and pass after it: the NFTs variant, the Activity variant, the tab surviving a menu toggle, and deploy details surviving a menu toggle.
  • Full e2e-tests/popup suite — no regressions. One unrelated flake (signature-request-scenarios › should cancel the signing process) passed on retry.

Linked tickets

WALLET-1360

Checklist

On localization: this PR deliberately does not regenerate lang/. Running npm run locale:extract_pot produces 410 new msgids, of which only one is from this PR — the other 409 are accumulated drift from strings added to the codebase since the last extraction, which is untracked in every one of the 10 locales. Committing that here would bury the actual change. The one new string renders in English (i18next falls back to the key), exactly as the sentence it replaces did — it was never extractable either. The catalog drift deserves its own ticket.

  • Include a screenshot or recording if implementing significant UI or user flow change

  • When this PR affects architecture changes wait for review from Dmytro before merging

ost-ptk added 7 commits July 30, 2026 13:33
Adds four failing Playwright scenarios for WALLET-1360: the Home tab is
rebuilt from whatever activeTabId an earlier navigation stamped onto the
history entry, so back from token details lands on NFTs or Activity.
Also covers the navigation menu discarding location.state.
Introduces a popup-session-scoped owner for the Home tab, mounted inside
HashRouter above the navigation-menu branch so it survives every unmount
of the Home page. Not consumed yet.
Tabs is now controlled-optional and addresses tabs by name instead of by
position in the children array. Home reads and writes the active tab
through HomeTabProvider, so back navigation restores the tab the user
actually left rather than the one an earlier navigation intended.

Also replaces the hardcoded 33% tab width with flex: 1, which removed
the component's undocumented three-tab-only constraint.
The four sites that mean 'send the user to Activity' now say so directly
via setActiveHomeTab; the two that meant 'go back' are plain navigate(-1),
because the session owner already knows which tab to return to.

Removes LocationState.activeTabId, LocationState.navigateHome, the
navigateHome prop chain through DeployPlate, and HomePageTabsId, whose
numeric indices were never tied to the order the tabs render in.

Deleting activeTabId from LocationState also required removing the Home
page's scroll effect that was keyed on it. That effect either duplicated
TokensList, which already scrolls to top on mount, or competed with the
position restoration in NftList and DeploysList; each list owns its own
scroll behaviour now.
The three helpers replaced the whole state object with { showNavigationMenu },
discarding everything the current route depends on. Deploy details reads
location.state.deploy and its route carries no parameter, so opening and
closing the menu left the page blank.
Deriving the default tab from children[0] threw on an empty array, where
the previous index-based implementation rendered nothing; the type does
not rule an empty array out.

The internal tab state was also written on every click even when the
parent supplies activeTabName and owns the value, letting the shadow
state drift from the controlled one.
The success copy told the user to check the 'Transactions' or 'Deploys'
tab depending on the network, but neither exists — the Home tabs are
Tokens, NFTs and Activity, and have been for some time.

The sentence was a template literal inside <Trans>, so i18next-parser
never extracted it and no catalog carried a translation. Making it a
static string fixes that too. Dropping the network branch leaves
selectIsCasper2Network unused in this component.
@ost-ptk
ost-ptk requested a review from Comp0te July 30, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant