> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobile-starter.amisi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile onboarding

> Follow the app-local security and onboarding state, routes, and completion flow.

Mobile onboarding is implemented inside `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:

1. Verify the authenticated email at `/(public)/verify-email`.
2. Create and confirm a six-digit PIN at `/(public)/setup-pin`.
3. Complete `/(public)/setup-biometrics` by enabling biometrics or choosing not to enable them.

PIN and biometric setup use per-user exports from `@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:

```ts theme={null}
interface OnboardingFlowState {
  hasCompletedWelcome: boolean;
  hasCompletedPushExplanation: boolean;
  hasCompletedPushPermissionPrompt: boolean;
}
```

`getOnboardingFlowState(userId)` reads them synchronously from `@amisi-mobile-starter/core` key-value storage. Missing values default to `false`.

| State             | Storage key                                             | Writer                                      |
| ----------------- | ------------------------------------------------------- | ------------------------------------------- |
| Welcome           | `onboarding:${userId}:welcome_completed`                | `markOnboardingWelcomeCompleted(userId)`    |
| Push explanation  | `onboarding:${userId}:push_explanation_completed`       | `markPushExplanationCompleted(userId)`      |
| Permission prompt | `onboarding:${userId}:push_permission_prompt_completed` | `markPushPermissionPromptCompleted(userId)` |

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 marks `hasCompletedPushExplanation`, 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 returned `granted` value.
* **Skip** does not call the provider.
* A permission-request error is logged and displayed briefly.
* Allow, skip, and error paths all mark `hasCompletedPushPermissionPrompt` and replace the route with `ROUTES.HOME`.

The completion boolean records that the prompt was handled, not that permission was granted.

<Note>
  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.
</Note>

## 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:

1. Add its route under `apps/mobile/app/(onboarding)` or document why it is a modal.
2. Add user-scoped storage in `apps/mobile/src/features/security/onboardingFlow.ts`.
3. Mark completion before navigating away from the step.
4. Add the gate in `AccessGate` before its completed-group redirect.
5. Add or update an exact constant in `apps/mobile/src/constants/routes.ts`.

Keep app-local onboarding paths distinct from public security setup and from package APIs.
