fix(spark): address settings + onboarding UI/UX issues from #3762
Merged
Conversation
This was referenced Apr 23, 2026
Collaborator
Author
0a3ffd0 to
d664712
Compare
This was referenced Apr 25, 2026
6ac3787 to
ece95dc
Compare
e28d173 to
104549a
Compare
This was referenced Apr 29, 2026
ece95dc to
a918f3a
Compare
6503c1f to
c824aac
Compare
…h getServiceStatus
…rop empty Recovery method group
…etSparkStatus call
…tead of swallowing
… partial state is no longer silent
…y are queryable, not breadcrumbs
b032704 to
b8f5803
Compare
1904857 to
71ae313
Compare
…g invoice generation failure
b8f5803 to
08a1582
Compare
grimen
approved these changes
May 20, 2026
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.

Spark: settings + onboarding UI/UX fixes
What this PR does
Hardening pass on the self-custodial onboarding and settings surfaces. Tightens the manual backup and restore flows (auto-advance between word inputs, paste-and-skip on the first restore step, clearer error feedback, post-success routing that respects re-backup vs first backup), reorganizes the settings screen, fixes several bugs around the backup nudge banner and modal, and replaces the SDK status-check loop in the wallet provider with a snapshot-first lifecycle that uses
AbortControllerso the wallet no longer hangs on initial render when Spark services are slow.Restore phrase flow
The 12-word restore screen now treats step 1 as a fast path: if the user pastes the full phrase and it is a valid BIP39 mnemonic, the screen navigates straight to step 2 with the parsed words pre-filled instead of forcing them to scroll back to a continue button. Word-by-word entry also auto-advances to the next field when what the user typed is unambiguously a single BIP39 match — typing
abandonjumps to the next slot, but typingrunwaits because bothrunandrunwayexist. The mnemonic input was lifted toforwardRefwithuseImperativeHandleso the parent can drive focus programmatically; both restore and manual-confirm screens useMnemonicWordInputHandlefor this.The error UX on step 2 is no longer hidden behind a button press: when all twelve fields are filled and the joined phrase fails
validateMnemonic, the screen surfaces "Invalid backup phrase. Please check if the word order is correct." inline with a warning icon, in a container with reserved height so the layout doesn't jump when the message appears or disappears. The same reserved-space pattern was applied to the manual backup confirm screen.After a successful restore, the wallet is now marked as backed up via
setBackupCompleted(BackupMethod.Manual)(previously the restore path calledresetBackupState()which left the user looking like they still owed a backup).Manual backup confirm flow
Typing a correct word in the confirm screen now auto-focuses the next challenge input instead of waiting for a tap. The focus-advance hook (
useBackupConfirm) returns afocusRequestindex and aclearFocusRequestcallback; the screen mounts ref handles for each input and applies focus from the effect. Same pattern as the restore screen.The completion handler now distinguishes three cases:
sparkMigrationTransferringFundsscreen.reBackup: true, which renders the genericLL.common.success()copy instead of the first-time "Welcome to non-custodial" headline. The migration check uses both theuseMigrationCheckpointcheckpoint and the currentBackupStatus, because the alerts screen sets the checkpoint even when the user enters from settings — checking only the checkpoint misroutes re-backups into the migration screen.reBackup: false.The
sparkBackupSuccessScreenroute param shape was widened to{ reBackup?: boolean } | undefinedand the screen reads it fromuseRoute.Cloud backup screen polish
Password validation errors on the cloud-backup form now only show after the field has been blurred, not while the user is still typing. The cloud hero icon and the backup/restore method icons in onboarding switched to the success color, and the "Important"
InfoBannericon switched to the warning color, so they match the rest of the design system.Backup nudge
The dismissal cooldown only applies to the home banner; the settings banner now ignores it (
shouldShowSettingsBannerno longer waits for theloadedflag fromAsyncStorage.getItem(DISMISSAL_KEY)). The homeBackupNudgeModalis now gated behinduseIsFocused(), so it doesn't pop up on top of unrelated screens (e.g. settings) when the user has the modal threshold balance and walks away from the home tab.The threshold check moved from the active wallet's BTC sats to the combined display balance via
useTotalBalance. Mixed-currency users with USD weight that crosses the threshold no longer slip past the modal because their BTC alone was below it.Account-type selection card
The selected account-type card on the picker screen now uses
colors.grey6as the background. The previous color blended into the unselected variant on dark mode and made it hard to tell which option was selected.Self-custodial SDK lifecycle
Rewrote
useSdkLifecycleto be snapshot-first: a refresh triesgetSelfCustodialWalletSnapshotdirectly and only falls back to the operational status check on failure. Before this, every refresh ran agetSparkStatuscall upfront, and on cold start that call could hang past any practical timeout — leaving the home screen in an indefinite loading state even though the SDK had already initialized correctly. The new flow only consultsgetServiceStatuswhen the snapshot rejects, which is when the operational state actually matters.getServiceStatusitself was rewritten to useAbortControllerinstead ofPromise.race. The 5-second timeout aborts the underlyinggetSparkStatuscall cleanly, no orphan promises. The bridge wrapper forgetSparkStatusnow accepts an optionalAbortSignaland forwards it to the SDK's async-opts.The
OFFLINE_EXEMPT_STATUSESset keepsErrorandUnavailablestates from being downgraded toOfflineon a refresh failure, so a user with no mnemonic doesn't see "wallet offline" copy.Settings reorganization
"View backup phrase" sits under Security & privacy alongside
TotpSettingandOnDeviceSecuritySettingfor self-custodial wallets. An earlier draft introduced a standalone Recovery method group, but custodial users would have seen an empty card under that header (the entry only renders for self-custodial), so the section was collapsed back into the existing security group.The settings screen also skips its GraphQL queries when the user isn't authenticated, which fixes a flicker where the network requests fired against a missing token and produced a brief error state.
Receive polish
The displayed Lightning invoice now strips a leading
lightning:URI prefix before rendering. Some external sources prepend it; copying or showing it to the user added noise.i18n
New keys:
SettingsScreen.recoveryMethod, the post-successcommon.successreuse for re-backup, and the updatedRestoreScreen.invalidMnemoniccopy ("Invalid backup phrase. Please check if the word order is correct."). All 28 locales updated via the project's Python-script i18n workflow, preserving the existing per-locale indentation and trailing-newline quirks.Phrase.headerTitlewas retitled to "Backup phrase" (was "Your backup phrase") to match the new settings entry.Tests
3667/3667 green. New coverage includes the prefix-unique BIP39 advance and
clearFocusRequestinuse-bip39-input, theforwardReffocus exposure onMnemonicWordInput, the auto-navigate paste path inuse-restore-phrase, the focus-advance hook inuse-backup-confirm, the migration / re-backup routing branches inbackup-confirm-screen(including the no-funds case), thereBackupparam inbackup-success-screen, the AbortController + signal forwarding inis-online, the snapshot-first / Offline / Error / Loading→Offline transitions inwallet-provider, and the combined-balance threshold path inuse-backup-nudge-state. Existing specs were updated to drop the removed balance-stale heuristic and the oldresetBackupStatecall site.Breaking notes
None for consumers. The self-custodial wallet provider's status snapshot no longer maintains an internal
isBalanceStaleref/state, since the post-convert sync from the receive auto-convert pipeline (PR #3764) makes the heuristic unnecessary. All in-tree callers were updated. Custodial flows are untouched.