# Solution Design Document

## Validation Checklist

- [x] All required sections are complete
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] All context sources are listed with relevance ratings
- [x] Project commands are discovered from actual project files
- [x] Constraints → Strategy → Design → Implementation path is logical
- [x] Architecture pattern is clearly stated with rationale
- [x] Every component in diagram has directory mapping
- [x] Every interface has specification
- [x] Error handling covers all error types
- [x] Quality requirements are specific and measurable
- [x] Every quality requirement has test coverage
- [x] **All architecture decisions confirmed by user**
- [x] Component names consistent across diagrams
- [x] A developer could implement from this design

---

## Constraints

CON-1: **Framework Requirements**
- Flutter 3.38.3 / Dart 3.10.1
- Riverpod 3.x (AsyncNotifier pattern)
- Freezed 3.x for data models (abstract class pattern)
- Equatable for domain entities
- Dio for HTTP with interceptors

CON-2: **Platform Requirements**
- iOS 14+ with Face ID/Touch ID support
- Android 8+ with fingerprint support
- SecureStorage for encrypted credential storage

CON-3: **API Requirements**
- New backend endpoint `/mobile/auth/login` must be available
- Legacy endpoints keep form-urlencoded format
- JWT in Bearer header, not request body
- Access token: 15 minutes TTL
- Refresh token: 30 days TTL

CON-4: **Migration Requirements**
- Existing API key users must re-authenticate
- No data loss during migration
- Single release (no gradual rollout within app)

## Implementation Context

**IMPORTANT**: You MUST read and analyze ALL listed context sources to understand constraints, patterns, and existing architecture.

### Required Context Sources

- ICO-1 [Scheduling Auth - Primary Pattern]
```yaml
- file: lib/presentation/providers/scheduling/scheduling_auth_provider.dart
  relevance: HIGH
  why: "Contains the mature JWT auth state machine to adapt for unified auth"

- file: lib/core/network/scheduling/jwt_interceptor.dart
  relevance: HIGH
  why: "JWT token injection and refresh logic to merge into main interceptor"

- file: lib/core/services/scheduling/biometric_service.dart
  relevance: HIGH
  why: "Biometric authentication to reuse as-is"
```

- ICO-2 [Main App Auth - To Replace]
```yaml
- file: lib/presentation/providers/auth_provider.dart
  relevance: HIGH
  why: "Current API key auth provider to replace with unified JWT auth"

- file: lib/core/network/api_interceptors.dart
  relevance: HIGH
  why: "Main interceptor to modify for JWT Bearer headers"

- file: lib/data/repositories/auth_repository_impl.dart
  relevance: HIGH
  why: "Auth repository to add JWT login method"

- file: lib/presentation/screens/installation/installation_screen.dart
  relevance: MEDIUM
  why: "API key entry screen to be replaced by login screen"
```

- ICO-3 [Router and Navigation]
```yaml
- file: lib/router/app_router.dart
  relevance: HIGH
  why: "Route definitions and auth redirects to update"

- file: lib/presentation/screens/scheduling/login/scheduling_login_screen.dart
  relevance: MEDIUM
  why: "Reference UI for new unified login screen"
```

### Implementation Boundaries

- **Must Preserve**:
  - All store data models and entities
  - Dashboard and store detail functionality
  - Existing API response parsing for stores/employees
  - Biometric service implementation

- **Can Modify**:
  - Auth provider state and methods
  - API interceptors
  - Router auth redirect logic
  - Settings screen (sign out UI)
  - Storage keys and secure storage usage

- **Must Not Touch**:
  - Business logic in store/queue/completed/stats providers
  - Ably real-time functionality
  - Push notification service
  - Chart and metric display logic

### External Interfaces

#### System Context Diagram

```mermaid
graph TB
    User[Store Manager/Owner] --> App[BuyerKiosk Live App]

    App --> AuthAPI["/mobile/auth/login<br/>(NEW JWT Endpoint)"]
    App --> LegacyAPI["/mobile.php<br/>(Legacy Endpoints)"]
    App --> SchedulingAPI["/api/scheduling/*<br/>(Scheduling Endpoints)"]

    AuthAPI --> Backend[(Backend Server)]
    LegacyAPI --> Backend
    SchedulingAPI --> Backend

    App --> SecureStorage[(Device Secure Storage)]
    App --> Biometric[Face ID / Touch ID]
```

#### Interface Specifications

```yaml
# Inbound Interfaces
inbound:
  - name: "User Login"
    type: HTTPS
    format: JSON
    endpoint: POST /mobile/auth/login
    authentication: None (login endpoint)
    data_flow: "Email/password credentials"

  - name: "Token Refresh"
    type: HTTPS
    format: JSON
    endpoint: POST /mobile/auth/refresh
    authentication: Refresh Token in body
    data_flow: "Refresh token exchange"

# Outbound Interfaces
outbound:
  - name: "Legacy Mobile API"
    type: HTTPS
    format: form-urlencoded
    authentication: JWT Bearer header
    data_flow: "Store data, queues, completed buys, stats"
    criticality: HIGH

  - name: "Scheduling API"
    type: HTTPS
    format: JSON
    authentication: JWT Bearer header
    data_flow: "Time-off requests, schedules, labor costs"
    criticality: HIGH

# Data Interfaces
data:
  - name: "Secure Storage"
    type: FlutterSecureStorage
    connection: Encrypted key-value store
    data_flow: "JWT tokens, user info, biometric flag"
```

### Project Commands

```bash
# Environment Setup
Install Dependencies: flutter pub get
Regenerate Models: dart run build_runner build --delete-conflicting-outputs

# Testing Commands
Unit Tests: flutter test
Integration Tests: flutter test integration_test/
Widget Tests: flutter test test/presentation/

# Code Quality Commands
Analyze: flutter analyze
Format: dart format lib/ test/

# Build Commands
Build iOS: flutter build ios --debug --no-codesign
Build Android: flutter build apk --debug
Run App: flutter run
Run on Device: flutter run -d iphone
```

## Solution Strategy

- **Architecture Pattern**: Clean Architecture with Riverpod (existing pattern - no change)
- **Integration Approach**: Adapt scheduling JWT auth pattern to replace API key auth system-wide
- **Justification**: The scheduling module already has a mature JWT implementation with token refresh, biometrics, and proper state management. Reusing this pattern minimizes risk and development time.

**Key Decisions**:
1. Adapt `SchedulingAuthNotifier` pattern for unified `AuthNotifier`
2. Merge JWT interceptor logic into main `AuthInterceptor`
3. Keep form-urlencoded for legacy endpoints, add JWT Bearer header
4. Move scheduling routes under `/store/:typeNum/scheduling/*`

## Building Block View

### Components

```mermaid
graph TB
    subgraph Presentation
        LoginScreen[Login Screen]
        Dashboard[Dashboard Screen]
        StoreDetail[Store Detail Screen]
        Scheduling[Scheduling Screens]
        Settings[Settings Screen]
    end

    subgraph Providers
        AuthProvider[Unified Auth Provider]
        DashboardProvider[Dashboard Provider]
        SchedulingProviders[Scheduling Providers]
    end

    subgraph Data
        AuthRepo[Auth Repository]
        AuthInterceptor[Auth Interceptor]
        SecureStorage[Secure Storage]
    end

    subgraph External
        AuthAPI[Auth API]
        LegacyAPI[Legacy API]
        BiometricService[Biometric Service]
    end

    LoginScreen --> AuthProvider
    Dashboard --> DashboardProvider
    StoreDetail --> DashboardProvider
    Scheduling --> SchedulingProviders
    Settings --> AuthProvider

    AuthProvider --> AuthRepo
    AuthProvider --> BiometricService
    DashboardProvider --> AuthInterceptor
    SchedulingProviders --> AuthInterceptor

    AuthRepo --> AuthAPI
    AuthInterceptor --> LegacyAPI
    AuthInterceptor --> SecureStorage
```

### Directory Map

```
lib/
├── core/
│   ├── constants/
│   │   └── auth_constants.dart          # NEW: Unified auth storage keys
│   ├── network/
│   │   └── api_interceptors.dart        # MODIFY: Add JWT Bearer, remove APIKey
│   └── services/
│       └── biometric_service.dart       # MOVE: From scheduling/ to here (shared)
├── data/
│   ├── models/
│   │   └── auth/
│   │       └── login_response_model.dart    # NEW: JWT login response model
│   ├── repositories/
│   │   └── auth_repository_impl.dart        # MODIFY: Add loginWithCredentials()
│   └── datasources/
│       └── local/
│           └── secure_storage_datasource.dart  # MODIFY: Unified token storage
├── domain/
│   ├── entities/
│   │   └── auth/
│   │       └── auth_state.dart              # NEW: Unified auth state entity
│   └── repositories/
│       └── auth_repository.dart             # MODIFY: Add login interface
├── presentation/
│   ├── providers/
│   │   ├── auth_provider.dart               # MODIFY: Full JWT auth state machine
│   │   └── providers.dart                   # MODIFY: Update exports
│   ├── screens/
│   │   ├── auth/
│   │   │   └── login_screen.dart            # NEW: Email/password login
│   │   ├── installation/
│   │   │   └── installation_screen.dart     # DELETE: Replaced by login
│   │   └── settings/
│   │       └── settings_screen.dart         # MODIFY: Sign out UI
│   └── widgets/
│       └── auth/
│           └── biometric_button.dart        # NEW: Biometric login button
└── router/
    └── app_router.dart                      # MODIFY: New routes, updated redirects
```

### Interface Specifications

#### Data Storage Changes

```yaml
# Secure Storage Keys (unified)
AuthStorageKeys:
  accessToken: "auth_access_token"           # NEW (replaces scheduling_access_token)
  refreshToken: "auth_refresh_token"         # NEW (replaces scheduling_refresh_token)
  tokenExpiry: "auth_token_expiry"           # NEW
  userId: "auth_user_id"                     # NEW (replaces userId)
  userEmail: "auth_user_email"               # NEW (replaces userEmail)
  userDisplayName: "auth_user_display_name"  # NEW (replaces userDisplayName)
  employeeLinks: "auth_employee_links"       # KEEP (same key)
  accessLevel: "auth_access_level"           # KEEP (same key)
  biometricEnabled: "auth_biometric_enabled" # NEW
  deviceFingerprint: "auth_device_fingerprint" # NEW

# Legacy Keys to Clear on Migration
LegacyKeys:
  apiKey: "APItoken"                         # DELETE after migration
  scheduling_access_token: "..."             # DELETE (consolidated)
  scheduling_refresh_token: "..."            # DELETE (consolidated)
```

#### Internal API Changes

```yaml
# NEW Endpoint: Login with Credentials
Endpoint: Authenticate User
  Method: POST
  Path: /mobile/auth/login
  Request:
    email: string, required, email format
    password: string, required, min 6 chars
    deviceName: string, required
    deviceFingerprint: string, required
  Response:
    success:
      accessToken: string (JWT)
      refreshToken: string (JWT)
      expiresIn: int (seconds)
      user:
        id: int
        email: string
        displayName: string
      stores: array of:
        typeNum: string
        name: string
        storeType: string
        employee:
          id: int
          fullName: string
          role: int (1-4)
    error:
      error_code: string (invalid_credentials, account_disabled, rate_limited)
      message: string

# NEW Endpoint: Refresh Token
Endpoint: Refresh Access Token
  Method: POST
  Path: /mobile/auth/refresh
  Request:
    refreshToken: string, required
  Response:
    success:
      accessToken: string
      refreshToken: string (optional, if rotated)
      expiresIn: int
    error:
      error_code: string (invalid_token, expired_token)
      message: string

# EXISTING Endpoint: Logout (extend to main app)
Endpoint: Logout
  Method: POST
  Path: /mobile/auth/logout
  Request:
    refreshToken: string
    deviceFingerprint: string
  Response:
    success: { message: "Logged out successfully" }
```

#### Application Data Models

```dart
// NEW: Unified Auth State Entity
@immutable
class AuthState extends Equatable {
  final String? accessToken;
  final String? refreshToken;
  final DateTime? tokenExpiry;
  final AuthUser? user;
  final List<AuthStore> stores;
  final bool biometricEnabled;
  final AuthStatus status;
  final String? errorMessage;

  // Computed properties
  bool get isAuthenticated => accessToken != null && status == AuthStatus.authenticated;
  bool get isTokenExpired => tokenExpiry != null && DateTime.now().isAfter(tokenExpiry!);
  bool get shouldRefreshToken => tokenExpiry != null &&
    DateTime.now().isAfter(tokenExpiry!.subtract(Duration(minutes: 5)));
}

enum AuthStatus {
  initial,
  unauthenticated,
  authenticating,
  authenticated,
  refreshing,
  error,
  sessionExpired,
}

// NEW: Auth User Entity
@immutable
class AuthUser extends Equatable {
  final int id;
  final String email;
  final String displayName;
}

// NEW: Auth Store Entity (combines store + employee)
@immutable
class AuthStore extends Equatable {
  final String typeNum;
  final String name;
  final String storeType;
  final int employeeId;
  final String employeeFullName;
  final int role; // 1=Owner, 2=Manager, 3=ShiftLead, 4=Employee
}

// NEW: Login Response Model (Freezed)
@freezed
abstract class LoginResponseModel with _$LoginResponseModel {
  const factory LoginResponseModel({
    required String accessToken,
    required String refreshToken,
    required int expiresIn,
    required LoginUserModel user,
    required List<LoginStoreModel> stores,
  }) = _LoginResponseModel;

  factory LoginResponseModel.fromJson(Map<String, dynamic> json) =>
    _$LoginResponseModelFromJson(json);
}
```

### Implementation Examples

#### Example: Auth Interceptor JWT Injection

**Why this example**: Shows the critical logic for injecting JWT into requests while maintaining form-urlencoded format for legacy endpoints.

```dart
// AuthInterceptor - Modified to use JWT Bearer header
class AuthInterceptor extends Interceptor {
  final SecureStorageDataSource _secureStorage;
  bool _isRefreshing = false;

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    // Get JWT access token
    final accessToken = await _secureStorage.read(key: AuthStorageKeys.accessToken);

    if (accessToken != null) {
      // Add JWT as Bearer token in header
      options.headers['Authorization'] = 'Bearer $accessToken';
    }

    // IMPORTANT: Keep form-urlencoded for legacy endpoints
    // Do NOT convert to JSON - legacy API expects form data
    if (options.path.contains('mobile.php')) {
      options.contentType = Headers.formUrlEncodedContentType;
      // Remove APIKey from body if present (legacy cleanup)
      if (options.data is Map) {
        (options.data as Map).remove('APIKey');
      }
    }

    handler.next(options);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (err.response?.statusCode == 401 && !_isRefreshing) {
      _isRefreshing = true;
      try {
        final refreshed = await _attemptTokenRefresh();
        if (refreshed) {
          // Retry original request with new token
          final retryResponse = await _retryRequest(err.requestOptions);
          handler.resolve(retryResponse);
          return;
        }
      } finally {
        _isRefreshing = false;
      }
      // Refresh failed - force logout
      await _forceLogout();
    }
    handler.next(err);
  }
}
```

#### Example: Legacy User Migration Detection

**Why this example**: Shows the critical migration detection logic on app startup.

```dart
// In AuthNotifier.build() - App startup
Future<AuthState> build() async {
  // Step 1: Check for existing JWT tokens (already migrated or new user)
  final accessToken = await _secureStorage.read(key: AuthStorageKeys.accessToken);
  if (accessToken != null) {
    // User has JWT - validate and restore session
    return await _restoreSession();
  }

  // Step 2: Check for legacy API key (needs migration)
  final legacyApiKey = await _secureStorage.read(key: 'APItoken');
  if (legacyApiKey != null) {
    // User has legacy API key - show migration screen
    // Don't clear it yet - only clear after successful JWT login
    return AuthState.initial().copyWith(
      status: AuthStatus.unauthenticated,
      requiresMigration: true, // Flag for UI
    );
  }

  // Step 3: No auth data - new user
  return AuthState.initial().copyWith(
    status: AuthStatus.unauthenticated,
  );
}
```

## Runtime View

### Primary Flow: User Login

1. User opens app → Router checks auth state
2. If unauthenticated → Redirect to `/login`
3. User enters email/password → Tap "Sign In"
4. Provider calls `login(email, password)`
5. Repository POSTs to `/mobile/auth/login`
6. On success → Store tokens, parse user/stores
7. Navigate to dashboard

```mermaid
sequenceDiagram
    actor User
    participant LoginScreen
    participant AuthProvider
    participant AuthRepository
    participant SecureStorage
    participant BackendAPI

    User->>LoginScreen: Enter email/password
    User->>LoginScreen: Tap "Sign In"
    LoginScreen->>AuthProvider: login(email, password)
    AuthProvider->>AuthProvider: state = authenticating
    AuthProvider->>AuthRepository: loginWithCredentials(email, password)
    AuthRepository->>BackendAPI: POST /mobile/auth/login
    BackendAPI-->>AuthRepository: {accessToken, refreshToken, user, stores}
    AuthRepository->>SecureStorage: storeTokens(...)
    AuthRepository->>SecureStorage: storeUserInfo(...)
    AuthRepository-->>AuthProvider: AuthState.authenticated
    AuthProvider-->>LoginScreen: state = authenticated
    LoginScreen->>LoginScreen: Navigate to Dashboard
```

### Secondary Flow: Token Refresh

```mermaid
sequenceDiagram
    participant DashboardProvider
    participant AuthInterceptor
    participant AuthRepository
    participant SecureStorage
    participant BackendAPI

    DashboardProvider->>AuthInterceptor: API Request
    AuthInterceptor->>AuthInterceptor: Add Bearer token
    AuthInterceptor->>BackendAPI: Request with JWT
    BackendAPI-->>AuthInterceptor: 401 Unauthorized
    AuthInterceptor->>SecureStorage: getRefreshToken()
    AuthInterceptor->>BackendAPI: POST /mobile/auth/refresh
    BackendAPI-->>AuthInterceptor: {newAccessToken, newRefreshToken}
    AuthInterceptor->>SecureStorage: updateTokens()
    AuthInterceptor->>BackendAPI: Retry original request
    BackendAPI-->>DashboardProvider: Success response
```

### Error Handling

- **Invalid credentials**: Show inline error "Invalid email or password"
- **Account disabled**: Show error "Your account has been disabled. Contact support."
- **Rate limited**: Show error "Too many attempts. Please try again in X minutes."
- **Network failure**: Show error "Unable to connect. Check your internet connection." with retry button
- **Token refresh failure**: Redirect to login with message "Session expired. Please sign in again."
- **Biometric failure**: Fall back to password login automatically

### Legacy Migration Flow

```mermaid
sequenceDiagram
    actor User
    participant App
    participant AuthProvider
    participant SecureStorage
    participant LoginScreen

    User->>App: Open app (after update)
    App->>AuthProvider: build()
    AuthProvider->>SecureStorage: check accessToken
    SecureStorage-->>AuthProvider: null
    AuthProvider->>SecureStorage: check 'APItoken' (legacy)
    SecureStorage-->>AuthProvider: "abc123" (exists!)
    AuthProvider-->>App: state = {requiresMigration: true}
    App->>LoginScreen: Navigate with migration banner
    LoginScreen->>LoginScreen: Show "Please sign in with your email"
    User->>LoginScreen: Enter credentials
    LoginScreen->>AuthProvider: login(email, password)
    AuthProvider->>AuthProvider: On success...
    AuthProvider->>SecureStorage: clearLegacyApiKey()
    AuthProvider->>SecureStorage: storeNewTokens()
```

## Deployment View

### Single Application Deployment
- **Environment**: Mobile app (iOS/Android)
- **Configuration**: No environment variables in app; API URL in `api_constants.dart`
- **Dependencies**: Backend `/mobile/auth/login` endpoint must be deployed first
- **Performance**: Login < 2s, token refresh < 500ms

### Backend Coordination
- **Deployment Order**:
  1. Backend deploys new auth endpoints
  2. Backend maintains API key validation (for old app versions)
  3. App release with JWT auth
  4. After 30 days, backend can deprecate API key validation

## Cross-Cutting Concepts

### Pattern Documentation

```yaml
# Existing patterns to follow
- pattern: Riverpod AsyncNotifier pattern
  relevance: CRITICAL
  why: "All providers must use AsyncNotifier, not StateNotifier"

- pattern: Clean Architecture layers
  relevance: HIGH
  why: "Maintain separation: presentation → domain → data"

- pattern: Freezed data models
  relevance: HIGH
  why: "All API response models use Freezed with abstract class"

- pattern: Equatable entities
  relevance: HIGH
  why: "Domain entities use Equatable, not Freezed"
```

### System-Wide Patterns

- **Security**:
  - JWT tokens stored in SecureStorage (encrypted)
  - Never log tokens
  - Clear all tokens on logout

- **Error Handling**:
  - Use `ErrorDisplay.fromError()` for consistent error UI
  - Wrap async operations in `AsyncValue.guard()`

- **State Management**:
  - Use `AuthStatus` enum for all auth states
  - Never expose raw tokens to UI layer

### Implementation Patterns

#### Auth State Machine Pattern

```dart
// Auth provider must follow this state machine
enum AuthStatus {
  initial,        // App just started, checking storage
  unauthenticated, // No valid session
  authenticating,  // Login in progress
  authenticated,   // Valid session active
  refreshing,      // Token refresh in progress
  error,           // Auth operation failed
  sessionExpired,  // Refresh failed, need re-login
}

// Valid transitions:
// initial → unauthenticated | authenticated
// unauthenticated → authenticating
// authenticating → authenticated | error
// authenticated → refreshing | unauthenticated
// refreshing → authenticated | sessionExpired
// error → unauthenticated | authenticating
// sessionExpired → unauthenticated
```

## Architecture Decisions

- [x] **ADR-1: Adapt scheduling auth pattern**
  - Rationale: Scheduling auth has mature JWT handling, biometrics, state machine
  - Trade-offs: Some scheduling-specific code may not be needed
  - User confirmed: ✅ Yes

- [x] **ADR-2: Keep form-urlencoded for legacy endpoints**
  - Rationale: Minimal backend changes, lower risk
  - Trade-offs: Two content types in same app
  - User confirmed: ✅ Yes

- [x] **ADR-3: Move scheduling routes under store context**
  - Rationale: User requested scheduling within store selection
  - Trade-offs: Must update all scheduling route references
  - User confirmed: ✅ Yes

- [x] **ADR-4: Force re-login for legacy users**
  - Rationale: Clean migration, no hybrid auth state
  - Trade-offs: Minor user friction on first update
  - User confirmed: ✅ Yes

## Quality Requirements

- **Performance**:
  - Login completes in < 2 seconds
  - Token refresh completes in < 500ms
  - Biometric prompt appears in < 100ms

- **Security**:
  - Tokens encrypted at rest (SecureStorage)
  - Access token TTL: 15 minutes
  - Refresh token TTL: 30 days
  - Tokens revoked server-side on logout

- **Reliability**:
  - Automatic token refresh with 5-minute buffer
  - Graceful degradation on network failure
  - No data loss during migration

## Risks and Technical Debt

### Known Technical Issues
- Current API key stored in SharedPreferences (not secure)
- Two separate Dio clients exist (main + scheduling)

### Implementation Gotchas
- Riverpod 3.x requires `AsyncNotifier`, not `StateNotifier`
- Freezed 3.x requires `abstract class`, not `class`
- Dio form-urlencoded requires `Options(contentType: Headers.formUrlEncodedContentType)`
- JWT interceptor must prevent concurrent refresh attempts

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Successful Login**
```gherkin
Given: User is on login screen
And: Backend auth endpoint is available
When: User enters valid email and password
And: User taps "Sign In"
Then: User is redirected to dashboard
And: JWT tokens are stored in secure storage
And: User info is displayed correctly
```

**Scenario 2: Invalid Credentials**
```gherkin
Given: User is on login screen
When: User enters invalid email or password
And: User taps "Sign In"
Then: Error message "Invalid email or password" is displayed
And: User remains on login screen
And: No tokens are stored
```

**Scenario 3: Token Refresh**
```gherkin
Given: User is authenticated with soon-to-expire access token
When: User makes an API request
Then: Access token is refreshed automatically
And: Original request completes successfully
And: New tokens are stored
```

**Scenario 4: Legacy Migration**
```gherkin
Given: User has legacy API key stored
And: User has no JWT tokens
When: User opens updated app
Then: User is shown login screen with migration message
When: User logs in successfully
Then: Legacy API key is cleared
And: New JWT tokens are stored
And: User is redirected to dashboard
```

**Scenario 5: Biometric Login**
```gherkin
Given: User has biometric enabled
And: User has valid refresh token
When: User opens app
And: User authenticates with Face ID / Touch ID
Then: Access token is refreshed using refresh token
And: User is redirected to dashboard
```

### Test Coverage Requirements

- **Business Logic**: Auth state transitions, token expiry detection, migration detection
- **User Interface**: Login form validation, error display, biometric button visibility
- **Integration Points**: API calls, secure storage operations, biometric service
- **Edge Cases**: Network offline, token expired during request, biometric lockout

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| API Key | Legacy static authentication token | Being replaced by JWT |
| Store | A retail location managed in the app | Users may have access to multiple stores |
| typeNum | Unique identifier for a store | e.g., "bk01", "pc02" |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| JWT | JSON Web Token | Standard for secure token-based auth |
| Access Token | Short-lived token for API requests | 15 minute TTL |
| Refresh Token | Long-lived token to obtain new access tokens | 30 day TTL |
| Bearer Token | Token sent in HTTP Authorization header | Format: "Bearer {token}" |
| Biometric | Face ID (iOS) or Fingerprint (Android) | For quick re-authentication |
