refactor(login): one entry point for first run and add-account - #449
Merged
Merged
Conversation
The Cancel button sat in a bottom overlay with a fixed 120pt inset, which put it on top of the Log In button, and the view already sets ignoresSafeArea(.keyboard, edges: .bottom) so a bottom anchor had no relationship to the keyboard at all. It moves to the top-leading corner. This is the only way out of add-account: the cover is presented with interactiveDismissDisabled(), so swipe-to-dismiss is off. White text on 15%-white also read as scenery rather than a button. The background goes to 92% white with a black label.
Adding a second account presented `LoginView`, its own screen, while a
cold launch presented `SplashView` with a bottom sheet. Two hand-written
copies of "choose an account", which had drifted apart in three ways
that cost real functionality:
- No watch-only. The splash sheet accepts an npub / nprofile and calls
`NostrKey.saveWatchOnly`; LoginView only ran `parseNsec`, so a
watch-only second account could not be added at all. Multi-account
supports it fine — `saveWatchOnly` → `save` → `addToAccountList`.
- No Apple or Google. Add-account offered nsec only, in the same build
that shows both buttons at first launch.
- No trimming. The sheet trims pasted input; LoginView passed the raw
string to `parseNsec`, so an nsec with a trailing newline (any
password manager) failed on one screen and worked on the other.
`SplashView` was already callback-shaped and holds no first-run-only
state, so it takes a `Mode` instead: `.addAccount` shows Cancel and
`.firstRun` keeps the launch behavior. ContentView grows one
`loginEntry(mode:)` carrying the Nostr sheet and the Google / Apple
covers, plus a shared `finishLogin`. What actually differs between the
two entry points turns out to be two lines — dismissing the cover, and
the shorter loading delay an account *switch* gets.
The 1.8s curtain that hides the launch animation's safe-area settling is
skipped in `.addAccount`: presented over a running app the safe area is
already stable, so it would only be 1.8s of blank screen after tapping
Add account.
The avatar collage stays in both modes. It costs a profile fetch and a
metrics socket per open, but it is the screen's design — without it
add-account would be a bare screen with three buttons, which is not
"one view" in any useful sense. `viewModel.cancel()` on disappear
already tears both sockets down.
Deletes LoginView.swift and its four project.pbxproj references — it
lived at the repo root, which is individually listed rather than a
fileSystemSynchronizedGroup.
Includes the Cancel-position commit from #446, which this supersedes:
that button now lives on the shared view, so the fix survives LoginView
being deleted instead of going with it.
barrydeen
approved these changes
Sep 16, 2026
barrydeen
left a comment
Owner
There was a problem hiding this comment.
Reviewed; the 'two paths differ by two lines' claim checks out line by line.
- First-run: every old splash branch (Nostr onLogin, Google onDone, Apple new/restored) maps 1:1 onto
loginEntry(mode: .firstRun)+finishLogin, including the only restructured branch — Apple brand-new still sets keypair/signUpExistingKeypair and routes to the sign-up wizard.accountSwitchInProgressremains add-account-only; onboarding-first ordering preserved both directions. - Add-account: old LoginView callback semantics reproduced exactly (dismiss cover, switch-delay flag only when already onboarded).
LoginView.swiftand all four pbxproj refs gone with zero surviving code references; the sole otherSplashView()caller is the #Preview, compiling via the defaulted mode.- The three gaps (watch-only npub, Apple/Google, trim) close for free since NostrLoginSheet becomes the single nostr path; #446's Cancel fix survives.
- Mode-gated curtain skip is right — the 1.8s hold exists only for launch-animation safe-area settling.
Non-blocking nits:
- Cancel hardcodes
.foregroundStyle(.black)on the white capsule — fine on the splash design, but one of the few hardcoded colors here. mode: Mode = .firstRundefaults silently to the uncancellable launch variant for any future call site; a required parameter is the safer contract.- Apple/Google in add-account (allows adding an account signed in elsewhere) is the product call the PR flags — fine to keep.
Note: no CI on the branch and no iOS toolchain at the reviewer's machine; build correctness relies on the author's simulator verification (and the pbxproj deletion check is 'build passes', which I couldn't run).
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.
Adding a second account presented
LoginView— its own screen — while a cold launch presentedSplashViewwith a bottom sheet. Two hand-written copies of "choose an account", and they had drifted apart in three ways that cost real functionality.Supersedes #446. That PR's commit is included here rather than rebased away, so the Cancel fix survives
LoginViewbeing deleted instead of going with it. #446 can be closed unmerged.The three gaps in add-account
No watch-only.
NostrLoginSheet.login()accepts an npub / nprofile viaNip19.decodeNostrUriand callsNostrKey.saveWatchOnly.LoginView.login()only ranparseNsec— its placeholder readnsec1...and its error didn't mention npub — so a watch-only second account couldn't be added at all. Multi-account supports it fine:saveWatchOnly→save→addToAccountList.No Apple or Google. Add-account offered nsec only, in the same build that shows both buttons at first launch (gated on
AppleAuthConfig.isConfigured/GoogleAuthConfig.isConfigured). A second account whose key lives in iCloud Keychain or Google had no way in.No trimming. The sheet trims pasted input;
LoginViewpassed the raw string toparseNsec. An nsec with a trailing newline — i.e. from any password manager — failed on one screen and worked on the other.Plus the cosmetic divergence you'd expect: different logo, title, button shape, and error copy.
The change
SplashViewwas already callback-shaped and holds no first-run-only state, so it takes aModeinstead:.firstRun— unchanged behavior, including the 1.8s curtain..addAccount— shows Cancel, skips the curtain.The curtain hides the launch animation's safe-area settling. Presented over a running app the safe area is already stable, so keeping it would mean 1.8s of blank screen after tapping Add account.
ContentViewgrows oneloginEntry(mode:)carrying the Nostr sheet and the Google / Apple covers, plus a sharedfinishLogin. What actually differs between the two entry points turns out to be two lines — dismissing the cover, and the shorter loading delay an account switch gets.Net −288 / +122, and the three gaps close for free: add-account is now literally the same code path as first launch.
Decisions worth a look
Apple / Google are offered in add-account. They're how a cloud-backed key gets in, so excluding them would leave the gap half-closed. It does mean someone can add an account that's already signed in elsewhere. Easy to gate to
.firstRunif you'd rather.The avatar collage stays in both modes. It costs a profile fetch and a metrics socket per open, but it is the screen's design — without it, add-account is a bare screen with three buttons, which isn't "one view" in any useful sense.
viewModel.cancel()on disappear already tears both sockets down.LoginView.swiftis deleted along with its fourproject.pbxprojreferences — it lived at the repo root, which is individually listed rather than afileSystemSynchronizedGroup. The build passing is the check that the right four lines went.Testing
Built and installed on the simulator. Two things worth a reviewer's attention, since they're what a refactor like this would break: that first launch still settles behind the curtain, and that Apple's brand-new-account path still routes into the sign-up wizard rather than the loading screen — that branch in
finishLoginis the only routing I restructured rather than moved.