Skip to content

fix(spark): address settings + onboarding UI/UX issues from #3762

Merged
grimen merged 35 commits into
mainfrom
fix--spark-settings-and-onboarding-ui
May 20, 2026
Merged

fix(spark): address settings + onboarding UI/UX issues from #3762
grimen merged 35 commits into
mainfrom
fix--spark-settings-and-onboarding-ui

Conversation

@esaugomez31

@esaugomez31 esaugomez31 commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

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 AbortController so 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 abandon jumps to the next slot, but typing run waits because both run and runway exist. The mnemonic input was lifted to forwardRef with useImperativeHandle so the parent can drive focus programmatically; both restore and manual-confirm screens use MnemonicWordInputHandle for 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 called resetBackupState() 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 a focusRequest index and a clearFocusRequest callback; 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:

  1. Migrating from custodial with funds → routes to the existing sparkMigrationTransferringFunds screen.
  2. Re-backup from settings (already backed up) → routes to the success screen with reBackup: true, which renders the generic LL.common.success() copy instead of the first-time "Welcome to non-custodial" headline. The migration check uses both the useMigrationCheckpoint checkpoint and the current BackupStatus, 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.
  3. Fresh manual backup → routes to the success screen with reBackup: false.

The sparkBackupSuccessScreen route param shape was widened to { reBackup?: boolean } | undefined and the screen reads it from useRoute.

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" InfoBanner icon 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 (shouldShowSettingsBanner no longer waits for the loaded flag from AsyncStorage.getItem(DISMISSAL_KEY)). The home BackupNudgeModal is now gated behind useIsFocused(), 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.grey6 as 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 useSdkLifecycle to be snapshot-first: a refresh tries getSelfCustodialWalletSnapshot directly and only falls back to the operational status check on failure. Before this, every refresh ran a getSparkStatus call 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 consults getServiceStatus when the snapshot rejects, which is when the operational state actually matters.

getServiceStatus itself was rewritten to use AbortController instead of Promise.race. The 5-second timeout aborts the underlying getSparkStatus call cleanly, no orphan promises. The bridge wrapper for getSparkStatus now accepts an optional AbortSignal and forwards it to the SDK's async-opts.

The OFFLINE_EXEMPT_STATUSES set keeps Error and Unavailable states from being downgraded to Offline on 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 TotpSetting and OnDeviceSecuritySetting for 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-success common.success reuse for re-backup, and the updated RestoreScreen.invalidMnemonic copy ("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.headerTitle was 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 clearFocusRequest in use-bip39-input, the forwardRef focus exposure on MnemonicWordInput, the auto-navigate paste path in use-restore-phrase, the focus-advance hook in use-backup-confirm, the migration / re-backup routing branches in backup-confirm-screen (including the no-funds case), the reBackup param in backup-success-screen, the AbortController + signal forwarding in is-online, the snapshot-first / Offline / Error / Loading→Offline transitions in wallet-provider, and the combined-balance threshold path in use-backup-nudge-state. Existing specs were updated to drop the removed balance-stale heuristic and the old resetBackupState call site.

Breaking notes

None for consumers. The self-custodial wallet provider's status snapshot no longer maintains an internal isBalanceStale ref/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.

@esaugomez31 esaugomez31 changed the title fix(spark): address settings + onboarding UI/UX issues from #686 fix(self-custodial): address settings + onboarding UI/UX issues from #686 Apr 23, 2026
@esaugomez31 esaugomez31 force-pushed the fix--spark-settings-and-onboarding-ui branch from 0a3ffd0 to d664712 Compare April 23, 2026 01:03
@esaugomez31 esaugomez31 changed the title fix(self-custodial): address settings + onboarding UI/UX issues from #686 fix(self-custodial): address settings + onboarding UI/UX issues from Apr 23, 2026
@esaugomez31 esaugomez31 marked this pull request as ready for review April 23, 2026 01:04
@esaugomez31 esaugomez31 self-assigned this Apr 23, 2026
@esaugomez31 esaugomez31 marked this pull request as draft April 23, 2026 01:04
@esaugomez31 esaugomez31 marked this pull request as ready for review April 26, 2026 18:15
@esaugomez31 esaugomez31 requested review from Copilot and grimen and removed request for Copilot April 27, 2026 17:57
@esaugomez31 esaugomez31 force-pushed the fix--spark-settings-and-onboarding-ui branch from 6ac3787 to ece95dc Compare April 28, 2026 16:27
@esaugomez31 esaugomez31 force-pushed the feat--spark-stable-balance-and-usd-conversion branch from e28d173 to 104549a Compare April 28, 2026 16:27
@grimen grimen changed the title fix(self-custodial): address settings + onboarding UI/UX issues from fix(spark): address settings + onboarding UI/UX issues from Apr 28, 2026
@esaugomez31 esaugomez31 force-pushed the fix--spark-settings-and-onboarding-ui branch from ece95dc to a918f3a Compare May 6, 2026 02:05
@esaugomez31 esaugomez31 force-pushed the feat--spark-stable-balance-and-usd-conversion branch 2 times, most recently from 6503c1f to c824aac Compare May 7, 2026 15:54
@grimen grimen force-pushed the fix--spark-settings-and-onboarding-ui branch from b032704 to b8f5803 Compare May 20, 2026 12:13
@grimen grimen force-pushed the graphite-base/3762 branch from 1904857 to 71ae313 Compare May 20, 2026 12:13
@graphite-app graphite-app Bot changed the base branch from graphite-base/3762 to main May 20, 2026 12:13
@grimen grimen force-pushed the fix--spark-settings-and-onboarding-ui branch from b8f5803 to 08a1582 Compare May 20, 2026 12:13
@grimen grimen self-requested a review May 20, 2026 12:22
@grimen grimen merged commit b221e04 into main May 20, 2026
7 of 9 checks passed
@grimen grimen deleted the fix--spark-settings-and-onboarding-ui branch May 20, 2026 12:23
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.

2 participants