# 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 & Platform Requirements**
- Flutter 3.38.3+ / Dart 3.10.1+ (existing tech stack)
- iOS 14+ and Android 8+ platform support (per PRD)
- Must integrate with existing app architecture (Clean Architecture + Riverpod 3.x)
- All scheduling features must work within existing app structure

**CON-2: Coding Standards & Patterns**
- Freezed 3.x for data models (`abstract class` syntax required)
- Equatable for domain entities
- AsyncNotifier pattern for Riverpod providers
- Extension-based mappers for model↔entity conversion
- Form-encoded POST requests (existing API pattern)

**CON-3: Authentication & Security**
- JWT authentication replacing API key auth
- Access token (15 min TTL) + Refresh token (30 day TTL)
- Biometric authentication (Face ID/Touch ID) optional
- Secure token storage via flutter_secure_storage
- Full audit trail on approval actions (device ID, location, timestamp)

**CON-4: API Dependencies**
- Mobile Scheduling API must be production-ready (per PRD)
- 99.5% uptime, <500ms p95 response time SLA
- All endpoints use POST with JSON body and JWT Bearer auth
- API base: `https://api.buyerkiosk.com/api/mobile/scheduling`

**CON-5: Backwards Compatibility**
- Existing features (store metrics, queue, completed buys) must continue working
- API key auth must work until user migrates to JWT
- Migration path: API key → JWT with immediate old key invalidation

## Implementation Context

### Required Context Sources

```yaml
# Internal Documentation
- doc: docs/specs/002-manager-scheduling-features/product-requirements.md
  relevance: CRITICAL
  why: "Defines all 14 features, acceptance criteria, and business rules"

- doc: docs/api/mobile-scheduling-openapi.yaml
  relevance: CRITICAL
  why: "Complete API specification for all scheduling endpoints"

# Existing App Patterns (discovered via exploration)
- file: lib/data/models/store_model.dart
  relevance: HIGH
  why: "Freezed model pattern to follow for scheduling models"

- file: lib/domain/entities/store.dart
  relevance: HIGH
  why: "Equatable entity pattern to follow for scheduling entities"

- file: lib/data/models/mappers/store_mapper.dart
  relevance: HIGH
  why: "Extension mapper pattern to follow"

- file: lib/presentation/providers/auth_provider.dart
  relevance: CRITICAL
  why: "Auth state management pattern - must be extended for JWT"

- file: lib/presentation/providers/dashboard_provider.dart
  relevance: HIGH
  why: "AsyncNotifier pattern with error caching to follow"

- file: lib/presentation/providers/workbook_notes_provider.dart
  relevance: HIGH
  why: "Complex state with pagination pattern for request lists"

- file: lib/core/network/api_interceptors.dart
  relevance: CRITICAL
  why: "Auth interceptor must be modified for JWT token injection"

- file: lib/presentation/screens/dashboard/dashboard_screen.dart
  relevance: HIGH
  why: "ConsumerStatefulWidget screen pattern to follow"

- file: lib/core/constants/permission_constants.dart
  relevance: HIGH
  why: "Permission system - must add new scheduling permissions"

# External Documentation
- url: https://pub.dev/packages/local_auth
  relevance: HIGH
  why: "Biometric authentication package for Face ID/Touch ID"

- url: https://pub.dev/packages/flutter_secure_storage
  relevance: HIGH
  why: "Secure storage for JWT tokens (already in use)"

- url: https://pub.dev/packages/device_info_plus
  relevance: HIGH
  why: "Device fingerprint capture for audit trail"

- url: https://pub.dev/packages/geolocator
  relevance: MEDIUM
  why: "GPS location capture for audit trail (optional)"

- url: https://pub.dev/packages/connectivity_plus
  relevance: MEDIUM
  why: "Network state monitoring for offline handling"

- url: https://pub.dev/packages/uuid
  relevance: MEDIUM
  why: "Generate idempotency keys for mutations"
```

### New Package Dependencies

```yaml
dependencies:
  # Auth & Security
  local_auth: ^2.1.0         # Biometric authentication
  flutter_secure_storage: ^9.0.0  # (already present) Token storage

  # Device & Location
  device_info_plus: ^10.0.0  # Device fingerprint for audit trail
  geolocator: ^11.0.0        # GPS location for audit trail
  permission_handler: ^11.0.0 # Location permission management

  # Networking & State
  connectivity_plus: ^5.0.0  # Network state monitoring
  uuid: ^4.0.0               # Idempotency key generation

  # Notifications
  firebase_messaging: ^14.0.0  # (already present) FCM
  flutter_local_notifications: ^16.0.0  # Notification actions

Platform Setup Required:
  iOS:
    - Add NSFaceIDUsageDescription to Info.plist
    - Add NSLocationWhenInUseUsageDescription to Info.plist
    - Configure notification categories for quick actions
    - Enable Push Notifications capability

  Android:
    - Add location permissions to AndroidManifest.xml
    - Configure notification channels
    - Add biometric permission
```

### Implementation Boundaries

- **Must Preserve**:
  - All existing dashboard, store detail, queue, completed buys functionality
  - Existing API key auth flow until user migrates
  - All existing Riverpod provider patterns
  - Existing theme and styling (AppColors, AppTheme)

- **Can Modify**:
  - Auth provider to support JWT in addition to API key
  - API interceptor to handle both auth methods
  - Permission constants to add scheduling permissions
  - Router to add scheduling routes
  - Dashboard screen to add scheduling entry point

- **Must Not Touch**:
  - Existing store metrics calculation logic
  - Existing Ably real-time subscription logic
  - Existing workbook notes feature (separate domain)
  - Existing task management feature (separate domain)

### External Interfaces

#### System Context Diagram

```mermaid
graph TB
    subgraph Mobile App
        App[BuyerKiosk Live Flutter]
    end

    Manager[Manager/Owner] --> App

    subgraph Backend Services
        SchedulingAPI[Mobile Scheduling API<br/>JWT Auth]
        ExistingAPI[Existing mobile.php API<br/>API Key Auth]
    end

    App --> SchedulingAPI
    App --> ExistingAPI

    subgraph External Services
        FCM[Firebase Cloud Messaging]
        Location[GPS Location Services]
        Biometrics[Face ID / Touch ID]
    end

    App --> FCM
    App --> Location
    App --> Biometrics

    SchedulingAPI --> FCM
```

#### Interface Specifications

```yaml
# Inbound Interfaces (what calls this system)
inbound:
  - name: "Manager User Interaction"
    type: Touch/Gesture
    format: Flutter UI Events
    authentication: Biometric or Password
    data_flow: "User actions for approvals, shift management"

# Outbound Interfaces (what this system calls)
outbound:
  - name: "Mobile Scheduling API"
    type: HTTPS
    format: REST (JSON body, POST requests)
    authentication: JWT Bearer Token
    doc: docs/api/mobile-scheduling-openapi.yaml
    data_flow: "Schedule data, approvals, shift CRUD"
    criticality: CRITICAL

  - name: "Existing Mobile API"
    type: HTTPS
    format: REST (form-encoded body, POST requests)
    authentication: API Key in body
    data_flow: "Store metrics, queue, completed buys (existing)"
    criticality: HIGH

  - name: "Firebase Cloud Messaging"
    type: HTTPS
    format: FCM SDK
    authentication: FCM Token
    data_flow: "Push notification registration"
    criticality: MEDIUM

  - name: "Location Services"
    type: Platform API
    format: Native SDK
    authentication: Permission prompt
    data_flow: "GPS coordinates for audit trail"
    criticality: LOW (optional)

  - name: "Biometric Services"
    type: Platform API
    format: local_auth package
    authentication: Device biometrics
    data_flow: "Authentication confirmation"
    criticality: MEDIUM

# Data Interfaces
data:
  - name: "Secure Local Storage"
    type: flutter_secure_storage
    connection: Platform keychain/keystore
    data_flow: "JWT tokens, refresh token, user preferences"
```

### Project Commands

```bash
# Environment Setup
Install Dependencies: flutter pub get
Environment Variables: N/A (uses ApiConstants)
Start Development: flutter run

# Code Generation (CRITICAL - run after model changes)
Generate Freezed/JSON: dart run build_runner build --delete-conflicting-outputs

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

# Code Quality Commands
Linting: flutter analyze
Formatting: dart format lib/

# Build & Deployment
Build Android Debug: flutter build apk --debug
Build iOS Debug: flutter build ios --debug --no-codesign
Build Android Release: flutter build apk --release
Build iOS Release: flutter build ios --release
```

## Solution Strategy

**Architecture Pattern: Feature-Slice Clean Architecture**

We extend the existing Clean Architecture with Riverpod by adding a new "scheduling" feature slice that follows the established patterns:

```
lib/
├── core/
│   ├── constants/
│   │   └── scheduling_constants.dart (NEW)
│   ├── network/
│   │   ├── api_interceptors.dart (MODIFY - add JWT support)
│   │   └── scheduling_api_client.dart (NEW)
│   └── services/
│       └── biometric_service.dart (NEW)
├── data/
│   ├── models/
│   │   └── scheduling/ (NEW)
│   │       ├── auth_response_model.dart
│   │       ├── shift_model.dart
│   │       ├── pending_request_model.dart
│   │       ├── working_employee_model.dart
│   │       └── ...
│   ├── models/mappers/
│   │   └── scheduling/ (NEW)
│   └── repositories/
│       └── scheduling_repository_impl.dart (NEW)
├── domain/
│   ├── entities/
│   │   └── scheduling/ (NEW)
│   └── repositories/
│       └── scheduling_repository.dart (NEW)
├── presentation/
│   ├── providers/
│   │   └── scheduling/ (NEW)
│   │       ├── scheduling_auth_provider.dart
│   │       ├── manager_dashboard_provider.dart
│   │       ├── pending_requests_provider.dart
│   │       └── ...
│   ├── screens/
│   │   └── scheduling/ (NEW)
│   │       ├── login_screen.dart
│   │       ├── store_selector_screen.dart
│   │       ├── manager_dashboard_screen.dart
│   │       ├── pending_requests_screen.dart
│   │       └── ...
│   └── widgets/
│       └── scheduling/ (NEW)
└── router/
    └── app_router.dart (MODIFY - add scheduling routes)
```

**Integration Approach:**
1. **Parallel Auth Systems**: JWT auth runs alongside existing API key auth during migration
2. **Feature Flag**: Scheduling features gated by user login status (JWT = scheduling enabled)
3. **Shared Components**: Reuse existing widgets (ErrorDisplay, LoadingIndicator, metric cards)
4. **Store Context**: Leverage existing store selection pattern, extend for multi-store

**Justification:**
- Follows established project patterns (minimize learning curve, maximize consistency)
- Feature-slice isolation enables parallel development and easy testing
- Clean separation allows JWT and API key auth to coexist during migration
- Existing Riverpod infrastructure handles state management

**Key Decisions:**
- **ADR-1**: Modify existing Dio client to support dual auth (JWT + API key)
- **ADR-2**: JWT tokens stored in flutter_secure_storage (same as existing API key)
- **ADR-3**: Family providers for all per-store scheduling data
- **ADR-4**: Permission integration with existing permission system
- **ADR-5**: Biometric auth gates refresh token access, not individual API calls
- **ADR-6**: Scheduling routes as separate `/scheduling/*` sub-tree

## Building Block View

### Components

```mermaid
graph TB
    subgraph Presentation Layer
        LoginScreen[Login Screen]
        StoreSelectorScreen[Store Selector]
        ManagerDashboard[Manager Dashboard]
        PendingRequests[Pending Requests]
        WhosWorking[Who's Working]
        MySchedule[My Schedule]
        ShiftCRUD[Shift CRUD]
        LaborCost[Labor Cost View]
    end

    subgraph Providers
        SchedulingAuthProvider[Scheduling Auth Provider]
        SelectedStoreProvider[Selected Store Provider]
        ManagerDashboardProvider[Manager Dashboard Provider]
        PendingRequestsProvider[Pending Requests Provider]
        WhosWorkingProvider[Who's Working Provider]
        MyScheduleProvider[My Schedule Provider]
        ShiftProvider[Shift Provider]
        LaborCostProvider[Labor Cost Provider]
    end

    subgraph Domain Layer
        SchedulingRepository[(Scheduling Repository)]
    end

    subgraph Data Layer
        SchedulingApiClient[Scheduling API Client]
        SecureStorage[Secure Storage]
        BiometricService[Biometric Service]
    end

    subgraph External
        SchedulingAPI[Mobile Scheduling API]
        LocationService[Location Service]
    end

    LoginScreen --> SchedulingAuthProvider
    StoreSelectorScreen --> SelectedStoreProvider
    ManagerDashboard --> ManagerDashboardProvider
    PendingRequests --> PendingRequestsProvider
    WhosWorking --> WhosWorkingProvider
    MySchedule --> MyScheduleProvider
    ShiftCRUD --> ShiftProvider
    LaborCost --> LaborCostProvider

    SchedulingAuthProvider --> SchedulingRepository
    ManagerDashboardProvider --> SchedulingRepository
    PendingRequestsProvider --> SchedulingRepository
    WhosWorkingProvider --> SchedulingRepository
    MyScheduleProvider --> SchedulingRepository
    ShiftProvider --> SchedulingRepository
    LaborCostProvider --> SchedulingRepository

    SchedulingRepository --> SchedulingApiClient
    SchedulingApiClient --> SchedulingAPI
    SchedulingAuthProvider --> SecureStorage
    SchedulingAuthProvider --> BiometricService
    PendingRequestsProvider --> LocationService
```

### Directory Map

**Core Layer (NEW/MODIFY)**
```
lib/core/
├── constants/
│   ├── api_constants.dart                    # MODIFY: Add scheduling API base URL
│   ├── permission_constants.dart             # MODIFY: Add scheduling permissions
│   └── scheduling/                           # NEW
│       └── scheduling_constants.dart         # NEW: Request types, status enums
├── network/
│   └── api_interceptors.dart                 # MODIFY: Support dual auth (JWT + API key)
└── services/
    └── biometric_service.dart                # NEW: local_auth wrapper
```

**Data Layer (NEW)**
```
lib/data/
├── models/
│   └── scheduling/                           # NEW
│       ├── auth_response_model.dart          # Login response with tokens
│       ├── user_info_model.dart              # User profile
│       ├── store_access_model.dart           # Store access with role
│       ├── shift_model.dart                  # Shift details
│       ├── pending_request_model.dart        # Time-off/swap/override request
│       ├── working_employee_model.dart       # Who's working status
│       ├── labor_cost_model.dart             # Weekly labor costs
│       ├── manager_dashboard_model.dart      # Dashboard summary
│       └── batch_result_model.dart           # Batch operation results
├── models/mappers/
│   └── scheduling/                           # NEW
│       ├── shift_mapper.dart
│       ├── request_mapper.dart
│       └── employee_mapper.dart
├── datasources/
│   └── scheduling/                           # NEW
│       └── scheduling_remote_datasource.dart # API calls implementation
└── repositories/
    └── scheduling_repository_impl.dart       # NEW: Repository implementation
```

**Domain Layer (NEW)**
```
lib/domain/
├── entities/
│   └── scheduling/                           # NEW
│       ├── scheduling_auth_state.dart        # Auth state entity
│       ├── store_access.dart                 # Store with role info
│       ├── shift.dart                        # Shift entity
│       ├── pending_request.dart              # Request entity
│       ├── working_employee.dart             # Employee status entity
│       ├── manager_dashboard.dart            # Dashboard entity
│       └── labor_cost.dart                   # Labor cost entity
└── repositories/
    └── scheduling_repository.dart            # NEW: Abstract interface
```

**Presentation Layer (NEW/MODIFY)**
```
lib/presentation/
├── providers/
│   ├── providers.dart                        # MODIFY: Export scheduling providers
│   └── scheduling/                           # NEW
│       ├── scheduling_auth_provider.dart     # JWT auth state management
│       ├── selected_scheduling_store_provider.dart  # Current store selection
│       ├── manager_dashboard_provider.dart   # Dashboard data
│       ├── pending_requests_provider.dart    # Request list with pagination
│       ├── request_history_provider.dart     # Feature 11: Past decisions
│       ├── whos_working_provider.dart        # Today's employees
│       ├── my_schedule_provider.dart         # Manager's own schedule
│       ├── employee_schedule_provider.dart   # Feature 12: Any employee's schedule
│       ├── shift_provider.dart               # Shift CRUD operations
│       ├── labor_cost_provider.dart          # Labor cost data
│       ├── conflicts_provider.dart           # Feature 13: Schedule conflicts
│       └── notification_action_provider.dart # Feature 14: Quick actions
├── screens/
│   └── scheduling/                           # NEW
│       ├── login/
│       │   └── scheduling_login_screen.dart
│       ├── store_selector/
│       │   └── store_selector_screen.dart
│       ├── dashboard/
│       │   └── scheduling_dashboard_screen.dart
│       ├── requests/
│       │   ├── pending_requests_screen.dart
│       │   ├── request_detail_screen.dart
│       │   └── request_history_screen.dart   # Feature 11
│       ├── whos_working/
│       │   └── whos_working_screen.dart
│       ├── my_schedule/
│       │   └── my_schedule_screen.dart
│       ├── employee_schedule/                # Feature 12
│       │   └── employee_schedule_screen.dart
│       ├── shifts/
│       │   ├── shift_create_screen.dart
│       │   └── shift_edit_screen.dart
│       ├── labor_cost/
│       │   └── labor_cost_screen.dart
│       └── conflicts/                        # Feature 13
│           └── conflicts_screen.dart
└── widgets/
    └── scheduling/                           # NEW
        ├── request_card.dart                 # Pending request card
        ├── history_request_card.dart         # Feature 11: Past request card
        ├── shift_card.dart                   # Shift display card
        ├── employee_status_tile.dart         # Who's working tile
        ├── employee_picker.dart              # Feature 12: Employee search/select
        ├── dashboard_stat_card.dart          # Dashboard metric
        ├── conflict_card.dart                # Feature 13: Conflict display
        ├── batch_action_bar.dart             # Batch selection bar
        └── request_filter_chips.dart         # Filter chips
```

**Router (MODIFY)**
```
lib/router/
└── app_router.dart                           # MODIFY: Add scheduling routes
```

### Interface Specifications

#### Data Storage Changes

No database changes - this is a mobile app consuming backend API.

**Local Storage (flutter_secure_storage):**
```yaml
Keys:
  - scheduling_access_token: JWT access token (15 min TTL)
  - scheduling_refresh_token: JWT refresh token (30 day TTL)
  - scheduling_token_expiry: ISO8601 timestamp of access token expiry
  - scheduling_selected_store: Last selected store typeNum
  - scheduling_biometric_enabled: "true" | "false"
  - scheduling_user_id: Logged in user ID
  - scheduling_user_email: User email for display
```

#### API Integration (Mobile Scheduling API)

Full API specification in `docs/api/mobile-scheduling-openapi.yaml`

**Key Endpoints by Feature:**

| Feature | Endpoints |
|---------|-----------|
| JWT Auth | POST /auth/login, POST /auth/refresh, POST /auth/logout |
| Device Token | POST /auth/device-token |
| Manager Dashboard | POST /{typeNum}/manager/dashboard |
| Pending Requests | POST /{typeNum}/manager/requests |
| Request Decision | POST /{typeNum}/manager/requests/{id}/decision |
| Batch Approval | POST /{typeNum}/manager/requests/batch |
| Who's Working | POST /{typeNum}/manager/whos-working |
| My Schedule | POST /{typeNum}/schedule/upcoming |
| Shift CRUD | POST /{typeNum}/manager/shifts/create, /update, /delete |
| Labor Cost | POST /{typeNum}/manager/labor-cost |
| Request History (F11) | POST /{typeNum}/manager/requests/history |
| Employee Schedule (F12) | POST /{typeNum}/manager/employees/{employeeId}/schedule |
| Employee List (F12) | POST /{typeNum}/manager/employees |
| Conflicts (F13) | POST /{typeNum}/manager/conflicts |
| Resolve Conflict (F13) | POST /{typeNum}/manager/conflicts/{conflictId}/resolve |

#### Application Data Models

**Auth Models (Freezed)**
```pseudocode
MODEL: AuthResponseModel
  FIELDS:
    accessToken: String
    refreshToken: String
    expiresIn: int (seconds)
    refreshExpiresAt: DateTime
    user: UserInfoModel
    stores: List<StoreAccessModel>

MODEL: UserInfoModel
  FIELDS:
    userId: int
    firstName: String
    lastName: String
    email: String

MODEL: StoreAccessModel
  FIELDS:
    typeNum: String
    storeName: String
    role: String
    roleId: int
    isManager: bool
    employeeId: int
    pendingRequestCount: int  # For store picker (PRD Feature 3)
    hasSchedulingAccess: bool # Feature flag per store
```

**Scheduling Models (Freezed)**
```pseudocode
MODEL: ShiftModel
  FIELDS:
    shiftId: int
    date: DateTime
    startTime: String (HH:mm:ss)
    endTime: String (HH:mm:ss)
    position: String?
    positionColor: String?
    totalHours: double
    isPublished: bool
    notes: String?
    employeeId: int? (for manager views)
    employeeName: String? (for manager views)

MODEL: PendingRequestModel
  FIELDS:
    requestId: int
    type: String (time_off | swap | override)
    employeeId: int
    employeeName: String
    employeePhoto: String?
    submittedAt: DateTime
    expiresAt: DateTime?
    # Type-specific fields
    startDate: DateTime? (time_off)
    endDate: DateTime? (time_off)
    requestType: String? (vacation | sick | personal)
    reason: String?
    initiatorShift: ShiftModel? (swap)
    targetShift: ShiftModel? (swap)
    conflicts: List<String>? (swap)
    requestedTime: DateTime? (override)
    punchType: String? (clockIn | clockOut)

MODEL: WorkingEmployeeModel
  FIELDS:
    employeeId: int
    name: String
    profilePicture: String?
    status: String (scheduled | clocked_in | clocked_out | late | on_leave)
    scheduledStart: String
    scheduledEnd: String
    clockedInAt: String?
    minutesLate: int?
    position: String?
    shiftId: int

MODEL: ManagerDashboardModel
  FIELDS:
    today: TodayStatsModel
    pendingRequests: PendingCountsModel
    thisWeek: LaborSummaryModel

MODEL: LaborCostModel
  FIELDS:
    weekStart: DateTime
    weekEnd: DateTime
    totalScheduledHours: double
    totalLaborCost: double
    budgetTarget: double?
    budgetVariance: double?
    byDay: List<DayLaborModel>

# Feature 11: Request History
MODEL: RequestHistoryItemModel
  FIELDS:
    requestId: int
    type: String (time_off | swap | override)
    employeeId: int
    employeeName: String
    employeePhoto: String?
    status: String (approved | denied)
    submittedAt: DateTime
    processedAt: DateTime
    processedBy: ProcessedByModel
    managerNote: String?  # Denial reason or approval note
    # Type-specific fields (same as PendingRequestModel)

MODEL: ProcessedByModel
  FIELDS:
    managerId: int
    managerName: String
    deviceId: String?
    latitude: double?
    longitude: double?

# Feature 12: Employee Schedule View
MODEL: EmployeeScheduleModel
  FIELDS:
    employeeId: int
    employeeName: String
    employeePhoto: String?
    position: String?
    weekStart: DateTime
    weekEnd: DateTime
    shifts: List<ShiftModel>
    totalHours: double
    pendingTimeOff: List<TimeOffPeriodModel>?

MODEL: TimeOffPeriodModel
  FIELDS:
    startDate: DateTime
    endDate: DateTime
    type: String (vacation | sick | personal)
    status: String (pending | approved)

# Feature 13: Schedule Conflicts
MODEL: ScheduleConflictModel
  FIELDS:
    conflictId: String
    type: String (overlap | overtime | availability | coverage_gap)
    severity: String (warning | error)
    description: String
    affectedEmployees: List<AffectedEmployeeModel>
    affectedShifts: List<ShiftModel>
    suggestedResolutions: List<ResolutionModel>?
    createdAt: DateTime

MODEL: AffectedEmployeeModel
  FIELDS:
    employeeId: int
    employeeName: String

MODEL: ResolutionModel
  FIELDS:
    resolutionId: String
    description: String
    action: String (swap | reassign | delete | adjust_time)
    targetShiftId: int?
    targetEmployeeId: int?

# Feature 14: Notification Quick Actions
MODEL: NotificationPayloadModel
  FIELDS:
    notificationId: String
    type: String (request_pending | shift_reminder | conflict_detected)
    typeNum: String
    requestId: int?
    requestType: String?
    shiftId: int?
    conflictId: String?
    title: String
    body: String
    actionable: bool
    expiresAt: DateTime?
```

**Domain Entities (Equatable)**
```pseudocode
ENTITY: SchedulingAuthState
  FIELDS:
    isAuthenticated: bool
    accessToken: String?
    refreshToken: String?
    tokenExpiry: DateTime?
    user: UserInfo?
    stores: List<StoreAccess>
    selectedStore: StoreAccess?
    biometricEnabled: bool

  BEHAVIORS:
    isTokenExpired(): bool
    needsRefresh(): bool (5 min before expiry)
    canAccessScheduling(): bool (authenticated + selected store)

ENTITY: PendingRequest
  FIELDS:
    requestId: int
    type: RequestType (enum)
    employeeName: String
    submittedAt: DateTime
    # ... type-specific fields

  BEHAVIORS:
    isExpiringSoon(): bool (< 24 hours)
    typeLabel: String (human readable)

ENTITY: WorkingEmployee
  FIELDS:
    # ... same as model

  BEHAVIORS:
    statusLabel: String
    statusColor: Color
    isLate: bool
```

### Implementation Examples

#### Example: Unified JWT Auth Interceptor (Migration-Aware)

**Why this example**: The interceptor supports migration from API key to JWT, with JWT taking precedence when available.

```dart
// Example: Unified auth interceptor in api_interceptors.dart
class AuthInterceptor extends Interceptor {
  final SecureStorage _secureStorage;
  final SchedulingAuthProvider _authProvider;

  @override
  Future<void> onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    // JWT auth takes precedence (post-migration state)
    final accessToken = await _secureStorage.read(key: 'scheduling_access_token');

    if (accessToken != null) {
      // User has migrated to JWT - use for ALL endpoints
      options.headers['Authorization'] = 'Bearer $accessToken';

      // Scheduling API expects JSON, legacy expects form-encoded
      final isSchedulingApi = options.path.contains('/mobile/scheduling');
      if (isSchedulingApi) {
        options.contentType = Headers.jsonContentType;
      }
      // Note: Backend must accept JWT on legacy endpoints too
    } else {
      // Fallback: API key for users who haven't migrated yet
      final apiKey = await _secureStorage.read(key: SecureStorageKeys.apiKey);
      if (apiKey != null) {
        final Map<String, dynamic> bodyData = {};
        if (options.data is Map) {
          bodyData.addAll(Map<String, dynamic>.from(options.data as Map));
        }
        bodyData[ApiConstants.paramApiKey] = apiKey;
        options.data = bodyData;
      }
    }

    return handler.next(options);
  }

  @override
  Future<void> onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    // Handle 401 - attempt JWT refresh if we have JWT auth
    if (err.response?.statusCode == 401) {
      final hasJwt = await _secureStorage.read(key: 'scheduling_access_token') != null;
      if (hasJwt) {
        final refreshed = await _attemptTokenRefresh();
        if (refreshed) {
          // Retry original request with new token
          return handler.resolve(await _retryRequest(err.requestOptions));
        }
        // Refresh failed - force logout
        await _authProvider.forceLogout();
      }
    }
    return handler.next(err);
  }

  Future<bool> _attemptTokenRefresh() async {
    final refreshToken = await _secureStorage.read(key: 'scheduling_refresh_token');
    if (refreshToken == null) return false;

    try {
      final response = await Dio().post(
        '${ApiConstants.schedulingBaseUrl}/auth/refresh',
        data: {'refreshToken': refreshToken},
      );

      if (response.statusCode == 200) {
        await _secureStorage.write(
          key: 'scheduling_access_token',
          value: response.data['accessToken'],
        );
        return true;
      }
    } catch (_) {}
    return false;
  }
}
```

#### Example: Batch Approval with Partial Failure Handling

**Why this example**: Batch operations must handle partial failures gracefully and provide clear feedback.

```dart
// Example: Batch approval handling in provider
Future<BatchApprovalResult> batchApprove(
  List<int> requestIds,
  RequestType type,
  String? note,
) async {
  // Validate batch size
  if (requestIds.length > 10) {
    throw BatchLimitExceededException(max: 10, actual: requestIds.length);
  }

  // Capture audit context
  final location = await _locationService.getCurrentLocation();
  final deviceId = await _deviceService.getDeviceId();

  final response = await _repository.batchProcess(
    requests: requestIds.map((id) => BatchRequest(id: id, type: type)).toList(),
    approved: true,
    note: note,
    auditContext: AuditContext(
      deviceId: deviceId,
      latitude: location?.latitude,
      longitude: location?.longitude,
      timestamp: DateTime.now(),
    ),
  );

  // Update state based on results
  final successIds = response.processed.map((r) => r.id).toSet();
  final failedItems = response.failed;

  // Remove successful items from pending list
  state = state.copyWith(
    requests: state.requests.where((r) => !successIds.contains(r.id)).toList(),
    lastBatchResult: BatchResult(
      processed: response.processed,
      failed: failedItems,
    ),
  );

  return BatchApprovalResult(
    successCount: successIds.length,
    failures: failedItems,
  );
}
```

## Runtime View

### Primary Flow: Request Approval

1. Manager opens app → checks JWT auth state
2. If authenticated with valid token → loads last selected store
3. Manager navigates to Scheduling → Dashboard
4. Dashboard provider fetches pending counts
5. Manager taps pending request count → navigates to Pending Requests
6. Pending Requests provider loads request list with pagination
7. Manager taps request → shows detail sheet
8. Manager taps Approve → captures location (if permitted)
9. Provider sends approval with audit trail
10. Success → removes from list, shows confirmation
11. Employee receives push notification

```mermaid
sequenceDiagram
    actor Manager
    participant App
    participant AuthProvider
    participant DashboardProvider
    participant RequestsProvider
    participant Repository
    participant API
    participant Location

    Manager->>App: Open App
    App->>AuthProvider: Check auth state
    AuthProvider-->>App: Authenticated (JWT valid)
    App->>App: Navigate to Dashboard
    App->>DashboardProvider: Load dashboard
    DashboardProvider->>Repository: getManagerDashboard(typeNum)
    Repository->>API: POST /{typeNum}/manager/dashboard
    API-->>Repository: Dashboard data
    Repository-->>DashboardProvider: ManagerDashboard entity
    DashboardProvider-->>App: Render dashboard

    Manager->>App: Tap pending requests (5)
    App->>RequestsProvider: Load pending requests
    RequestsProvider->>Repository: getPendingRequests(typeNum)
    Repository->>API: POST /{typeNum}/manager/requests
    API-->>Repository: Request list
    Repository-->>RequestsProvider: List<PendingRequest>
    RequestsProvider-->>App: Render request list

    Manager->>App: Tap request
    App->>App: Show detail sheet
    Manager->>App: Tap Approve
    App->>Location: Get current location
    Location-->>App: Location (or null)
    App->>RequestsProvider: approve(requestId, auditContext)
    RequestsProvider->>Repository: processRequest(...)
    Repository->>API: POST /{typeNum}/manager/requests/{id}/decision
    API-->>Repository: Success
    Repository-->>RequestsProvider: Updated state
    RequestsProvider-->>App: Remove from list, show toast
```

### Error Handling

| Error Type | Handling |
|------------|----------|
| Network unavailable | Show "No connection" banner, disable action buttons, show cached dashboard if available |
| Token expired during action | Auto-refresh, retry once, if fails force login |
| Request already processed | Show "Already processed by [name]" message, remove from list |
| Batch partial failure | Show summary "5 approved, 2 failed", keep failed in list with error reason |
| Location permission denied | Proceed without location (null in audit trail) |
| Biometric failure (3x) | Fall back to password entry |
| API 403 during session | Force logout, clear tokens, show "Session expired" |
| API 500 error | Show "Server error. Try again." with retry button |

### Complex Logic: Auth State Machine

```
ALGORITHM: Auth State Management
INPUT: user_action (login, refresh, logout, biometric_auth)
OUTPUT: updated_auth_state

STATES: Unauthenticated, Authenticating, Authenticated, Refreshing, ExpiredSession

TRANSITIONS:
  Unauthenticated + login_request → Authenticating
  Authenticating + login_success → Authenticated (store tokens, prompt biometric setup)
  Authenticating + login_failure → Unauthenticated (show error)

  Authenticated + token_near_expiry → Refreshing
  Refreshing + refresh_success → Authenticated (update access token)
  Refreshing + refresh_failure → ExpiredSession

  ExpiredSession + re_login → Authenticating
  ExpiredSession + timeout → Unauthenticated (clear all tokens)

  Authenticated + logout → Unauthenticated (revoke tokens, clear storage)

  Unauthenticated + biometric_unlock → (if refresh token valid) → Authenticated
  Unauthenticated + biometric_unlock → (if refresh token expired) → Unauthenticated (show login)
```

## Deployment View

### Single Application Deployment

- **Environment**: Mobile client (iOS/Android)
- **Configuration**:
  - API base URL via `ApiConstants` (dev vs prod)
  - Feature flags via backend response (scheduling access)
- **Dependencies**:
  - Mobile Scheduling API (CRITICAL)
  - FCM for push notifications (MEDIUM)
  - Location Services (OPTIONAL)
  - Device Biometrics (OPTIONAL)
- **Performance**:
  - Dashboard load: <1s target (API SLA <500ms)
  - Request list: Paginate at 20 items
  - Token refresh: Automatic 5 min before expiry

### Migration Deployment

**Phase 1: Soft Launch**
- Feature flag controls access (pilot stores only)
- Both API key and JWT auth supported
- Old app version continues working

**Phase 2: Gradual Rollout**
- Expand feature flag to more stores
- Monitor JWT adoption rate
- Support mixed auth in same app session

**Phase 3: Full Migration**
- Remove API key auth option from UI
- Backend continues supporting API key for legacy
- Track migration completion metric

## Cross-Cutting Concepts

### Feature Flag Enforcement

**How feature flags work:**

1. **Source**: Feature flag returned in login response per store (`hasSchedulingAccess` in `StoreAccessModel`)
2. **Storage**: Cached in memory with auth state, refreshed on token refresh
3. **Enforcement Points**:
   - Store selector: Only show stores with `hasSchedulingAccess: true`
   - Route guard: Redirect to "Feature not available" screen if flag is false
   - Dashboard entry: Hide scheduling entry point if no stores have access

```pseudocode
PROVIDER: SchedulingFeatureFlagProvider
  BUILD:
    authState = ref.watch(schedulingAuthProvider)
    if (!authState.isAuthenticated) return FeatureFlagState.unknown

    storesWithAccess = authState.stores.where(s => s.hasSchedulingAccess)
    return FeatureFlagState(
      hasAnyAccess: storesWithAccess.isNotEmpty,
      accessibleStores: storesWithAccess.toList(),
    )

ROUTER_GUARD: schedulingGuard
  IF (!featureFlagState.hasAnyAccess):
    REDIRECT to /scheduling/not-available
  IF (selectedStore != null && !selectedStore.hasSchedulingAccess):
    REDIRECT to /scheduling/store-selector
```

### Analytics & Event Tracking

**Instrumentation Points (per PRD analytics requirements):**

```yaml
Events:
  # Auth Events
  - event: scheduling_login_attempt
    properties: [method: email|biometric, success: bool, error_code: string?]

  - event: scheduling_logout
    properties: [reason: user_initiated|session_expired|forced]

  # Feature Usage Events
  - event: scheduling_screen_view
    properties: [screen_name: string, typeNum: string]

  - event: scheduling_request_action
    properties: [action: approve|deny, request_type: time_off|swap|override, batch: bool, count: int]

  - event: scheduling_shift_action
    properties: [action: create|update|delete, typeNum: string]

  - event: scheduling_conflict_resolved
    properties: [conflict_type: string, resolution_type: string]

  # Performance Events
  - event: scheduling_api_latency
    properties: [endpoint: string, latency_ms: int, success: bool]

  # Error Events
  - event: scheduling_error
    properties: [error_code: string, error_type: string, context: string]

Implementation:
  - Use existing analytics infrastructure (if present) or add firebase_analytics
  - Emit events from providers after successful state transitions
  - Include typeNum in all store-scoped events for segmentation
```

### Interface Contracts (Detailed)

**Request List Pagination Contract:**
```yaml
Request:
  POST /{typeNum}/manager/requests
  Body:
    cursor: string?          # Opaque cursor from previous response (preferred over offset)
    limit: int (default: 20, max: 50)
    type: string?            # Filter: time_off | swap | override | all
    sortBy: string           # submitted_at_desc (default) | expires_at_asc

Response:
  requests: List<PendingRequest>
  pagination:
    cursor: string?          # Next page cursor (null if no more)
    hasMore: bool
    totalCount: int          # Total matching requests
```

**Decision Payload Contract:**
```yaml
Request:
  POST /{typeNum}/manager/requests/{id}/decision
  Body:
    approved: bool           # true = approve, false = deny
    note: string?            # Required if denied, optional if approved
    idempotencyKey: string   # UUID generated client-side, prevents double-submit
    audit:
      deviceId: string       # From device_info_plus
      deviceFingerprint: string?  # Optional additional fingerprint
      latitude: double?      # Null if location denied
      longitude: double?
      timestamp: string      # ISO8601

Response (Success):
  requestId: int
  status: approved | denied
  processedAt: string

Response (Error - Already Processed):
  error:
    code: REQUEST_ALREADY_PROCESSED
    message: "Request was already processed by {managerName}"
    processedBy: { managerId, managerName, processedAt }
```

**Batch Approval Contract:**
```yaml
Request:
  POST /{typeNum}/manager/requests/batch
  Body:
    requests: List<{ requestId: int, type: string }>  # Same type required
    approved: bool
    note: string?
    idempotencyKey: string   # Covers entire batch
    audit: { ... }           # Same as single decision

Validation:
  - Max 10 requests per batch (enforced client-side AND server-side)
  - All requests must be same type
  - Returns 400 if validation fails

Response:
  processed: List<{ requestId, status }>
  failed: List<{ requestId, errorCode, errorMessage }>
  summary: { successCount, failureCount }
```

**Notification Quick Action Payload:**
```yaml
# Notification data payload from FCM
data:
  notificationId: string
  type: request_pending
  typeNum: string
  requestId: string
  requestType: time_off | swap | override
  actionable: true

# Quick action flow
1. User taps "Approve" action on notification
2. App launches with deep link: /scheduling/action?notificationId=X&action=approve
3. If session expired: Show login → after success, execute action
4. If session valid: Execute action immediately
5. Show result toast (success or error)
```

### Error Model & Idempotency

**Unified Error Response:**
```yaml
ErrorResponse:
  error:
    code: string           # Machine-readable code (e.g., REQUEST_ALREADY_PROCESSED)
    message: string        # Human-readable message
    details: object?       # Additional context (e.g., { processedBy: ... })
    retryable: bool        # Whether client should retry
    supportCode: string?   # Code for support tickets

Error Codes:
  # Auth Errors (1xxx)
  - AUTH_INVALID_CREDENTIALS: "Invalid email or password"
  - AUTH_ACCOUNT_LOCKED: "Account locked. Contact support."
  - AUTH_TOKEN_EXPIRED: "Session expired. Please log in again."
  - AUTH_REFRESH_FAILED: "Could not refresh session."

  # Request Errors (2xxx)
  - REQUEST_NOT_FOUND: "Request not found or already processed"
  - REQUEST_ALREADY_PROCESSED: "Already processed by another manager"
  - REQUEST_EXPIRED: "Request has expired and can no longer be processed"

  # Validation Errors (3xxx)
  - VALIDATION_BATCH_LIMIT: "Maximum 10 requests per batch"
  - VALIDATION_BATCH_MIXED_TYPES: "All batch requests must be same type"
  - VALIDATION_DENIAL_NOTE_REQUIRED: "Denial reason is required"

  # Conflict Errors (4xxx)
  - CONFLICT_ALREADY_RESOLVED: "Conflict has already been resolved"
  - CONFLICT_RESOLUTION_FAILED: "Could not apply resolution"

  # Server Errors (5xxx)
  - SERVER_ERROR: "An unexpected error occurred. Try again."
  - SERVER_MAINTENANCE: "Server is under maintenance. Try again later."
```

**Idempotency Strategy:**
```pseudocode
CLIENT:
  Generate UUID for each mutation (approval, batch, shift CRUD)
  Store in pending_mutations map: { idempotencyKey -> requestData }
  On network error/timeout: Retry with SAME idempotencyKey
  On success: Remove from pending_mutations
  On app restart: Check pending_mutations, offer to retry

SERVER:
  Store idempotencyKey -> result for 24 hours
  If same key received: Return cached result (no re-execution)
  Prevents double-approvals from retries or duplicate taps
```

**Offline Behavior (per PRD: no offline support):**
```pseudocode
PROVIDER: ConnectivityProvider
  Monitor network state via connectivity_plus

SCREENS:
  IF (!isConnected):
    Show "No connection" banner at top
    Disable ALL mutation buttons (approve, deny, create shift, etc.)
    Show cached dashboard data with "Last updated X ago" timestamp
    Pull-to-refresh shows "Connect to refresh" message

  # NO local queue for mutations - block until online
```

### Pattern Documentation

```yaml
# Existing patterns used
- pattern: Freezed Model Pattern
  relevance: CRITICAL
  why: "All scheduling data models follow this pattern"

- pattern: Equatable Entity Pattern
  relevance: CRITICAL
  why: "All scheduling domain entities follow this pattern"

- pattern: Extension Mapper Pattern
  relevance: HIGH
  why: "Model ↔ Entity conversion"

- pattern: AsyncNotifier Provider Pattern
  relevance: CRITICAL
  why: "All scheduling providers follow this pattern"

- pattern: ConsumerStatefulWidget Screen Pattern
  relevance: HIGH
  why: "All scheduling screens follow this pattern"

# New patterns introduced
- pattern: JWT Auth State Machine (NEW)
  relevance: CRITICAL
  why: "Core pattern for scheduling auth management"

- pattern: Dual Auth Interceptor (NEW)
  relevance: HIGH
  why: "Support both API key and JWT during migration"

- pattern: Audit Trail Capture (NEW)
  relevance: MEDIUM
  why: "Location + device capture for approval actions"
```

### System-Wide Patterns

- **Security**: JWT Bearer auth, refresh token rotation, biometric gate
- **Error Handling**: Consistent toast/banner display, retry callbacks
- **Performance**: Pagination for lists, optimistic UI updates where safe
- **Logging**: API request/response logging in debug mode
- **Audit Trail**: Device ID + location + timestamp on all mutations

### Implementation Patterns

#### State Management Pattern

```pseudocode
COMPONENT: SchedulingProvider<T>
  INITIALIZE:
    selectedStore from storage
    initial state = loading

  BUILD:
    if (!hasValidToken) throw NotAuthenticatedException
    fetch data from repository
    return entities

  HANDLE:
    loading → show skeleton/spinner
    error → ErrorDisplay with retry
    data → render content

  METHODS:
    refresh() → invalidate and rebuild
    mutate() → optimistic update, API call, rollback on failure
```

#### Component Structure Pattern

```pseudocode
SCREEN: SchedulingScreen
  INITIALIZE:
    RefreshController
    PostFrameCallback → read provider

  BUILD:
    Scaffold(
      appBar: with refresh + optional actions
      body: provider.when(
        data: content
        loading: LoadingIndicator
        error: ErrorDisplay.fromError
      )
    )
```

## Architecture Decisions

- [x] **ADR-1: Single JWT Auth for All Endpoints (Post-Migration)**
  - Choice: After JWT migration, ALL endpoints (existing mobile.php + new scheduling) accept JWT Bearer auth
  - Rationale: Clean auth model, no dual code paths, aligns with PRD requirement that "all existing features work with JWT auth"
  - Trade-offs: Requires backend update to accept JWT on legacy endpoints
  - Implementation:
    - During migration: Interceptor uses JWT if available, falls back to API key
    - Post-migration: JWT only, API key code path removed
    - Backend dependency: Legacy mobile.php endpoints must accept `Authorization: Bearer <token>`
  - User confirmed: ✅ 2025-12-30 (updated per Codex review)

- [x] **ADR-2: JWT Tokens in Secure Storage**
  - Choice: Store JWT tokens in flutter_secure_storage with dedicated keys
  - Rationale: Same proven approach as existing API key storage, platform keychain security
  - Trade-offs: Need to clear on logout, handle keychain errors
  - User confirmed: ✅ (follows existing pattern)

- [x] **ADR-3: Family Providers for Per-Store Data**
  - Choice: Use AsyncNotifierProvider.family with typeNum parameter for all store-scoped data
  - Rationale: Follows existing pattern, enables caching per store
  - Trade-offs: Provider explosion (one per store), need careful invalidation
  - User confirmed: ✅ 2025-12-30

- [x] **ADR-4: Location as Soft Requirement**
  - Choice: Request location on first approval, proceed without if denied
  - Rationale: PRD specifies "soft prompt, allow without" for UX
  - Trade-offs: Some audit trails will have null location
  - User confirmed: ✅ (per PRD decision)

- [x] **ADR-5: Biometric Gates Refresh Token Only**
  - Choice: Biometric auth unlocks refresh token to get new access token, not individual API calls
  - Rationale: Balance security with UX - one biometric per session, not per action
  - Trade-offs: If refresh token stolen, biometric bypass possible
  - User confirmed: ✅ 2025-12-30

- [x] **ADR-6: Scheduling Routes as Sub-Tree**
  - Choice: Add scheduling routes under `/scheduling/*` path, separate from existing `/store/*`
  - Rationale: Clean separation, scheduling has different auth requirements
  - Trade-offs: Different navigation pattern from store metrics
  - User confirmed: ✅ 2025-12-30

## Quality Requirements

- **Performance**:
  - Dashboard load: <2s on 4G network
  - Request list render: <500ms for 20 items
  - Token refresh: <1s (invisible to user)
  - Approval action: <2s total (including location capture)

- **Usability**:
  - One-tap approval flow (confirmation optional)
  - Batch selection: select/deselect with visual feedback
  - Pull-to-refresh on all list screens
  - Skeleton loaders during initial load

- **Security**:
  - Tokens never logged or exposed in error messages
  - Biometric failure (3x) triggers password requirement
  - Session timeout after 30 days of inactivity

- **Reliability**:
  - Graceful degradation: show cached dashboard if API fails
  - Retry on transient failures (network, timeout)
  - No data loss: pending local changes queued if offline

## Risks and Technical Debt

### Known Technical Issues

- Existing `api_interceptors.dart` assumes form-encoded body for all requests
  - Impact: JWT API uses JSON body
  - Mitigation: New scheduling client bypasses old interceptor

### Technical Debt

- Dual auth systems (API key + JWT) will coexist during migration
  - Debt: Two code paths for authentication
  - Plan: Remove API key path after 90% migration

### Implementation Gotchas

- Store timezone vs device timezone for shift times
  - All times should display in store timezone
  - API returns `storeTimezone` field for conversion

- Batch approval max 10 items
  - UI must enforce limit before API call
  - Show "(X/10 selected)" counter

- Biometric availability varies by device
  - Some devices have fingerprint only, some have face only
  - `local_auth` handles detection, show appropriate icon

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Successful Login and Store Selection**
```gherkin
Given: User has valid credentials
And: User has access to 2 stores
When: User enters email and password
And: User taps Sign In
Then: Store selector screen appears
And: Both stores shown with role badges
When: User selects a store
Then: Manager dashboard loads
And: Biometric prompt appears
```

**Scenario 2: Request Approval with Audit Trail**
```gherkin
Given: Manager is authenticated
And: Manager is on Pending Requests screen
And: Location permission is granted
When: Manager taps a time-off request
And: Manager taps Approve
Then: Approval sent with device ID and location
And: Request removed from list
And: Success toast appears
```

**Scenario 3: Batch Approval with Partial Failure**
```gherkin
Given: Manager has selected 5 requests
And: 2 requests were already processed by another manager
When: Manager taps Batch Approve
Then: 3 requests approved successfully
And: Summary shows "3 approved, 2 failed"
And: Failed requests remain in list with error reason
```

**Scenario 4: Token Expiry During Action**
```gherkin
Given: Access token expires during API call
When: Approval request returns 401
Then: Token refresh attempted automatically
And: If refresh succeeds, approval retried
And: User sees no interruption
```

### Test Coverage Requirements

- **Business Logic**: Auth state transitions, batch limit validation, expiry calculations
- **User Interface**: Login form validation, batch selection, filter states
- **Integration Points**: API client JWT injection, token refresh, location capture
- **Edge Cases**: Offline mode, expired refresh token, biometric failure
- **Performance**: Dashboard render time, list scrolling performance

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| Time-Off Request | Employee request for scheduled absence (vacation, sick, personal) | Approval workflow |
| Shift Swap | Exchange of shifts between two employees | Requires both employee and manager approval |
| Clock Override | Manager adjustment of employee punch time | For missed punches or corrections |
| Open Shift | Unassigned shift available for employees to claim | Shift management |
| Late | Clocked in >5 minutes after scheduled start | Who's Working status |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| JWT | JSON Web Token - access credential | Authentication |
| Access Token | Short-lived (15 min) API credential | Per-request auth |
| Refresh Token | Long-lived (30 day) credential to get new access tokens | Session persistence |
| typeNum | Store identifier pattern `[a-z][a-z]\d+` (e.g., `ou00`) | Multi-store context |
| Audit Trail | Record of who/when/where for approval actions | Compliance tracking |
| Family Provider | Riverpod provider parameterized by value | Per-store data caching |

### API Terms

| Term | Definition | Context |
|------|------------|---------|
| Bearer Auth | `Authorization: Bearer <token>` header | JWT API authentication |
| POST /{typeNum}/... | Store-scoped endpoint pattern | All scheduling endpoints |
| Batch Request | Multiple items processed in single API call | Bulk approvals |
