# 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 Technical Platform**
- Flutter 3.38.4 / Dart 3.10.3
- iOS 12.0+ (Firebase minimum), Android 21+ (Lollipop)
- Must use official `firebase_messaging` package
- FCM payload limited to 4KB

**CON-2 Coding Standards**
- Riverpod 3.x Notifier pattern (not StateNotifier)
- Freezed 3.x with `abstract class` for models
- Equatable for domain entities
- Clean Architecture: core → data → domain → presentation

**CON-3 Security & Platform Requirements**
- iOS: Requires APNs certificate/key in Firebase Console
- Android 13+: Runtime notification permission required
- FCM token must be sent to backend over authenticated connection
- Token must be cleared on logout

**CON-4 Integration Constraints**
- Ably remains primary for in-app real-time (do not replace)
- Backend handles all notification triggering logic
- Mobile app is notification receiver only

---

## Implementation Context

### Required Context Sources

```yaml
# Internal documentation
- doc: docs/specs/005-firebase-push-notifications/product-requirements.md
  relevance: CRITICAL
  why: "Defines notification types, user journeys, and acceptance criteria"

- doc: CLAUDE.md
  relevance: HIGH
  why: "Project patterns, architecture, and coding standards"

# Source code files
- file: lib/core/services/storage_service.dart
  relevance: HIGH
  why: "Existing pattern for service providers and storage"

- file: lib/presentation/providers/auth_provider.dart
  relevance: HIGH
  why: "Auth flow where FCM token registration integrates"

- file: lib/core/constants/app_constants.dart
  relevance: MEDIUM
  why: "Storage key patterns and app constants"

- file: pubspec.yaml
  relevance: HIGH
  why: "Current dependencies, will add firebase packages"

# External documentation
- url: https://firebase.flutter.dev/docs/messaging/overview
  relevance: CRITICAL
  why: "Official Flutter Firebase Messaging documentation"

- url: https://firebase.flutter.dev/docs/overview
  relevance: HIGH
  why: "FlutterFire setup and initialization"

- url: https://developer.apple.com/documentation/usernotifications
  relevance: MEDIUM
  why: "iOS notification handling and rich notifications"
```

### Implementation Boundaries

- **Must Preserve**:
  - Existing auth flow (tokens, biometric, session restore)
  - Ably integration for in-app real-time
  - Clean Architecture layers
  - Existing router structure

- **Can Modify**:
  - `auth_provider.dart` - add FCM token registration on login/logout
  - `storage_service.dart` - add notification preference storage
  - `app_constants.dart` - add notification storage keys
  - `main.dart` - add Firebase initialization

- **Must Not Touch**:
  - Live Activity widget code (separate feature)
  - Existing Ably configuration
  - Backend implementation (separate team)

### External Interfaces

#### System Context Diagram

```mermaid
graph TB
    subgraph Mobile["BuyerKiosk Team App"]
        FCMService[FCM Service]
        NotifProvider[Notification Provider]
        NotifPrefs[Notification Preferences]
    end

    User[Team Member] --> Mobile

    Mobile -->|Register Token| Backend[BuyerKiosk Backend]
    Backend -->|Send Notification| FCM[Firebase Cloud Messaging]
    FCM -->|Deliver Push| Mobile

    Mobile -->|APNs Setup| APNS[Apple Push Service]
    APNS -->|iOS Delivery| Mobile
```

#### Interface Specifications

```yaml
# Inbound Interfaces
inbound:
  - name: "FCM Push Notifications"
    type: Firebase Cloud Messaging
    format: JSON payload
    authentication: Firebase project credentials
    data_flow: "Receive schedule, clock, shift, chat notifications"

# Outbound Interfaces
outbound:
  - name: "FCM Token Registration"
    type: HTTPS
    format: REST JSON
    authentication: JWT Bearer token
    endpoint: POST /api/mobile/me/devices
    data_flow: "Register FCM token with backend"
    criticality: HIGH

  - name: "FCM Token Removal"
    type: HTTPS
    format: REST JSON
    authentication: JWT Bearer token
    endpoint: DELETE /api/mobile/me/devices/{token}
    data_flow: "Remove FCM token on logout"
    criticality: MEDIUM

  - name: "Notification Preferences Sync"
    type: HTTPS
    format: REST JSON
    authentication: JWT Bearer token
    endpoint: PUT /api/mobile/me/notification-preferences
    data_flow: "Sync user notification preferences"
    criticality: MEDIUM
```

### Project Commands

```bash
# Environment Setup
Install Dependencies: flutter pub get
Firebase CLI Setup: dart pub global activate flutterfire_cli
Firebase Configure: flutterfire configure --project=<your-firebase-project-id>

# Code Generation (after adding models)
Generate Code: dart run build_runner build --delete-conflicting-outputs

# Testing Commands
Unit Tests: flutter test
Integration Tests: flutter test integration_test/
Test Coverage: flutter test --coverage

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

# Build Commands
iOS Debug: flutter build ios --debug --no-codesign
Android Debug: flutter build apk --debug
Run on Device: flutter run

# iOS Specific (Xcode)
Pod Install: cd ios && pod install
```

---

## Solution Strategy

**Architecture Pattern**: Service-Provider Pattern within Clean Architecture

The solution adds a `PushNotificationService` (core/services) that handles Firebase initialization, permission requests, and token management. A `NotificationProvider` (presentation/providers) exposes notification state and preferences to the UI using Riverpod's Notifier pattern.

**Integration Approach**:
1. Firebase initializes in `main.dart` before `runApp()`
2. `PushNotificationService` registered as Riverpod provider
3. Auth flow modified to register/deregister FCM token on login/logout
4. Notification preferences stored locally and synced to backend
5. Notification tap handling routed through GoRouter

**Justification**:
- Follows existing codebase patterns (services in core, providers in presentation)
- Keeps FCM concerns separate from business logic
- Enables easy testing with mocked services
- Maintains Clean Architecture boundaries

**Key Decisions**:
- Use `firebase_messaging` + `firebase_core` packages
- Store FCM token in SharedPreferences (not secure storage - not sensitive)
- Handle foreground notifications via in-app banner (not system notification)
- Background/terminated notifications handled natively by FCM

---

## Building Block View

### Components

```mermaid
graph TB
    subgraph Presentation["Presentation Layer"]
        NotifProvider[NotificationProvider]
        NotifSettingsScreen[NotificationSettingsScreen]
        AuthProvider[AuthProvider]
    end

    subgraph Core["Core Layer"]
        PushService[PushNotificationService]
        StorageService[StorageService]
        RouterNav[AppRouter]
    end

    subgraph Data["Data Layer"]
        NotifPrefsModel[NotificationPreferencesModel]
        NotifPayloadModel[NotificationPayloadModel]
    end

    subgraph External["External"]
        FirebaseMessaging[firebase_messaging]
        Backend[Backend API]
    end

    NotifProvider --> PushService
    NotifProvider --> StorageService
    AuthProvider --> PushService
    NotifSettingsScreen --> NotifProvider

    PushService --> FirebaseMessaging
    PushService --> Backend
    PushService --> RouterNav

    NotifProvider --> NotifPrefsModel
    PushService --> NotifPayloadModel
```

### Directory Map

```
lib/
├── core/
│   ├── constants/
│   │   └── app_constants.dart           # MODIFY: Add notification storage keys
│   │   └── notification_constants.dart  # NEW: Notification types and actions
│   └── services/
│       └── push_notification_service.dart  # NEW: FCM integration
│       └── storage_service.dart            # MODIFY: Add notification prefs storage
│
├── data/
│   └── models/
│       └── notification_payload_model.dart     # NEW: FCM payload parsing
│       └── notification_payload_model.freezed.dart
│       └── notification_payload_model.g.dart
│       └── notification_preferences_model.dart # NEW: User preferences
│       └── notification_preferences_model.freezed.dart
│       └── notification_preferences_model.g.dart
│
├── domain/
│   └── entities/
│       └── notification_preferences.dart  # NEW: Preferences entity
│
├── presentation/
│   ├── providers/
│   │   └── auth_provider.dart             # MODIFY: FCM token on login/logout
│   │   └── notification_provider.dart     # NEW: Notification state
│   ├── screens/
│   │   └── settings/
│   │       └── notification_settings_screen.dart  # NEW: Preferences UI
│   └── widgets/
│       └── notifications/
│           └── in_app_notification_banner.dart    # NEW: Foreground banner
│
├── router/
│   └── app_router.dart                    # MODIFY: Deep link handling
│
└── main.dart                              # MODIFY: Firebase init

# Platform Files
ios/
├── Runner/
│   └── AppDelegate.swift                  # MODIFY: APNs setup (if needed)
└── Runner.xcworkspace/
    └── Podfile                            # MODIFY: Firebase pods added automatically

android/
├── app/
│   └── src/main/
│       └── AndroidManifest.xml            # Already has POST_NOTIFICATIONS
│   └── build.gradle                       # MODIFY: Add Firebase dependencies
└── build.gradle                           # MODIFY: Add Google services plugin

# Firebase Config Files (auto-generated)
ios/Runner/GoogleService-Info.plist       # NEW: Firebase iOS config
android/app/google-services.json          # NEW: Firebase Android config
lib/firebase_options.dart                 # NEW: Generated by flutterfire configure
```

### Interface Specifications

#### Data Models

```dart
// lib/data/models/notification_payload_model.dart
@freezed
abstract class NotificationPayloadModel with _$NotificationPayloadModel {
  const factory NotificationPayloadModel({
    required String type,           // schedule, clock, open_shift, chat
    required String action,         // view_schedule, clock_in, clock_out, claim_shift, view_details, reply, mark_read
    String? title,
    String? body,
    String? targetRoute,            // Deep link route
    Map<String, dynamic>? data,     // Type-specific payload
  }) = _NotificationPayloadModel;

  factory NotificationPayloadModel.fromJson(Map<String, dynamic> json) =>
      _$NotificationPayloadModelFromJson(json);
}

// lib/data/models/notification_preferences_model.dart
@freezed
abstract class NotificationPreferencesModel with _$NotificationPreferencesModel {
  const factory NotificationPreferencesModel({
    @Default(true) bool scheduleEnabled,
    @Default(true) bool clockRemindersEnabled,
    @Default(true) bool openShiftsEnabled,
    @Default(true) bool chatEnabled,
    @Default(15) int shiftReminderMinutes,  // 15, 30, 60
    @Default(false) bool doNotDisturb,
    String? quietHoursStart,                 // "22:00"
    String? quietHoursEnd,                   // "07:00"
  }) = _NotificationPreferencesModel;

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

#### Notification Payload Types (from Backend)

```yaml
# Schedule Notification
type: schedule
actions: [view_schedule]
data:
  shift_id: string
  date: ISO8601
  change_type: added | modified | removed
  message: string

# Clock Reminder Notification
type: clock
actions: [clock_in, clock_out, view_schedule]
data:
  shift_id: string
  store_type_num: string
  action_type: clock_in_reminder | clock_out_reminder | overtime_alert | missed_punch
  geofence_required: boolean

# Open Shift Notification
type: open_shift
actions: [claim_shift, view_details]
data:
  shift_id: string
  store_type_num: string
  date: ISO8601
  start_time: string
  end_time: string

# Chat Notification
type: chat
actions: [reply, mark_read, view]
data:
  conversation_id: string
  sender_name: string
  message_preview: string
  is_direct_message: boolean
```

#### API Endpoints (Mobile → Backend)

```yaml
# Register FCM Token
Endpoint: Register Device Token
  Method: POST
  Path: /api/mobile/me/devices
  Request:
    fcm_token: string (required)
    device_id: string (required)
    platform: ios | android (required)
    app_version: string
  Response:
    success:
      registered: true
      device_id: string
    error:
      error_code: string
      message: string

# Remove FCM Token
Endpoint: Remove Device Token
  Method: DELETE
  Path: /api/mobile/me/devices/{device_id}
  Request: (none, device_id in path)
  Response:
    success:
      removed: true
    error:
      error_code: string
      message: string

# Update Notification Preferences
Endpoint: Update Preferences
  Method: PUT
  Path: /api/mobile/me/notification-preferences
  Request:
    schedule_enabled: boolean
    clock_reminders_enabled: boolean
    open_shifts_enabled: boolean
    chat_enabled: boolean
    shift_reminder_minutes: integer (15|30|60)
    do_not_disturb: boolean
    quiet_hours_start: string (HH:mm)
    quiet_hours_end: string (HH:mm)
  Response:
    success:
      preferences: NotificationPreferences
    error:
      error_code: string
      message: string

# Get Notification Preferences
Endpoint: Get Preferences
  Method: GET
  Path: /api/mobile/me/notification-preferences
  Response:
    success:
      preferences: NotificationPreferences
    error:
      error_code: string
      message: string
```

### Implementation Examples

#### Example: Push Notification Service Structure

**Why this example**: Shows the core service pattern following existing codebase conventions.

```dart
// lib/core/services/push_notification_service.dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

/// Provider for push notification service
final pushNotificationServiceProvider = Provider<PushNotificationService>((ref) {
  return PushNotificationService(ref);
});

/// Service for handling Firebase Cloud Messaging
class PushNotificationService {
  final Ref _ref;
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  PushNotificationService(this._ref);

  /// Initialize FCM and request permissions
  Future<void> initialize() async {
    // Request permission (iOS will show prompt, Android 13+ will show prompt)
    final settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
      provisional: false,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      await _setupMessageHandlers();
    }
  }

  /// Get current FCM token
  Future<String?> getToken() async {
    return await _messaging.getToken();
  }

  /// Listen for token refresh
  void onTokenRefresh(void Function(String) callback) {
    _messaging.onTokenRefresh.listen(callback);
  }

  /// Setup message handlers
  Future<void> _setupMessageHandlers() async {
    // Foreground messages
    FirebaseMessaging.onMessage.listen(_handleForegroundMessage);

    // Background/terminated tap
    FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationTap);

    // Check if app opened from terminated state via notification
    final initialMessage = await _messaging.getInitialMessage();
    if (initialMessage != null) {
      _handleNotificationTap(initialMessage);
    }
  }

  void _handleForegroundMessage(RemoteMessage message) {
    // Show in-app banner instead of system notification
    // Delegate to NotificationProvider
  }

  void _handleNotificationTap(RemoteMessage message) {
    // Parse payload and navigate via GoRouter
  }
}
```

#### Example: Auth Provider Integration

**Why this example**: Shows how FCM token registration integrates with existing auth flow.

```dart
// In auth_provider.dart - additions to login() method
Future<void> login({required String email, required String password}) async {
  // ... existing login logic ...

  // After successful login, register FCM token
  final pushService = ref.read(pushNotificationServiceProvider);
  final fcmToken = await pushService.getToken();

  if (fcmToken != null) {
    final deviceId = await _storage.getDeviceId();
    await _authRepo.registerDevice(
      fcmToken: fcmToken,
      deviceId: deviceId,
      platform: Platform.isIOS ? 'ios' : 'android',
    );
  }

  // Listen for token refresh
  pushService.onTokenRefresh((newToken) async {
    final deviceId = await _storage.getDeviceId();
    await _authRepo.registerDevice(
      fcmToken: newToken,
      deviceId: deviceId,
      platform: Platform.isIOS ? 'ios' : 'android',
    );
  });
}

// In logout() method
Future<void> logout() async {
  // ... existing logout prep ...

  // Remove FCM token from backend before clearing local data
  final deviceId = await _storage.getDeviceId();
  try {
    await _authRepo.removeDevice(deviceId);
  } catch (_) {
    // Ignore errors - proceed with logout
  }

  // ... rest of existing logout logic ...
}
```

#### Example: Notification Action Handling

**Why this example**: Shows deep linking from notification tap.

```dart
// In push_notification_service.dart
void _handleNotificationTap(RemoteMessage message) {
  final data = message.data;
  final type = data['type'] as String?;
  final action = data['action'] as String?;

  // Get router from provider
  final router = _ref.read(routerProvider);

  switch (type) {
    case 'schedule':
      final date = data['date'] as String?;
      router.go('/schedule${date != null ? '?date=$date' : ''}');
      break;

    case 'clock':
      // Navigate to home (clock widget is there)
      router.go('/home');
      break;

    case 'open_shift':
      final shiftId = data['shift_id'] as String?;
      router.go('/open-shifts${shiftId != null ? '/$shiftId' : ''}');
      break;

    case 'chat':
      final conversationId = data['conversation_id'] as String?;
      router.go('/chat${conversationId != null ? '/$conversationId' : ''}');
      break;

    default:
      router.go('/home');
  }
}
```

---

## Runtime View

### Primary Flow: Notification Delivery & Handling

1. Backend triggers notification via FCM
2. FCM delivers to device
3. If app in foreground: Show in-app banner
4. If app in background/terminated: Show system notification
5. User taps notification
6. App opens to relevant screen via deep link

```mermaid
sequenceDiagram
    participant Backend
    participant FCM
    participant Device
    participant App
    participant User

    Backend->>FCM: Send notification (with token)
    FCM->>Device: Deliver push

    alt App in Foreground
        Device->>App: onMessage callback
        App->>App: Show in-app banner
        User->>App: Tap banner
        App->>App: Navigate to relevant screen
    else App in Background/Terminated
        Device->>User: Show system notification
        User->>Device: Tap notification
        Device->>App: Launch app
        App->>App: onMessageOpenedApp / getInitialMessage
        App->>App: Navigate to relevant screen
    end
```

### Secondary Flow: FCM Token Registration

```mermaid
sequenceDiagram
    participant User
    participant App
    participant FCM
    participant Backend

    User->>App: Login with credentials
    App->>Backend: POST /auth/login
    Backend-->>App: Auth tokens + user data
    App->>FCM: getToken()
    FCM-->>App: FCM token
    App->>Backend: POST /me/devices (FCM token)
    Backend-->>App: Registration confirmed
    App->>App: Store device registration
```

### Error Handling

| Error Type | Handling |
|------------|----------|
| Permission Denied | Store preference, don't prompt again for 7 days, show in-app indicator |
| Token Retrieval Failed | Retry 3x with exponential backoff, log error, continue without push |
| Token Registration Failed | Queue for retry, retry on next app launch |
| Invalid Payload | Log error, show generic "New notification" message |
| Deep Link Route Not Found | Fall back to home screen |
| Network Unavailable (for token reg) | Queue locally, register on next network availability |

---

## Deployment View

### Single Application Deployment

- **Environment**: Mobile app (iOS App Store, Google Play)
- **Configuration**:
  - Firebase project ID via `firebase_options.dart` (generated)
  - `GoogleService-Info.plist` (iOS)
  - `google-services.json` (Android)
- **Dependencies**:
  - Firebase project with Cloud Messaging enabled
  - APNs key/certificate uploaded to Firebase Console (iOS)
- **Performance**:
  - FCM initialization adds ~200ms to app startup
  - Token registration is async, non-blocking

### Firebase Console Setup (One-time)

1. Create Firebase project (or use existing)
2. Add iOS app: Enter bundle ID `com.buyerkiosk.team` (or actual ID)
3. Download `GoogleService-Info.plist` → place in `ios/Runner/`
4. Add Android app: Enter package name `com.buyerkiosk.team`
5. Download `google-services.json` → place in `android/app/`
6. For iOS: Upload APNs authentication key (from Apple Developer Portal)
7. Enable Cloud Messaging API in Firebase Console

---

## Cross-Cutting Concepts

### Pattern Documentation

```yaml
# Existing patterns used
- pattern: Riverpod Notifier pattern
  relevance: CRITICAL
  why: "All providers use Notifier, not StateNotifier"

- pattern: Service Provider pattern
  relevance: HIGH
  why: "Services exposed via Provider, injected via ref"

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

# New patterns introduced
- pattern: Firebase initialization in main.dart
  relevance: CRITICAL
  why: "Firebase must init before runApp()"
```

### System-Wide Patterns

- **Security**: FCM tokens are not sensitive (public key encryption), stored in SharedPreferences
- **Error Handling**: All notification errors are non-fatal; app continues without push
- **Logging**: Log permission status, token registration success/failure, notification receive events
- **Performance**: FCM init is async but awaited in main(); message handlers are lightweight

### State Management Pattern

```dart
// lib/presentation/providers/notification_provider.dart
class NotificationNotifier extends Notifier<NotificationState> {
  @override
  NotificationState build() {
    return const NotificationState.initial();
  }

  PushNotificationService get _pushService => ref.read(pushNotificationServiceProvider);
  StorageService get _storage => ref.read(storageServiceProvider);

  Future<void> initialize() async {
    state = const NotificationState.loading();

    try {
      await _pushService.initialize();
      final preferences = await _storage.getNotificationPreferences();
      final permissionGranted = await _pushService.isPermissionGranted();

      state = NotificationState.ready(
        preferences: preferences,
        permissionGranted: permissionGranted,
      );
    } catch (e) {
      state = NotificationState.error(message: e.toString());
    }
  }

  Future<void> updatePreferences(NotificationPreferences prefs) async {
    await _storage.setNotificationPreferences(prefs);
    // Sync to backend
    // Update state
  }
}
```

---

## Architecture Decisions

- [x] **ADR-1 Official firebase_messaging package**: Use official FlutterFire package
  - Rationale: Best documentation, official support, stable API
  - Trade-offs: Requires FlutterFire CLI setup, Firebase Console config
  - User confirmed: ✅ Yes

- [x] **ADR-2 FCM for push only, keep Ably for real-time**: Hybrid approach
  - Rationale: FCM excellent for background delivery, Ably better for in-app real-time
  - Trade-offs: Two systems to maintain, but clear separation of concerns
  - User confirmed: ✅ Yes (from PRD)

- [x] **ADR-3 Foreground notifications as in-app banner**: Don't show system notification when app is open
  - Rationale: Better UX, user is already engaged, avoid notification spam
  - Trade-offs: Must build custom banner component
  - User confirmed: ✅ Implicit (standard practice)

- [x] **ADR-4 Store FCM token in SharedPreferences**: Not secure storage
  - Rationale: FCM tokens are not sensitive (designed for network transit)
  - Trade-offs: None significant
  - User confirmed: ✅ Implicit (standard practice)

---

## Quality Requirements

| Requirement | Target | Measurement |
|-------------|--------|-------------|
| Token registration success | >99% | Backend logs |
| Notification delivery (FCM side) | >95% | Firebase Console analytics |
| App crash rate (push-related) | <0.1% | Firebase Crashlytics |
| Permission prompt acceptance | >70% | Analytics event tracking |
| Deep link navigation success | 100% | Test coverage |
| Cold start time impact | <300ms | Performance profiling |

---

## Risks and Technical Debt

### Known Technical Issues
- iOS simulator cannot receive push notifications (device required for testing)
- FCM tokens expire and refresh unpredictably; must handle token refresh events

### Technical Debt
- None introduced; following existing patterns

### Implementation Gotchas
- `Firebase.initializeApp()` MUST complete before `runApp()`
- iOS requires APNs setup in Apple Developer Portal AND Firebase Console
- Android 13+ requires runtime notification permission (already have `POST_NOTIFICATIONS` in manifest)
- Background message handler must be top-level function (not class method)

---

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Permission Grant and Token Registration**
```gherkin
Given: User has installed app and logged in
And: Notification permission not yet requested
When: App requests notification permission
And: User grants permission
Then: FCM token is retrieved
And: Token is registered with backend
And: NotificationProvider state shows permissionGranted: true
```

**Scenario 2: Notification Tap Deep Link**
```gherkin
Given: App is in background or terminated
And: User receives schedule notification with date=2025-01-15
When: User taps the notification
Then: App launches (if terminated)
And: App navigates to /schedule?date=2025-01-15
And: Schedule screen shows January 15, 2025
```

**Scenario 3: Token Registration on Login**
```gherkin
Given: User has notification permission granted
And: User is on login screen
When: User logs in successfully
Then: FCM token is retrieved
And: POST /api/mobile/me/devices is called with token
And: Device is registered for push notifications
```

**Scenario 4: Token Removal on Logout**
```gherkin
Given: User is logged in
And: Device is registered for push notifications
When: User logs out
Then: DELETE /api/mobile/me/devices/{device_id} is called
And: Device no longer receives notifications
```

### Test Coverage Requirements

- **Push Service**: Token retrieval, permission handling, message handlers
- **Notification Provider**: State transitions, preference updates
- **Deep Link Routing**: All notification types navigate correctly
- **Auth Integration**: Token registered on login, removed on logout
- **Preferences Screen**: Toggle states, save functionality
- **Error Handling**: Network failures, invalid payloads, permission denied

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| FCM Token | Unique identifier for a device to receive push notifications | Generated by Firebase SDK, sent to backend |
| Push Notification | Message sent to device even when app is closed | Delivered via FCM/APNs |
| Deep Link | URL that opens specific screen in app | Used for notification tap navigation |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| APNs | Apple Push Notification service | Required for iOS push delivery |
| FCM | Firebase Cloud Messaging | Google's push notification service |
| FlutterFire | Official Flutter plugins for Firebase | Package family including firebase_messaging |
| RemoteMessage | FCM message object in Flutter | Contains notification and data payload |

### API/Interface Terms

| Term | Definition | Context |
|------|------------|---------|
| Notification Payload | JSON data sent with push notification | Contains type, action, and type-specific data |
| Notification Preferences | User settings for notification categories | Stored locally and synced to backend |
