Skip to content

Sean/mobile 464 ios add public api to clear pre filled email#381

Merged
seanperez29 merged 7 commits intomainfrom
sean/mobile-464-ios-add-public-api-to-clear-pre-filled-email
Mar 23, 2026
Merged

Sean/mobile 464 ios add public api to clear pre filled email#381
seanperez29 merged 7 commits intomainfrom
sean/mobile-464-ios-add-public-api-to-clear-pre-filled-email

Conversation

@seanperez29
Copy link
Collaborator

@seanperez29 seanperez29 commented Mar 19, 2026

Summary by CodeRabbit

  • New Features

    • Auth forms can be pre-filled with an initial identifier or phone number via a new configuration API; apps can also opt out of persisting those values.
  • Bug Fixes

    • Initialization now applies persistence and provided initial values consistently on first view.
  • Tests

    • Added tests covering configuration, persistence, seeding, clearing, and related behaviors.

Introduce three new view modifiers on AuthView:
- clerkInitialIdentifier(_:) to pre-fill email/username
- clerkInitialPhoneNumber(_:) to pre-fill phone number
- clerkPersistsIdentifiers(_:) to control persistence

When persistence is disabled, previously stored identifiers
are cleared and future edits are not saved. This supports
shared device scenarios where user data should not persist.
When providing an initial identifier or phone number, clear the
opposite stored field to prevent stale data from appearing in the
form. Also move identifier config modifiers to View extension for
broader usability.
@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Auth identifier prefill and persistence were made configurable: new iOS-only AuthIdentifierConfig and AuthView modifiers drive AuthState.configure(_), AuthState now uses an injected UserDefaults and a persistsIdentifiers flag to gate reads/writes, AuthStartView defers last-used storage to AuthState, and LastUsedAuth accepts injectable UserDefaults.

Changes

Cohort / File(s) Summary
Configuration type & view API
Sources/ClerkKitUI/Components/Auth/AuthIdentifierConfig.swift, Sources/ClerkKitUI/Components/Auth/AuthView.swift
Added iOS-only AuthIdentifierConfig (initialIdentifier, persistsIdentifiers). AuthView stores a config, exposes initialIdentifier(_:) and persistsIdentifiers(_:) modifiers, and applies config via .onChange to drive AuthState.configure(_).
Auth state & persistence
Sources/ClerkKitUI/Components/Auth/AuthState.swift
AuthState now injects UserDefaults, adds persistsIdentifiers and hasInitialValues, initializes auth-start values from the injected store, persists/clears values only when persistsIdentifiers is true, adds configure(_:) and storeLastUsedIdentifierType(_:), and centralizes storage keys.
Auth start UI adjustments
Sources/ClerkKitUI/Components/Auth/AuthStartView.swift
Removed unconditional init seeding. On first appearance, initializes last-used identifier only when authState.persistsIdentifiers is enabled, sets phone field active state depending on authState.hasInitialValues, and calls authState.storeLastUsedIdentifierType(...).
Last-used helper injection
Sources/ClerkKitUI/Components/Auth/LastUsedAuth.swift
Persistence helpers updated to accept UserDefaults (default .standard) for store/retrieve/clear operations.
Tests
Tests/UI/AuthStateConfigurationTests.swift
Added iOS @MainActor tests validating AuthState loading from injected UserDefaults, persistence behavior, configure(_:) handling of initial values and persistence toggling, and clearing stored identifier/phone and last-used identifier when persistence is disabled.

Sequence Diagram(s)

sequenceDiagram
  participant User as User/UI
  participant AuthView as AuthView
  participant AuthState as AuthState
  participant UD as UserDefaults
  participant LastUsed as LastUsedAuth
  participant AuthStart as AuthStartView

  User->>AuthView: apply modifier (initialIdentifier / persistsIdentifiers)
  AuthView->>AuthState: .configure(AuthIdentifierConfig)   %% apply config on change
  AuthState->>AuthState: set hasInitialValues, persistsIdentifiers
  alt persistsIdentifiers == true
    AuthState->>UD: read identifier/phone (identifierStorageKey / phoneNumberStorageKey)
    AuthState->>LastUsed: storeLastUsedIdentifierType(..., userDefaults: UD)
  else persistsIdentifiers == false
    AuthState->>UD: remove stored identifier/phone
    AuthState->>LastUsed: clearStoredIdentifierType(userDefaults: UD)
  end
  User->>AuthStart: view appears
  AuthStart->>AuthState: query lastUsedAuth / hasInitialValues -> update UI fields
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hop and stash the start so neat,

Injected Defaults make things sweet,
Config nudges where the input goes,
Persistence listens — then it knows,
Tests clap paws and dance a beat.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'clear pre filled email' but the changeset primarily implements a configurable identifier persistence system with optional initialization values, identity type tracking, and cross-session storage control—not specifically email clearing. Update the title to reflect the main changes: something like 'Add configurable identifier persistence and initial values support to AuthView' or 'Implement AuthIdentifierConfig for identifier prefill and persistence control'.
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sean/mobile-464-ios-add-public-api-to-clear-pre-filled-email

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/ClerkKitUI/Components/Auth/AuthStartView.swift (1)

377-386: ⚠️ Potential issue | 🟠 Major

Missing userDefaults parameter in storeIdentifierType calls breaks DI pattern.

AuthState uses an injected userDefaults instance, and AuthState.configure() correctly passes it to LastUsedAuth.clearStoredIdentifierType(userDefaults:). However, the calls here use the default .standard, causing writes to potentially go to a different UserDefaults suite than reads.

Additionally, authState.persistsIdentifiers (line 378) may not reflect the configured value yet due to SwiftUI's bottom-up onAppear ordering—AuthView.onFirstAppear (which calls configure()) runs after AuthStartView.onFirstAppear. Consider using the environment value persistsIdentifiers directly here for consistency with line 183.

🔧 Proposed fix

Since AuthState.userDefaults is private, you may need to expose a method on AuthState for storing identifier type, or pass the environment value directly:

  private func storeIdentifierType() {
-   guard authState.persistsIdentifiers else { return }
+   guard persistsIdentifiers else { return }
    if phoneNumberFieldIsActive, phoneNumberIsEnabled {
-     LastUsedAuth.storeIdentifierType(.phone)
+     // Consider adding a method on AuthState that wraps this
+     // or exposing userDefaults access
+     LastUsedAuth.storeIdentifierType(.phone)
    } else if authState.authStartIdentifier.isEmailAddress {
-     LastUsedAuth.storeIdentifierType(.email)
+     LastUsedAuth.storeIdentifierType(.email)
    } else {
-     LastUsedAuth.storeIdentifierType(.username)
+     LastUsedAuth.storeIdentifierType(.username)
    }
  }

A cleaner approach would be to add a storeIdentifierType(_:) method on AuthState that uses its internal userDefaults:

// In AuthState.swift
func storeLastUsedIdentifierType(_ identifier: LastUsedAuth) {
  guard persistsIdentifiers else { return }
  LastUsedAuth.storeIdentifierType(identifier, userDefaults: userDefaults)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ClerkKitUI/Components/Auth/AuthStartView.swift` around lines 377 -
386, The current storeIdentifierType() in AuthStartView calls
LastUsedAuth.storeIdentifierType(...) without passing AuthState's injected
userDefaults and also reads authState.persistsIdentifiers which may not be
initialized yet; add a method on AuthState (e.g.,
storeLastUsedIdentifierType(_:) or storeIdentifierType(_:)) that checks
persistsIdentifiers and writes via its internal userDefaults, then update
AuthStartView.storeIdentifierType() to call that new AuthState method (or,
alternatively, read the environment persistsIdentifiers instead of
authState.persistsIdentifiers) so writes use the correct UserDefaults suite and
respect DI.
🧹 Nitpick comments (1)
Sources/ClerkKitUI/Components/Auth/AuthStartView.swift (1)

182-191: Consider using consistent source for lastUsedAuth initialization.

Line 184 initializes lastUsedAuth without passing the userDefaults instance, so retrieveStoredIdentifierType() inside LastUsedAuth.init will read from .standard. If AuthState is configured with a custom UserDefaults suite (e.g., in tests), this could read stale or inconsistent data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ClerkKitUI/Components/Auth/AuthStartView.swift` around lines 182 -
191, Initialize lastUsedAuth with the same UserDefaults instance used by your
AuthState to avoid reading from .standard; replace the current
LastUsedAuth(environment: Clerk.shared.environment) call with one that passes
the AuthState's userDefaults (e.g., LastUsedAuth(environment:
Clerk.shared.environment, userDefaults: authState.userDefaults) or the actual
variable name for your AuthState's UserDefaults), so
retrieveStoredIdentifierType() reads from the correct suite instead of
.standard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Sources/ClerkKitUI/Components/Auth/AuthStartView.swift`:
- Around line 377-386: The current storeIdentifierType() in AuthStartView calls
LastUsedAuth.storeIdentifierType(...) without passing AuthState's injected
userDefaults and also reads authState.persistsIdentifiers which may not be
initialized yet; add a method on AuthState (e.g.,
storeLastUsedIdentifierType(_:) or storeIdentifierType(_:)) that checks
persistsIdentifiers and writes via its internal userDefaults, then update
AuthStartView.storeIdentifierType() to call that new AuthState method (or,
alternatively, read the environment persistsIdentifiers instead of
authState.persistsIdentifiers) so writes use the correct UserDefaults suite and
respect DI.

---

Nitpick comments:
In `@Sources/ClerkKitUI/Components/Auth/AuthStartView.swift`:
- Around line 182-191: Initialize lastUsedAuth with the same UserDefaults
instance used by your AuthState to avoid reading from .standard; replace the
current LastUsedAuth(environment: Clerk.shared.environment) call with one that
passes the AuthState's userDefaults (e.g., LastUsedAuth(environment:
Clerk.shared.environment, userDefaults: authState.userDefaults) or the actual
variable name for your AuthState's UserDefaults), so
retrieveStoredIdentifierType() reads from the correct suite instead of
.standard.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7cba716a-e4e7-470e-897d-80d305389683

📥 Commits

Reviewing files that changed from the base of the PR and between 92ab31a and 09eb5a4.

📒 Files selected for processing (6)
  • Sources/ClerkKitUI/Components/Auth/AuthStartView.swift
  • Sources/ClerkKitUI/Components/Auth/AuthState.swift
  • Sources/ClerkKitUI/Components/Auth/AuthView.swift
  • Sources/ClerkKitUI/Components/Auth/LastUsedAuth.swift
  • Sources/ClerkKitUI/Extensions/View+AuthIdentifierConfig.swift
  • Tests/UI/AuthStateConfigurationTests.swift

Move the `persistsIdentifiers` guard check into `AuthState.storeLastUsedIdentifierType()` to centralize persistence logic and simplify the call sites in AuthStartView.
This replaces the generic View extension modifiers (clerkInitialIdentifier,
clerkInitialPhoneNumber, clerkPersistsIdentifiers) with type-specific methods
directly on AuthView (initialIdentifier, initialPhoneNumber, persistsIdentifiers).

The configuration is now stored in an AuthIdentifierConfig struct and passed
through the view hierarchy rather than using SwiftUI environment values.
Merge initialIdentifier and initialPhoneNumber into one method
that auto-detects phone numbers vs email/username inputs.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
Sources/ClerkKitUI/Components/Auth/AuthView.swift (1)

226-233: Keep identifier fields mutually exclusive when overriding.

At Line 227, the method copies existing config, but Lines 228-232 only set one field and never clear the other. If initialIdentifier(_:) is applied multiple times (email/username then phone, or vice versa), stale values can coexist.

Proposed fix
 public func initialIdentifier(_ identifier: String) -> AuthView {
   var config = config
+  config.initialIdentifier = nil
+  config.initialPhoneNumber = nil
   if identifier.isPhoneNumber {
     config.initialPhoneNumber = identifier
   } else {
     config.initialIdentifier = identifier
   }
   return AuthView(mode: authState.mode, isDismissable: isDismissable, config: config)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ClerkKitUI/Components/Auth/AuthView.swift` around lines 226 - 233,
The method initialIdentifier(_ identifier: String) in AuthView currently sets
config.initialPhoneNumber or config.initialIdentifier but never clears the other
field, allowing stale values to coexist; update initialIdentifier(_:) to
explicitly clear the alternate field whenever one is set (i.e., when setting
config.initialPhoneNumber also set config.initialIdentifier = nil/empty, and
vice versa) before returning the new AuthView via
AuthView(mode:isDismissable:config:), ensuring the identifier fields remain
mutually exclusive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/ClerkKitUI/Components/Auth/AuthView.swift`:
- Around line 226-233: The method initialIdentifier(_ identifier: String) in
AuthView currently sets config.initialPhoneNumber or config.initialIdentifier
but never clears the other field, allowing stale values to coexist; update
initialIdentifier(_:) to explicitly clear the alternate field whenever one is
set (i.e., when setting config.initialPhoneNumber also set
config.initialIdentifier = nil/empty, and vice versa) before returning the new
AuthView via AuthView(mode:isDismissable:config:), ensuring the identifier
fields remain mutually exclusive.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8a4e78dc-a7b4-4b18-a851-f2ce41effce3

📥 Commits

Reviewing files that changed from the base of the PR and between 4514ba6 and 7d19412.

📒 Files selected for processing (1)
  • Sources/ClerkKitUI/Components/Auth/AuthView.swift

Move phone number detection from AuthView to AuthState so that
a single initialIdentifier property handles both email/username
and phone number inputs. This simplifies the public API while
preserving the behavior of clearing the alternate field when
seeding a value.
@mikepitre
Copy link
Collaborator

I found one iOS-specific issue in the new initialIdentifier(...) flow.

AuthState.configure(_:) decides whether the initial value should go into the phone field or the email/username field by calling identifier.isPhoneNumber. That helper uses PhoneNumberKit.isValidPhoneNumber(...), which is a strict validation check.

The result is that values that look like phone numbers but do not pass full validation get routed into authStartIdentifier instead of authStartPhoneNumber. For example, with initialIdentifier("+17777770123"), the phone field is left empty and the email/username field gets the string instead.

I reproduced this locally on the current PR head by running the iOS test target:

xcodebuild test -scheme Tests -destination 'id=B02A373A-649A-4D50-8F99-838909BE734E' -only-testing:Tests/AuthStateConfigurationTests

That fails in initialPhoneNumberOverridesPersistedValues().

I think the root issue is that we are using strict phone validation to decide which field to prefill. This probably wants a looser "phone-like" heuristic, or a dedicated phone-prefill path, rather than requiring the value to already be a fully valid number.

One reason this is easy to miss: the PR checks are green because CI is currently running the macOS unit-test job, while this test is under #if os(iOS).

Use NSDataDetector-based `looksLikePhoneNumber` for determining
identifier type during initial setup, keeping the strict
PhoneNumberKit validation for other use cases.
@seanperez29 seanperez29 merged commit 3433df3 into main Mar 23, 2026
10 checks passed
@seanperez29 seanperez29 deleted the sean/mobile-464-ios-add-public-api-to-clear-pre-filled-email branch March 23, 2026 20:27
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