apps/mobile; the workspace does not contain an onboarding package. The route files live in apps/mobile/app/(onboarding), and per-user completion state lives in apps/mobile/src/features/security/onboardingFlow.ts.
Prerequisites enforced by the access gate
An authenticated user reaches the onboarding group only after completing these app-level gates:- Verify the authenticated email at
/(public)/verify-email. - Create and confirm a six-digit PIN at
/(public)/setup-pin. - Complete
/(public)/setup-biometricsby enabling biometrics or choosing not to enable them.
@amisi-mobile-starter/security. savePinForUser(userId, pin) marks PIN setup. The biometrics screen always calls setHasBiometricsSetupForUser(userId, true) and records whether biometrics are enabled with setBiometricsEnabledForUser(userId, isEnabled).
After biometric setup, the screen replaces the route with /(onboarding)/steps/welcome.
Completion state
The app-local state has exactly three booleans:getOnboardingFlowState(userId) reads them synchronously from @amisi-mobile-starter/core key-value storage. Missing values default to false.
State is scoped by authenticated user ID. There is no reset helper in the current app-local module.
Actual route flow
1. Welcome
/(onboarding) immediately redirects to /(onboarding)/steps/welcome.
The welcome step marks hasCompletedWelcome, tracks onboarding_welcome_continue, and pushes /(onboarding)/steps/permissions.
2. Push explanation
The permissions step explains notifications without requesting system permission. Continuing markshasCompletedPushExplanation, tracks onboarding_push_explanation_continue, and replaces the route with ROUTES.HOME.
Because hasCompletedPushPermissionPrompt is still false, AccessGate then replaces home with ROUTES.NOTIFICATION_PERMISSION_MODAL.
3. System permission modal
The actual prompt is/(modal)/notification-permission, not a route inside (onboarding).
- Allow calls
useNotifications().requestPermission()and tracks the returnedgrantedvalue. - Skip does not call the provider.
- A permission-request error is logged and displayed briefly.
- Allow, skip, and error paths all mark
hasCompletedPushPermissionPromptand replace the route withROUTES.HOME.
Notification initialization occurs after authentication in
AuthenticatedServices. It initializes the selected adapter and immediately calls registerForPushNotifications(userId). The permission modal only requests permission; it does not register again after a grant.Paywall route
/(onboarding)/steps/paywall exists, and ROUTES.ONBOARDING_PAYWALL points to it. Both actions on that screen replace the route with /(app)/(tabs).
The current AccessGate does not navigate to the paywall, and onboarding state has no paywall-completion field. The normal enforced flow ends after the notification permission modal. flags.enableSubscriptions controls whether SubscriptionsProvider is mounted; it does not add the onboarding paywall to this flow.
Extending the flow
When you add an enforced step:- Add its route under
apps/mobile/app/(onboarding)or document why it is a modal. - Add user-scoped storage in
apps/mobile/src/features/security/onboardingFlow.ts. - Mark completion before navigating away from the step.
- Add the gate in
AccessGatebefore its completed-group redirect. - Add or update an exact constant in
apps/mobile/src/constants/routes.ts.