Skip to content

feat(wallet): show the lifecycle overlay from the first moment of Add Wallet - #1064

Merged
llbartekll merged 1 commit into
developfrom
feat/add-wallet-overlay
Aug 25, 2026
Merged

feat(wallet): show the lifecycle overlay from the first moment of Add Wallet#1064
llbartekll merged 1 commit into
developfrom
feat/add-wallet-overlay

Conversation

@llbartekll

@llbartekll llbartekll commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Issue being fixed or feature implemented

Stacked on #1062 (review it first). Wallets → Add Wallet → Create/Import blocked the MainActor for ~5 s before any indicator appeared: ~2.2 s of wallet-creation FFI on the current network, then ~2.9 s for the other network's mirror (SDK build ~1.6 s + creation + temp-manager shutdown) — all before the post-add switch finally raised the overlay. Simulator logs: tap at 14:48:20.206, overlay only at 14:48:25.331.

What was done?

  • New phase .addingWallet(isImport:) with a "Creating wallet…" / "Importing wallet…" card ("Preparing your wallet. This may take a few seconds."). Admitted from idle; the composite pair .addingWallet → .switchingWallet lets the existing gated switch advance the window Creating → Switching without dropping through idle. The pair is deliberately not owner-scoped — the blocking overlay window covers every other interactive entry point for the phase's lifetime (noted in the matrix).
  • WalletsViewModel.addWallet begins the phase before provisioning and sleeps 100 ms so UIKit commits the overlay window before the blocking work starts (the same trick as the Obj-C wipe's dispatch_after; the ordering is provable — the phase-apply Task is enqueued synchronously by the @Published sink before the sleep's continuation exists). Every exit pairs the begin: provisioning error and .alreadyExistsfinish(); .added hands the phase to gatedSwitchWallet (whose previousId correctly captures the pre-add wallet — host.addWallet is additive and never publishes); a rejected post-add admission defensively finishes only when the phase is still .addingWallet.
  • 🔁 WALLETOP add begin/done/FAILED telemetry with duration.
  • Admission-matrix table test gains the .addingWallet rows and the composite-continuation case.

Scope note: this is the cosmetic stage — the MainActor still blocks under the overlay (the SwiftUI spinner may pause; the card and copy stay visible). The real fix (an async off-main createWallet variant in the Swift SDK mirroring the #4469 destroyQueue pattern, plus lifecycle-queue serialization of the add) follows as a separate platform + app PR pair.

How Has This Been Tested?

  • DashPay Debug build for a generic iOS Simulator (platform v4.2-dev @ 1e26927c61): BUILD SUCCEEDED.
  • Simulator smoke (testnet): Create New Wallet — overlay card up immediately after the tap, advancing Creating → Switching over the still-presented sheet without an idle flicker, landing on Home with the new wallet active.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added clear progress feedback while creating or importing a wallet.
    • Wallet creation and import can now transition directly into wallet switching for a smoother experience.
  • Bug Fixes

    • Improved lifecycle handling when wallet provisioning fails or a duplicate wallet is detected.
    • Prevented unnecessary intermediate states during wallet setup and switching.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 93b49ac3-cf2e-45e4-b98e-c62dd5db8f00

📥 Commits

Reviewing files that changed from the base of the PR and between 08c3be5 and 4d34115.

📒 Files selected for processing (4)
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/WalletLifecycleTransitionState.swift
  • DashWallet/Sources/UI/Main/WalletLifecycleOverlay.swift
  • DashWallet/Sources/UI/Menu/Security/Wallets/WalletsViewModel.swift
  • DashWalletTests/WalletLifecycleTransitionStateTests.swift

📝 Walkthrough

Walkthrough

The wallet lifecycle now tracks wallet creation and import operations. The overlay displays phase-specific progress text. Wallet provisioning handles transition cleanup and direct progression to wallet switching. Tests cover the new admission rules.

Changes

Wallet Addition Lifecycle

Layer / File(s) Summary
Lifecycle phase and admission rules
DashWallet/Sources/Infrastructure/SwiftDashSDK/WalletLifecycleTransitionState.swift, DashWalletTests/WalletLifecycleTransitionStateTests.swift
Adds Phase.addingWallet(isImport:), its log label, and admission rules for idle-to-add and add-to-switch transitions. Tests cover the new phase and direct transition.
Wallet addition progress overlay
DashWallet/Sources/UI/Main/WalletLifecycleOverlay.swift
Presents the overlay during wallet addition. The progress card shows separate text for importing and creating a wallet.
Wallet addition orchestration
DashWallet/Sources/UI/Menu/Security/Wallets/WalletsViewModel.swift
Starts the addition phase before provisioning, finishes it on failure or duplicate results, and preserves switch failure handling during post-add switching.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WalletsViewModel
    participant LifecycleState
    participant LifecycleOverlay
    participant WalletProvisioning

    User->>WalletsViewModel: addWallet(isImport)
    WalletsViewModel->>LifecycleState: begin addingWallet
    WalletsViewModel->>LifecycleOverlay: show progress card
    WalletsViewModel->>WalletProvisioning: provision wallet
    WalletProvisioning-->>WalletsViewModel: result
    alt Provisioning succeeds
        WalletsViewModel->>LifecycleState: begin switchingWallet
    else Provisioning fails or duplicates
        WalletsViewModel->>LifecycleState: finish addingWallet
    end
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-wallet-overlay

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@llbartekll
llbartekll requested a review from romchornyi August 25, 2026 15:39

@romchornyi romchornyi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and built this. Small, careful change — no blockers. Two non-blocking notes below.

Build

feat/add-wallet-overlay against platform v4.2-dev @ 1e26927c61, the revision the description names. Only local modification is the SwiftDashSDK package path; no source patches.

0 compile errors
0 linker errors
no warnings in the changed files
Ld .../dashpay.app/dashpay.debug.dylib
Ld .../dashpay.app/dashpay
** BUILD SUCCEEDED **

Verified

The gate does not leak. Every exit after tryBegin(.addingWallet) either releases the phase or hands it to an owner:

Exit Release
addWallet throws state.finish()
.alreadyExists state.finish()
.added, switch succeeds finish() inside gatedSwitchWallet
.added, admission rejected if case .addingWallet { finish() }
.added, real switch failure .failedWalletSwitch, the card owns recovery

try? await Task.sleep introduces no early return — a cancellation is swallowed and the flow continues into the do/catch.

The previousId claim holds. I checked SwiftDashSDKHost.addWallet (639-698): it only walks networksToCreate calling createAndPersist, never assigns self.wallet, and never touches the active-wallet registry. So gatedSwitchWallet captures the pre-add wallet and Switch Back points where it should.

The un-scoped composite is safe here. Every interactive entry into .switchingWallet — the row tap, the Home "Switch Wallet" shortcut, the overlay's Retry / Switch Back — is either covered by the blocking window or requires a .failed* phase, so in practice only the add flow can reach (.addingWallet, .switchingWallet). The reasoning in the comment matches what the code allows.

The tests were not weakened. The exception in testBusyPhasesRejectEveryBegin is scoped to exactly the addingWallet → switchingWallet pair rather than skipping the row wholesale, and testAddFlowCompositeAdvancesToSwitch pins the continuation separately.

Notes

The rejected-admission alert cannot be seen. In

guard state.tryBegin(.addingWallet(isImport: isImported)) else {
    errorMessage = SwiftDashSDKWalletRuntime.SwitchError.switchInProgress.localizedDescription
    return nil
}

a rejection means another operation already holds the gate — which means the blocking overlay window is already up at .alert + 1. errorMessage surfaces from the sheet in the main window at .normal, underneath it. This is the same class of issue as the silent Obj-C rejections fixed in #1062, reappearing at a new entry point. Either render it on the overlay card or drop the message and just return.

The scope note is honest, and the ticket should stay open. The MainActor still blocks for ~5 s under the overlay, so the SwiftUI spinner freezes while the card and copy stay up. That trades "frozen app" for "frozen card with an explanation" — a real improvement, but not the fix. Worth making sure the follow-up (async off-main createWallet mirroring the #4469 destroyQueue pattern) does not get closed out by this PR.

Merge order

This is stacked on #1062 and must land after it. #1062's branch has already been force-pushed once during a rebase; another one will require rebasing this branch too.

@llbartekll
llbartekll changed the base branch from feat/wallet-lifecycle-overlay to develop August 25, 2026 17:03
… Wallet

Wallets -> Add Wallet blocked the MainActor for ~5s (wallet-creation FFI
on the current network, then the other network's SDK build + creation)
before the post-add switch finally raised the overlay - the app looked
frozen and was dead to touch with no indicator.

- new phase .addingWallet(isImport:) with a Creating/Importing wallet…
  card; admitted from idle, and the composite continuation
  (.addingWallet -> .switchingWallet) lets the existing gatedSwitchWallet
  advance the window Creating -> Switching without dropping through idle
  (the pair is not owner-scoped on purpose - the blocking overlay window
  covers every other interactive entry point for the phase's lifetime)
- WalletsViewModel.addWallet begins the phase before provisioning and
  sleeps 100 ms so UIKit commits the overlay window before the blocking
  work starts (same trick as the Obj-C wipe's dispatch_after); every
  exit pairs the begin: provisioning error and .alreadyExists finish(),
  .added hands the phase to the gated switch, and a rejected post-add
  admission defensively finishes only if still .addingWallet
- WALLETOP add begin/done/FAILED telemetry with duration
- admission-matrix table test gains the .addingWallet rows and the
  composite-continuation case

This is the cosmetic stage: the MainActor still blocks under the
overlay. The real fix (async off-main createWallet in the Swift SDK +
lifecycle-queue serialization) follows separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@llbartekll
llbartekll force-pushed the feat/add-wallet-overlay branch from fed937c to 4d34115 Compare August 25, 2026 17:05
@llbartekll
llbartekll merged commit 738022e into develop Aug 25, 2026
1 of 2 checks passed
@llbartekll
llbartekll deleted the feat/add-wallet-overlay branch August 25, 2026 17:06
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