# 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 **Flutter/Dart stack**: Flutter 3.41.1, Dart 3.10.1, Riverpod 3.x (AsyncNotifier pattern), GoRouter 17.x, Freezed 3.x (`abstract class`), Equatable for domain entities

CON-2 **Backend is fixed**: All 54+ scheduling API endpoints are production-ready POST-only endpoints. The Flutter app adapts to existing contracts — no backend modifications

CON-3 **Unified JWT auth**: Scheduling uses main app JWT tokens (Spec 003). No separate scheduling login. Access requires `roleId <= 2` (Manager or Owner)

CON-4 **All POST convention**: Even read-like operations (get dashboard, get schedule) use POST method with JSON body per backend convention

CON-5 **Existing codebase is substantial**: 13 providers, 10 models, 10 entities, 12 screens, 10 widgets, API client, and full routing already exist. This is an **enhancement and gap-fill**, not a ground-up build

CON-6 **Store-scoped state**: All scheduling state is scoped per `typeNum` via Riverpod family providers. Store switching resets scheduling context

CON-7 **Manager-only access**: Scheduling tab hidden for `roleId > 2`. Deep links redirect to "not available" screen

## Implementation Context

### Required Context Sources

- ICO-1 General Application Context
 ```yaml
 - doc: docs/specs/008-scheduling-module-rewrite/product-requirements.md
   relevance: HIGH
   why: "16 features across MoSCoW categories — the what and why"

 - doc: docs/api/mobile-scheduling-openapi.yaml
   relevance: CRITICAL
   why: "Canonical API contract for all 54+ scheduling endpoints"

 - doc: docs/specs/003-unified-jwt-auth/implementation-plan.md
   relevance: HIGH
   why: "Auth architecture that scheduling depends on"

 - doc: STYLE_GUIDE.md
   relevance: MEDIUM
   why: "Design tokens, typography, color system"
 ```

- ICO-2 Scheduling Module (existing)
 ```yaml
 - file: lib/presentation/providers/scheduling/
   relevance: CRITICAL
   why: "13 existing providers — core state management already built"

 - file: lib/data/datasources/scheduling/scheduling_remote_datasource.dart
   relevance: HIGH
   why: "572-line datasource with error handling, idempotency keys"

 - file: lib/data/repositories/scheduling_repository_impl.dart
   relevance: HIGH
   why: "349-line repository with model → entity mapping"

 - file: lib/domain/repositories/scheduling_repository.dart
   relevance: HIGH
   why: "Repository interface — defines all available operations"

 - file: lib/core/network/scheduling/scheduling_api_client.dart
   relevance: HIGH
   why: "339-line dedicated Dio client with UnifiedAuthInterceptor"

 - file: lib/presentation/screens/scheduling/
   relevance: HIGH
   why: "12 existing screens — dashboard, requests, schedule, labor, etc."

 - file: lib/presentation/widgets/scheduling/
   relevance: HIGH
   why: "10 existing widgets — cards, filters, batch bar, pickers"
 ```

- ICO-3 App-wide Patterns
 ```yaml
 - file: lib/presentation/providers/backstock_provider.dart
   relevance: MEDIUM
   why: "Reference pattern for Notifier state classes with copyWith"

 - file: lib/data/models/mappers/backstock_mapper.dart
   relevance: MEDIUM
   why: "Reference pattern for model → entity mapping"

 - file: lib/core/network/api_interceptors.dart
   relevance: MEDIUM
   why: "UnifiedAuthInterceptor — JWT injection, proactive refresh, 401 handling"

 - file: lib/router/app_router.dart
   relevance: MEDIUM
   why: "Routing structure with scheduling sub-tree at /scheduling/*"
 ```

### Implementation Boundaries

- **Must Preserve**: Unified auth integration (`SchedulingContextProvider`), existing provider state interfaces consumed by screens, API client configuration, GoRouter route tree structure
- **Can Modify**: Screen UI implementations, widget styling, provider internal logic, entity/model fields (additive), datasource methods (additive), analytics events
- **Must Not Touch**: `UnifiedAuthInterceptor`, main app auth system, core network layer, non-scheduling providers, Spec 005 navigation system (consume only)

### External Interfaces

#### System Context Diagram

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

    App --> SchedulingAPI[Scheduling Backend API<br/>POST /api/mobile/scheduling/*]
    App --> MainAPI[Main App Backend API<br/>POST /api/mobile/*]
    App --> Ably[Ably Realtime<br/>Push Notifications]
    App --> FCM[Firebase Cloud Messaging]

    SchedulingAPI --> DB[(Scheduling Database)]
    MainAPI --> MainDB[(Main App Database)]

    Ably --> FCM
    FCM --> App

    subgraph "Flutter App"
        Nav[Store Navigation<br/>Spec 005] --> Scheduling[Scheduling Module]
        Auth[Unified Auth<br/>Spec 003] --> Scheduling
        Scheduling --> Screens[12 Screens]
        Scheduling --> Providers[13 Providers]
        Scheduling --> DataLayer[Models + Entities + Mappers]
    end
```

#### Interface Specifications

```yaml
# Inbound Interfaces
inbound:
  - name: "Manager Touch Interactions"
    type: Flutter UI
    format: Widgets + GoRouter navigation
    authentication: JWT (checked at route redirect level)
    data_flow: "User actions → Providers → API calls → State updates → UI re-render"

  - name: "Push Notifications (Scheduling)"
    type: FCM via Ably
    format: JSON payload
    authentication: Device token registration
    data_flow: "Backend → Ably → FCM → App → NotificationHandler → DeepLink → Screen"

# Outbound Interfaces
outbound:
  - name: "Scheduling Backend API"
    type: HTTPS POST
    format: JSON request/response
    authentication: JWT Bearer token (via UnifiedAuthInterceptor)
    doc: docs/api/mobile-scheduling-openapi.yaml
    base_url: "https://try.buyerkiosk.com/api/mobile/scheduling"
    data_flow: "All scheduling operations — dashboard, requests, shifts, schedule, labor"
    criticality: CRITICAL

  - name: "Analytics Pipeline"
    type: In-app event logging
    format: Custom events via SchedulingAnalyticsService
    authentication: N/A (local)
    data_flow: "Screen views, user actions, error events → analytics buffer"
    criticality: MEDIUM

# Data Interfaces
data:
  - name: "Secure Storage"
    type: flutter_secure_storage
    connection: SecureStorageDatasource
    data_flow: "JWT tokens, user info, store preferences"

  - name: "In-Memory State"
    type: Riverpod providers
    connection: ProviderScope
    data_flow: "All scheduling state — dashboard, requests, shifts, schedule, labor"
```

### Project Commands

```bash
# Environment Setup
flutter pub get

# Code Generation (Freezed + JSON serialization)
dart run build_runner build --delete-conflicting-outputs

# Run App
flutter run
flutter run -d iphone

# Analysis
flutter analyze

# Testing
flutter test
flutter test test/presentation/providers/scheduling/
flutter test test/presentation/screens/scheduling/
flutter test test/presentation/widgets/scheduling/

# Build
flutter build ios --debug --no-codesign
flutter build apk --debug
```

## Solution Strategy

- **Architecture Pattern**: Clean Architecture with Riverpod — identical to all other app modules (backstock, chat, close reports). Presentation → Domain → Data layering with dependency inversion via abstract repository interfaces.

- **Integration Approach**: Enhancement of the existing substantial scheduling codebase. The current module has ~90% of the data/provider/screen structure built. This design fills gaps (notification preferences, schedule publish UX, copy week UX, clock override form, daily schedule view) and hardens existing implementations against PRD acceptance criteria.

- **Justification**: The existing scheduling module follows the exact same architecture as the rest of the app. Rewriting from scratch would discard ~5,000 lines of working, pattern-compliant code. Instead, we validate existing code against the OpenAPI spec, fill feature gaps, and polish UX to match PRD acceptance criteria.

- **Key Decisions**:
  1. **Validate-and-enhance** over rebuild — existing code is architecturally sound
  2. **Add missing features** via new files — notification preferences provider/screen, schedule publishing enhancements, copy week flow, daily schedule view
  3. **Extend existing entities/models** additively — add missing fields for new features without breaking existing code
  4. **Screen-first gap analysis** — compare each PRD feature against existing screen implementation to identify specific UI gaps
  5. **Defer F15 (Team Notifications) and F16 (Open Shift Management)** — these are PRD "Could Have" features. Open shifts are partially supported (shift with `employeeId == null`), but the "list unclaimed open shifts" and "claim notification" flows require employee-facing Team App integration not in scope. Team notifications require backend notification dispatch infrastructure validation. Both are deferred to a follow-up phase

## Building Block View

### Components

```mermaid
graph TB
    subgraph Presentation["Presentation Layer"]
        Screens["12 Screens<br/>(dashboard, requests, schedule, labor, shifts, etc.)"]
        Widgets["10+ Widgets<br/>(cards, filters, batch bar, pickers, etc.)"]
        Providers["13 Providers<br/>(dashboard, requests, shifts, labor, etc.)"]
    end

    subgraph Domain["Domain Layer"]
        Entities["10 Entity Classes<br/>(Equatable)"]
        RepoInterface["SchedulingRepository<br/>(abstract interface)"]
    end

    subgraph Data["Data Layer"]
        RepoImpl["SchedulingRepositoryImpl"]
        Datasource["SchedulingRemoteDatasource"]
        Models["10 Model Classes<br/>(Freezed)"]
        Mappers["Extension Mappers<br/>(model.toEntity())"]
    end

    subgraph Core["Core Layer"]
        ApiClient["SchedulingApiClient<br/>(Dio + UnifiedAuthInterceptor)"]
        Analytics["SchedulingAnalyticsService"]
        ContextBridge["SchedulingContextProvider"]
        Services["LocationService, DeviceInfoService"]
    end

    Screens --> Providers
    Screens --> Widgets
    Providers --> RepoInterface
    Providers --> Analytics
    Providers --> ContextBridge
    RepoInterface --> RepoImpl
    RepoImpl --> Datasource
    RepoImpl --> Mappers
    Datasource --> ApiClient
    Datasource --> Models
    Mappers --> Models
    Mappers --> Entities
    ContextBridge --> Auth["Main App AuthProvider"]
```

### Directory Map

```
lib/
├── core/
│   ├── constants/scheduling/
│   │   ├── scheduling_api_constants.dart          # EXISTING: API base URL, endpoints
│   │   └── scheduling_constants.dart              # EXISTING: Request types, status values
│   ├── network/scheduling/
│   │   ├── scheduling_api_client.dart             # EXISTING: Dedicated Dio client (339 lines)
│   │   └── scheduling_network.dart                # EXISTING: Network barrel export
│   └── services/scheduling/
│       ├── scheduling_services.dart               # EXISTING: Analytics, latency tracking
│       ├── biometric_service.dart                 # EXISTING: (unused post Spec 003)
│       └── notification_handler.dart              # EXISTING: Push notification deep linking
├── data/
│   ├── datasources/scheduling/
│   │   └── scheduling_remote_datasource.dart      # EXISTING: 572-line datasource
│   ├── models/scheduling/
│   │   ├── manager_dashboard_model.dart           # EXISTING
│   │   ├── pending_request_model.dart             # EXISTING
│   │   ├── shift_model.dart                       # EXISTING
│   │   ├── working_employee_model.dart            # EXISTING
│   │   ├── labor_cost_model.dart                  # EXISTING
│   │   ├── employee_schedule_model.dart           # EXISTING
│   │   ├── request_history_model.dart             # EXISTING
│   │   ├── schedule_conflict_model.dart           # EXISTING
│   │   ├── notification_payload_model.dart        # EXISTING
│   │   ├── notification_preferences_model.dart    # NEW: Notification preference models
│   │   └── scheduling_models.dart                 # EXISTING: Barrel export
│   └── repositories/
│       └── scheduling_repository_impl.dart        # EXISTING: 349 lines (MODIFY for new methods)
├── domain/
│   ├── entities/scheduling/
│   │   ├── manager_dashboard.dart                 # EXISTING
│   │   ├── pending_request.dart                   # EXISTING
│   │   ├── shift.dart                             # EXISTING
│   │   ├── working_employee.dart                  # EXISTING
│   │   ├── labor_cost.dart                        # EXISTING
│   │   ├── employee_schedule.dart                 # EXISTING
│   │   ├── request_history.dart                   # EXISTING
│   │   ├── schedule_conflict.dart                 # EXISTING
│   │   ├── notification_payload.dart              # EXISTING
│   │   ├── notification_preferences.dart          # NEW: Notification preference entities
│   │   └── scheduling_entities.dart               # EXISTING: Barrel export (MODIFY)
│   └── repositories/
│       └── scheduling_repository.dart             # EXISTING: Interface (MODIFY for new methods)
├── presentation/
│   ├── providers/scheduling/
│   │   ├── manager_dashboard_provider.dart        # EXISTING
│   │   ├── pending_requests_provider.dart         # EXISTING: 525 lines (comprehensive)
│   │   ├── shift_provider.dart                    # EXISTING
│   │   ├── whos_working_provider.dart             # EXISTING
│   │   ├── labor_cost_provider.dart               # EXISTING
│   │   ├── my_schedule_provider.dart              # EXISTING
│   │   ├── employee_schedule_provider.dart        # EXISTING
│   │   ├── request_history_provider.dart          # EXISTING
│   │   ├── conflicts_provider.dart                # EXISTING
│   │   ├── notification_action_provider.dart      # EXISTING
│   │   ├── notification_preferences_provider.dart # NEW: Notification preferences state
│   │   ├── scheduling_context_provider.dart       # EXISTING: Auth bridge
│   │   ├── scheduling_feature_flag_provider.dart  # EXISTING
│   │   ├── selected_scheduling_store_provider.dart # EXISTING
│   │   ├── scheduling_auth_provider.dart          # EXISTING: Infrastructure (API client, repo)
│   │   └── scheduling_providers.dart              # EXISTING: Barrel export (MODIFY)
│   ├── screens/scheduling/
│   │   ├── dashboard/
│   │   │   └── scheduling_dashboard_screen.dart   # EXISTING: 645 lines (MODIFY for F1 gaps)
│   │   ├── pending_requests/
│   │   │   ├── pending_requests_screen.dart        # EXISTING: 642 lines (complete)
│   │   │   └── request_detail_sheet.dart           # EXISTING
│   │   ├── whos_working/
│   │   │   └── whos_working_screen.dart            # EXISTING (MODIFY for F4 gaps)
│   │   ├── my_schedule/
│   │   │   └── my_schedule_screen.dart             # EXISTING
│   │   ├── labor_cost/
│   │   │   └── labor_cost_screen.dart              # EXISTING (MODIFY for chart/budget F6)
│   │   ├── shifts/
│   │   │   └── shift_form_screen.dart              # EXISTING (MODIFY for F7 acceptance criteria)
│   │   ├── conflicts/
│   │   │   └── conflicts_screen.dart               # EXISTING
│   │   ├── employee_schedule/
│   │   │   └── employee_schedule_screen.dart       # EXISTING
│   │   ├── history/
│   │   │   └── request_history_screen.dart         # EXISTING
│   │   ├── store_selector/
│   │   │   └── scheduling_store_selector_screen.dart # EXISTING
│   │   ├── notifications/
│   │   │   └── notification_preferences_screen.dart # NEW: F14 notification preferences
│   │   ├── schedule/
│   │   │   ├── weekly_schedule_screen.dart          # NEW: F5 dedicated weekly schedule view
│   │   │   └── daily_schedule_screen.dart           # NEW: F12 daily schedule view
│   │   ├── clock_override/
│   │   │   └── clock_override_screen.dart           # NEW: F9 dedicated clock override form
│   │   ├── scheduling_not_available_screen.dart     # EXISTING
│   │   └── scheduling_screens.dart                  # EXISTING: Barrel export (MODIFY)
│   └── widgets/scheduling/
│       ├── dashboard_stat_card.dart                 # EXISTING
│       ├── request_card.dart                        # EXISTING
│       ├── request_filter_chips.dart                # EXISTING
│       ├── batch_action_bar.dart                    # EXISTING
│       ├── employee_picker.dart                     # EXISTING
│       ├── employee_status_tile.dart                # EXISTING
│       ├── shift_card.dart                          # EXISTING
│       ├── conflict_card.dart                       # EXISTING
│       ├── history_request_card.dart                # EXISTING
│       ├── labor_chart.dart                         # NEW: fl_chart weekly labor visualization
│       ├── schedule_day_section.dart                # NEW: Day section for weekly schedule
│       ├── publish_schedule_dialog.dart             # NEW: Publish confirmation dialog
│       ├── copy_week_dialog.dart                    # NEW: Copy week flow dialog
│       ├── notification_toggle_tile.dart            # NEW: Per-category notification toggle
│       └── scheduling_widgets.dart                  # EXISTING: Barrel export (MODIFY)
└── router/
    └── app_router.dart                              # EXISTING: Full scheduling route tree
```

### Interface Specifications

#### Interface Documentation References

```yaml
interfaces:
  - name: "Scheduling Backend API"
    doc: docs/api/mobile-scheduling-openapi.yaml
    relevance: CRITICAL
    sections: [all_endpoints]
    why: "Canonical API contract — every model/entity must match this spec"

  - name: "Unified Auth System"
    doc: docs/specs/003-unified-jwt-auth/implementation-plan.md
    relevance: HIGH
    sections: [auth_flow, token_lifecycle, scheduling_context_bridge]
    why: "Auth architecture that scheduling depends on"

  - name: "Store Navigation System"
    doc: docs/specs/005-store-navigation-redesign/
    relevance: MEDIUM
    sections: [navigation_categories, badge_counts]
    why: "Schedule tab integration in bottom nav"
```

#### Data Storage Changes

No database changes (backend is fixed). Local storage changes:

```yaml
# Secure Storage (flutter_secure_storage)
# No new keys needed — auth tokens are shared via Spec 003

# In-memory state additions:
Provider: notificationPreferencesProvider (NEW)
  State: NotificationPreferencesState
    categories: List<NotificationCategory>
    preferences: Map<String, Map<String, bool>>  # categoryId → { push, sms, email }
    isLoading: bool
    error: String?
```

#### Internal API Changes

No new Flutter API endpoints. All calls go to the existing backend. New repository methods:

```yaml
# New methods on SchedulingRepository (additive)

Method: getNotificationCategories
  Input: String typeNum
  Output: Future<List<NotificationCategory>>
  Backend: POST /{typeNum}/notifications/categories

Method: getNotificationPreferences
  Input: String typeNum
  Output: Future<NotificationPreferences>
  Backend: POST /{typeNum}/notifications/preferences

Method: updateNotificationPreference
  Input: String typeNum, String categoryId, String channel, bool enabled
  Output: Future<void>
  Backend: POST /{typeNum}/notifications/preferences/update

Method: resetNotificationPreferences
  Input: String typeNum
  Output: Future<void>
  Backend: POST /{typeNum}/notifications/preferences/reset

Method: publishSchedule
  Input: String typeNum, DateTime weekStart
  Output: Future<PublishResult> (shiftCount, employeeCount, notifiedCount)
  Backend: POST /{typeNum}/manager/schedule/publish

Method: createClockOverride
  Input: String typeNum, int employeeId, String punchType, DateTime timestamp, String reason
  Output: Future<ClockOverrideResult> (punchId, timestamp)
  Backend: POST /{typeNum}/manager/clock/override

Method: getDailySchedule
  Input: String typeNum, DateTime date
  Output: Future<DailySchedule> (shifts, summary)
  Backend: POST /{typeNum}/schedule/daily

Method: copyWeekSchedule
  Input: String typeNum, DateTime sourceWeekStart, DateTime targetWeekStart
  Output: Future<CopyWeekResult> (shiftsCopied, conflictsSkipped)
  Backend: POST /{typeNum}/manager/schedule/copy-week
  Note: Existing method in SchedulingRepository interface — ensure datasource + repo impl match

Method: updateNotificationPreferencesBatch
  Input: String typeNum, Map<String, Map<String, bool>> preferences
  Output: Future<void>
  Backend: POST /{typeNum}/notifications/preferences/update-batch
  Note: Batch update for toggling multiple categories at once (e.g., "Reset to Defaults" then reapply)
```

#### Application Data Models

```pseudocode
# NEW Models (Freezed)

MODEL: NotificationCategoryModel (NEW)
  FIELDS:
    categoryId: String
    name: String
    description: String
    channels: List<String>  # ["push", "sms", "email"]

MODEL: NotificationPreferencesModel (NEW)
  FIELDS:
    categories: List<NotificationCategoryModel>
    preferences: Map<String, Map<String, bool>>

MODEL: PublishResultModel (NEW)
  FIELDS:
    shiftCount: int
    employeeCount: int
    notifiedCount: int?
    smsCount: int?

MODEL: ClockOverrideResultModel (NEW)
  FIELDS:
    punchId: int
    employeeId: int
    punchType: String
    timestamp: DateTime
    reason: String

MODEL: DailyScheduleModel (NEW)
  FIELDS:
    date: DateTime
    shifts: List<ShiftModel>
    totalShifts: int
    assignedShifts: int
    openShifts: int
    uniqueEmployees: int

# NEW Entities (Equatable)

ENTITY: NotificationCategory (NEW)
  FIELDS: categoryId, name, description, channels

ENTITY: NotificationPreferences (NEW)
  FIELDS: categories, preferences

ENTITY: PublishResult (NEW)
  FIELDS: shiftCount, employeeCount, notifiedCount, smsCount

ENTITY: ClockOverrideResult (NEW)
  FIELDS: punchId, employeeId, punchType, timestamp, reason

ENTITY: DailySchedule (NEW)
  FIELDS: date, shifts, totalShifts, assignedShifts, openShifts, uniqueEmployees

# EXTENDED Entities (add fields)

ENTITY: WeeklySummary (MODIFIED)
  FIELDS:
    + isPublished: bool (NEW)
    + publishedAt: DateTime? (NEW)

ENTITY: Shift (MODIFIED)
  FIELDS:
    + positionColor: String? (NEW) — hex color from backend
    + isOpenShift: bool (computed: employeeId == null)

# Shift Deletion behavior note:
# deleteShift() requires a `reason` parameter (String, required) per PRD F7.
# The shift_form_screen.dart delete confirmation dialog must include a
# TextField for the reason note. This is enforced in the repository interface:
#   Future<void> deleteShift({ required String typeNum, required int shiftId, required String reason })
# The form must validate that reason is non-empty before enabling the delete button.

MODEL: CopyWeekResultModel (NEW)
  FIELDS:
    shiftsCopied: int
    conflictsSkipped: int

ENTITY: CopyWeekResult (NEW)
  FIELDS: shiftsCopied, conflictsSkipped
```

#### Integration Points

```yaml
# Inter-Module Communication (within the Flutter app)

- from: Scheduling Module
  to: Main Auth (Spec 003)
  protocol: Riverpod provider watch
  endpoints: [authProvider, schedulingContextProvider]
  data_flow: "Auth state → scheduling access check → JWT tokens for API calls"

- from: Store Navigation (Spec 005)
  to: Scheduling Module
  protocol: GoRouter navigation + Riverpod provider
  endpoints: [NavigationCategory.schedule, tabBadgeProvider]
  data_flow: "Schedule tab tap → scheduling dashboard; pending count → tab badge"

- from: Push Notification Service
  to: Scheduling Module
  protocol: Deep linking via GoRouter
  endpoints: [/scheduling/action, NotificationHandler]
  data_flow: "Push payload → deep link → scheduling screen (requests, dashboard)"

# External System Integration

Scheduling_Backend_API:
  - doc: docs/api/mobile-scheduling-openapi.yaml
  - integration: "POST requests via SchedulingApiClient → SchedulingRemoteDatasource"
  - critical_data: [dashboard_stats, pending_requests, shifts, labor_costs, employee_status]
  - error_handling: "DioException → custom exceptions → provider error state → ErrorDisplay"
```

### Implementation Examples

#### Example: Optimistic Request Approval (Existing Pattern)

**Why this example**: The pending requests provider uses an optimistic UI pattern with rollback that is the most complex state management in the module. New features (publish, override) should follow this same pattern.

```dart
// Pattern from pending_requests_provider.dart
Future<bool> approveRequest(int requestId) async {
  // 1. Snapshot original state for rollback
  final originalRequests = List<PendingRequest>.from(state.requests);

  // 2. Optimistic update — remove from list immediately
  state = state.copyWith(
    requests: state.requests.where((r) => r.requestId != requestId).toList(),
  );

  try {
    // 3. Execute API call
    await _repository.processRequest(
      typeNum: _typeNum,
      requestId: requestId,
      type: request.type,
      approved: true,
      idempotencyKey: _generateIdempotencyKey(),
      audit: await _buildAuditContext(),
    );

    // 4. Log analytics
    schedulingAnalytics.logRequestApproved(requestId: requestId, ...);

    return true;
  } catch (e) {
    // 5. Rollback on failure
    state = state.copyWith(requests: originalRequests, error: e.toString());
    return false;
  }
}
```

#### Example: Notification Preference Toggle (New Pattern)

**Why this example**: Notification preferences use optimistic UI with per-toggle granularity, which is a new pattern for this module.

```dart
// Optimistic toggle with rollback
Future<void> togglePreference(String categoryId, String channel, bool enabled) async {
  // 1. Snapshot current preference
  final original = Map<String, Map<String, bool>>.from(state.preferences);

  // 2. Optimistic update
  final updated = Map<String, Map<String, bool>>.from(state.preferences);
  updated[categoryId] = Map<String, bool>.from(updated[categoryId] ?? {});
  updated[categoryId]![channel] = enabled;
  state = state.copyWith(preferences: updated);

  try {
    await _repository.updateNotificationPreference(
      typeNum: _typeNum,
      categoryId: categoryId,
      channel: channel,
      enabled: enabled,
    );
  } catch (e) {
    // Rollback on failure
    state = state.copyWith(preferences: original, error: 'Failed to update preference');
  }
}
```

#### Example: Weekly Schedule with Publish Flow

**Why this example**: Schedule publishing is a one-way operation with confirmation dialog — demonstrates the critical UX pattern.

```dart
// Publish flow in weekly schedule screen
Future<void> _handlePublish(BuildContext context, WidgetRef ref, String typeNum, WeeklySummary schedule) async {
  // 1. Show confirmation dialog with stats
  final confirmed = await showDialog<bool>(
    context: context,
    builder: (ctx) => PublishScheduleDialog(
      shiftCount: schedule.shifts.length,
      employeeCount: schedule.uniqueEmployees,
    ),
  );

  if (confirmed != true) return;

  // 2. Execute publish
  try {
    final result = await ref.read(schedulingRepositoryProvider).publishSchedule(
      typeNum,
      schedule.weekStart,
    );

    // 3. Show success with notification counts
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Schedule published — ${result.employeeCount} employees notified'),
          backgroundColor: AppColors.success,
        ),
      );
    }

    // 4. Refresh schedule to show published state
    ref.invalidate(weeklyScheduleProvider(/* params */));

  } catch (e) {
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to publish: $e'), backgroundColor: AppColors.error),
      );
    }
  }
}
```

## Runtime View

### Primary Flow: Manager Daily Check-In

1. Manager taps Schedule tab in bottom navigation
2. GoRouter redirects to `/scheduling/dashboard` (or store selector if no store selected)
3. `SchedulingDashboardScreen` builds, watches `managerDashboardProvider(typeNum)`
4. Provider calls `SchedulingRepository.getManagerDashboard(typeNum)`
5. Repository calls `SchedulingRemoteDatasource.getManagerDashboard(typeNum)`
6. Datasource sends `POST /{typeNum}/manager/dashboard` via `SchedulingApiClient`
7. API client adds JWT Bearer token via `UnifiedAuthInterceptor`
8. Response parsed into `ManagerDashboardModel` → mapped to `ManagerDashboard` entity
9. Provider updates state → UI renders today's stats, pending counts, labor summary
10. Manager taps "3 Pending Requests" → navigates to `/scheduling/dashboard/requests`

```mermaid
sequenceDiagram
    actor Manager
    participant Nav as StoreNavigationTabBar
    participant Router as GoRouter
    participant Screen as SchedulingDashboardScreen
    participant Provider as ManagerDashboardProvider
    participant Repo as SchedulingRepository
    participant DS as SchedulingRemoteDatasource
    participant API as SchedulingApiClient
    participant Backend as Backend API

    Manager->>Nav: Tap Schedule tab
    Nav->>Router: context.go('/scheduling/dashboard')
    Router->>Screen: Build SchedulingDashboardScreen
    Screen->>Provider: ref.watch(managerDashboardProvider(typeNum))
    Provider->>Repo: getManagerDashboard(typeNum)
    Repo->>DS: getManagerDashboard(typeNum)
    DS->>API: post('/{typeNum}/manager/dashboard')
    API->>Backend: POST with JWT Bearer token
    Backend-->>API: { success: true, data: { today, pendingRequests, thisWeek } }
    API-->>DS: Response
    DS-->>Repo: ManagerDashboardModel
    Repo-->>Provider: ManagerDashboard (entity)
    Provider-->>Screen: AsyncValue.data(dashboard)
    Screen-->>Manager: Renders stats cards, pending counts, labor summary
```

### Secondary Flow: Batch Request Approval

1. Manager opens pending requests screen
2. Long-presses a request → enters selection mode
3. Selects multiple requests (same type, max 10)
4. Taps "Approve All" in batch action bar
5. Confirmation dialog shows selected count
6. Provider calls `batchProcessRequests()` with idempotency key
7. Backend processes batch → returns per-request results
8. Provider updates state: successful removals + failure display
9. UI shows success SnackBar (or partial failure with "Details" action)

### Tertiary Flow: Schedule Publishing

1. Manager opens weekly schedule view
2. Sees "Unpublished" indicator on current week
3. Taps "Publish" button
4. Confirmation dialog shows shift count and employee count
5. API call to `POST /{typeNum}/manager/schedule/publish`
6. Success: SnackBar with notification counts, schedule marked as published
7. Published indicator changes to green checkmark

### Error Handling

- **Network failure**: `DioException` caught in datasource → custom exception → provider sets `error` in state → UI shows `ErrorDisplay` with retry button
- **401 Unauthorized**: `UnifiedAuthInterceptor` attempts token refresh → if refresh fails → redirect to login
- **409 Conflict (Already Processed)**: Show "This request has already been processed" message with decision details
- **422 Validation Error**: Show field-specific error messages from backend `errors` object
- **500 Server Error**: Show generic "Something went wrong" with retry
- **Timeout**: Show "Request timed out — check your connection" with retry
- **Batch partial failure**: Show which succeeded and which failed, with per-failure error messages

### Complex Logic: Batch Processing State Machine

```
ALGORITHM: Batch Request Processing
INPUT: selectedIds, approved, note, typeNum
OUTPUT: BatchProcessResult (processed count, failures list)

1. VALIDATE: selectedIds.length <= 10, all same RequestType
2. SNAPSHOT: originalRequests = List.from(state.requests)
3. OPTIMISTIC_REMOVE: Remove selected requests from state.requests
4. GENERATE: idempotencyKey = UUID v4
5. BUILD_AUDIT: { deviceId, fingerprint, GPS (soft), timestamp }
6. CALL_API: batchProcessRequests(requests, approved, note, idempotencyKey, audit)
7. ON_SUCCESS:
   - If partialFailure: Restore failed requests to list
   - Log analytics: batchSize, successCount, failCount
   - Return BatchProcessResult
8. ON_FAILURE:
   - ROLLBACK: state.requests = originalRequests
   - Log error analytics
   - Return failure result
```

### Behavioral Specifications (PRD Alignment)

#### Pending Requests Sorting and Filtering
- **Default sort**: Submission date ascending (oldest first) — ensures FIFO processing per PRD Rule 1
- **Filter types**: All (default), Time-Off, Swap, Override — with count badges per type
- **Sorting is not user-configurable** in this phase — always oldest-first

#### Request History Behaviors
- **Default sort**: Most recent decision first (decision date descending) per PRD F10
- **Filters**: By status (Approved / Denied / All) per PRD F10
- **History depth**: Backend returns available history — no client-side date filtering. PRD specifies "at least 30 days" which is a backend responsibility
- **Displayed fields**: Employee name, request type, decision (approved/denied), manager who processed, decision date, manager note

#### Shift Deletion Flow
- Delete button in shift detail/edit view opens confirmation dialog
- Dialog includes required reason note TextField (validated non-empty)
- Delete disabled until reason is provided
- On confirm: calls `deleteShift(typeNum, shiftId, reason)`
- Success SnackBar: "Shift deleted"; Error SnackBar with retry

#### Who's Working Status Colors (PRD F4)
- Green (`AppColors.success`): Clocked In
- Orange/Amber (`AppColors.warning`): Late
- Blue (`AppColors.info`): Scheduled (not yet arrived)
- Gray (`AppColors.neutral400`): Clocked Out
- Purple (custom `Color(0xFF9333EA)`): On Leave

#### Deferred Features (PRD "Could Have")
- **F15 Team Notifications**: Deferred — requires backend notification dispatch validation and employee-facing flows
- **F16 Open Shift Management**: Partially supported (open shifts created via shift form with null employeeId). "List unclaimed" and "claim notification" flows deferred — these require Team App integration

## Deployment View

No change to existing deployment. The scheduling module is part of the main Flutter app binary.

- **Environment**: iOS and Android via Flutter
- **Configuration**: No new environment variables. API base URL configured in `scheduling_api_constants.dart`
- **Dependencies**: No new packages needed. Existing dependencies (Dio, Riverpod, GoRouter, fl_chart, Freezed) cover all requirements
- **Performance**: Target <2 second API response times. All screens use pull-to-refresh. Dashboard auto-refreshes on foreground return

## Cross-Cutting Concepts

### Pattern Documentation

```yaml
# Existing patterns used in this feature
- pattern: Riverpod Notifier + Custom State Class
  relevance: CRITICAL
  why: "All 13+ scheduling providers follow this pattern — state class with copyWith, Notifier with async build"

- pattern: Optimistic UI with Rollback
  relevance: HIGH
  why: "Request approval, shift CRUD, notification toggles all use optimistic updates"

- pattern: Family Provider (store-scoped)
  relevance: HIGH
  why: "All scheduling state is scoped per typeNum — NotifierProvider.family pattern"

- pattern: Model → Entity Mapping via Extensions
  relevance: HIGH
  why: "Freezed models convert to Equatable entities via .toEntity() extension methods"

# New patterns created for this feature
- pattern: Per-Toggle Optimistic UI
  relevance: MEDIUM
  why: "Notification preferences need per-toggle optimistic update with granular rollback"
```

### System-Wide Patterns

- **Security**: JWT Bearer tokens injected by `UnifiedAuthInterceptor`. Access level check at GoRouter redirect level (`roleId <= 2`). No scheduling-specific auth
- **Error Handling**: Layered — datasource catches `DioException` → maps to custom exceptions → provider catches → sets error state → UI shows `ErrorDisplay`
- **Performance**: Pull-to-refresh on all screens. Provider invalidation on mutations. No local caching (API is source of truth)
- **Logging/Auditing**: `SchedulingAnalyticsService` logs all screen views, user actions, and errors with standard `scheduling.*` event prefix. The PRD defines 15 specific tracking events (see PRD "Tracking Requirements" table) that must each map to a `SchedulingAnalyticsService` method. Event-to-method mapping:

| PRD Event | Analytics Method | Trigger Location |
|-----------|-----------------|------------------|
| `scheduling.dashboard.viewed` | `logDashboardViewed()` | `SchedulingDashboardScreen` |
| `scheduling.request.viewed` | `logRequestViewed()` | `RequestDetailSheet` |
| `scheduling.request.approved` | `logRequestApproved()` | `PendingRequestsProvider` |
| `scheduling.request.denied` | `logRequestDenied()` | `PendingRequestsProvider` |
| `scheduling.request.latency` | `logRequestLatency()` | `PendingRequestsProvider` (computed from submittedAt) |
| `scheduling.whos_working.viewed` | `logWhosWorkingViewed()` | `WhosWorkingScreen` |
| `scheduling.schedule.viewed` | `logScheduleViewed()` | `WeeklyScheduleScreen` |
| `scheduling.schedule.published` | `logSchedulePublished()` | `WeeklyScheduleScreen` (after publish) |
| `scheduling.shift.created` | `logShiftCreated()` | `ShiftProvider` |
| `scheduling.shift.edited` | `logShiftEdited()` | `ShiftProvider` |
| `scheduling.shift.deleted` | `logShiftDeleted()` | `ShiftProvider` |
| `scheduling.labor.viewed` | `logLaborViewed()` | `LaborCostScreen` |
| `scheduling.override.created` | `logOverrideCreated()` | `ClockOverrideScreen` |
| `scheduling.schedule.copied` | `logScheduleCopied()` | `WeeklyScheduleScreen` (after copy) |
| `scheduling.error` | `logError()` | All providers (catch blocks) |

### Implementation Patterns

#### Code Patterns and Conventions

- All scheduling files under `lib/*/scheduling/` namespace
- Provider files named `{feature}_provider.dart`
- Screen files in feature subdirectories: `screens/scheduling/{feature}/{feature}_screen.dart`
- Widget files in flat structure: `widgets/scheduling/{widget_name}.dart`
- Barrel exports in `scheduling_providers.dart`, `scheduling_screens.dart`, `scheduling_widgets.dart`
- All models use Freezed 3.x with `abstract class` keyword
- All entities use Equatable with `props` getter

#### State Management Patterns

- `NotifierProvider.family<Notifier, State, String>` where `String` is `typeNum`
- Custom state classes extending `Equatable` with `copyWith`, `isLoading`, `error`, domain data
- `Future.microtask(() => load())` in `build()` for initial data fetch
- `AsyncValue.guard()` for simple one-shot loads
- Manual `state = state.copyWith(isLoading: true)` → `try/catch` → `state.copyWith(data, isLoading: false)` for complex operations

#### Performance Characteristics

- Provider family instances per store — switching stores creates new provider instance
- No local persistence — all data fetched from API on screen entry
- Pull-to-refresh invalidates provider → re-fetches
- Dashboard `Today's Stats` is a single API call (efficient aggregation on backend)
- Batch operations limited to 10 items (backend constraint, not performance)

#### Integration Patterns

- `SchedulingApiClient` wraps Dio with scheduling-specific base URL
- All API calls go through `SchedulingRemoteDatasource` which handles error mapping
- `SchedulingContextProvider` bridges main auth → scheduling context (no direct auth dependency)
- Push notifications arrive via FCM → `NotificationHandler` → GoRouter deep link

#### Component Structure Pattern

```pseudocode
SCREEN: SchedulingFeatureScreen(typeNum)
  WATCH: featureProvider(typeNum)

  BUILD:
    IF loading AND no_data: CircularProgressIndicator
    IF error AND no_data: ErrorDisplay with retry
    IF data:
      RefreshIndicator
        SingleChildScrollView / ListView
          Feature-specific content

  ACTIONS:
    Pull-to-refresh → provider.refresh()
    Tap → navigate or show bottom sheet
    Mutation → provider.method() → SnackBar feedback
```

#### Error Handling Pattern

```pseudocode
FUNCTION: handle_scheduling_error(error)
  CLASSIFY:
    DioException.connectionTimeout → "Request timed out"
    DioException.receiveTimeout → "Server took too long to respond"
    DioException.connectionError → "No internet connection"
    DioException.response (401) → Token refresh (handled by interceptor)
    DioException.response (403) → "You don't have permission"
    DioException.response (404) → "Resource not found"
    DioException.response (409) → "Already processed by another manager"
    DioException.response (422) → Field-specific validation errors
    DioException.response (429) → "Too many requests — try again later"
    DioException.response (500+) → "Something went wrong"
    Other → "An unexpected error occurred"
  SANITIZE: Strip technical details (DioException, stack traces)
  DISPLAY: SnackBar for mutations, ErrorDisplay for full-screen loads
```

#### Test Pattern

```pseudocode
TEST_SCENARIO: "Provider handles successful API response"
  SETUP:
    Create mock SchedulingRepository
    Stub repository method to return test entity
    Create ProviderContainer with mock override
  EXECUTE:
    Read provider(typeNum)
    Await future completion
  VERIFY:
    State contains expected data
    isLoading is false
    error is null

TEST_SCENARIO: "Screen renders data correctly"
  SETUP:
    Create mock providers with test data
    Build widget with ProviderScope overrides
  EXECUTE:
    pumpAndSettle
  VERIFY:
    Expected widgets present (find.text, find.byType)
    Tap interactions navigate correctly
    Error states display ErrorDisplay
```

### Integration Points

- **Store Navigation (Spec 005)**: Schedule tab in `NavigationCategory.schedule`. Badge count from `tabBadgeProvider` reads `pendingRequestsProvider` count. Features under Schedule category: Time-Off Requests, Schedule View, Labor Costs
- **Push Notifications**: `NotificationHandler` in `lib/core/services/scheduling/notification_handler.dart` routes scheduling push types to appropriate screens via GoRouter
- **Analytics**: All events prefixed `scheduling.*` via `SchedulingAnalyticsService` singleton

## Architecture Decisions

- [x] ADR-1 **Validate-and-enhance over rebuild**: Enhance the existing ~5,000-line scheduling codebase rather than rewriting from scratch
  - Rationale: Existing code follows identical patterns to rest of app (Clean Architecture + Riverpod). Providers, models, entities, screens, widgets all exist and compile
  - Trade-offs: Must validate existing code against OpenAPI spec (may find mismatches). Harder to make sweeping pattern changes
  - User confirmed: Yes

- [x] ADR-2 **New screens for missing features**: Create new screen files for notification preferences, weekly schedule, daily schedule, and clock override rather than overloading existing screens
  - Rationale: Each PRD feature maps to a distinct user journey. Separate screens keep files focused (<500 lines each) and testable
  - Trade-offs: More files to maintain. Some shared state between weekly/daily schedule views
  - User confirmed: Yes

- [x] ADR-3 **fl_chart for labor cost visualization**: Use existing fl_chart dependency for weekly labor cost bar chart
  - Rationale: fl_chart 1.x is already a project dependency (used elsewhere). No new packages needed. Bar chart with daily breakdown is a standard fl_chart pattern
  - Trade-offs: fl_chart customization can be verbose. Alternative was simple Column-based bars, but fl_chart provides touch interactions and animations
  - User confirmed: Yes

- [x] ADR-4 **Additive entity/model extensions**: Add new fields to existing entities/models rather than creating separate models
  - Rationale: `Shift` entity needs `positionColor` and `isOpenShift`. `WeeklySummary` needs `isPublished`. Adding fields is backward-compatible
  - Trade-offs: Must regenerate Freezed code. Existing tests may need updating for new required fields (mitigated by using nullable new fields)
  - User confirmed: Yes

- [x] ADR-5 **Optimistic UI for all mutations**: All write operations use optimistic UI with rollback on failure
  - Rationale: Matches existing pattern in `pending_requests_provider.dart` (525 lines). Provides instant feedback. Rollback handles failures gracefully
  - Trade-offs: More complex state management. Must handle race conditions (two managers acting on same request)
  - User confirmed: Yes

- [x] ADR-6 **Defer F15 (Team Notifications) and F16 (Open Shift Management)**: These PRD "Could Have" features are explicitly deferred from this implementation phase
  - Rationale: F15 requires backend notification dispatch infrastructure validation. F16's "list unclaimed" and "claim notification" flows require employee-facing Team App integration not in scope. Open shifts are partially supported (create via shift form with null employeeId)
  - Trade-offs: Two PRD features won't be implemented. Partial open shift support exists but without the employee claim workflow
  - User confirmed: _Auto-approved per PRD MoSCoW classification (Could Have = deferrable)_

## Quality Requirements

- **Performance**: All screens render in <500ms after data is available. API calls complete in <2 seconds. List scrolling at 60fps for up to 100 items
- **Usability**: All scheduling screens follow STYLE_GUIDE.md design tokens. Color-coded status indicators per PRD F4: green (clocked in), orange/amber (late), blue (scheduled/not yet arrived), gray (clocked out), purple (on leave). Batch operations limited to 10 items with clear messaging
- **Security**: JWT Bearer tokens on all API calls (via interceptor). Manager-only access check at router level. No PII in analytics events. Error messages sanitized (no stack traces shown to user)
- **Reliability**: Optimistic UI with rollback for all mutations. Idempotency keys prevent duplicate submissions. 409 conflict handling for concurrent manager actions. Pull-to-refresh on all screens for manual recovery

## Risks and Technical Debt

### Known Technical Issues

- Existing dashboard screen uses `dynamic` types for `dashboard.today`, `dashboard.pendingRequests`, `dashboard.thisWeek` — should be strongly typed entity types
- `_handleLogout` in dashboard navigates to `/` — this is a no-op since scheduling doesn't have its own auth. Button should be removed or repurposed
- Some screens reference `schedulingAnalytics` as a global singleton — should use provider-based injection for testability (per NavigationAnalyticsService learning in MEMORY.md)

### Technical Debt

- `my_schedule_provider.dart` exists but "My Schedule" is not in the PRD (it's an employee feature). May need removal or repurposing as "Employee Schedule View" (F13)
- `conflicts_provider.dart` and `conflicts_screen.dart` handle a feature not explicitly in the PRD. Keep as-is for now but may need validation against actual backend behavior
- `scheduling_store_selector_screen.dart` may be redundant given Spec 005 store switcher in bottom nav. Evaluate during implementation

### Implementation Gotchas

- **Backend returns monetary values in cents (integers)**: Display must divide by 100 and format as currency. Verify existing `labor_cost_model.dart` handles this correctly
- **Times are in `HH:mm:ss` format (24-hour)**: Must parse and display correctly, especially for overnight shifts that cross midnight
- **Position colors are hex strings from backend**: May or may not include `#` prefix — strip `#` before parsing (per MEMORY.md learning about color hex parsing)
- **`dynamic` types in dashboard screen**: Existing code uses `dynamic` for dashboard sections — must be updated to use strongly-typed entity fields
- **`Future.microtask` in provider `build()`**: Tests must use widget-based tests with `ProviderScope`, not raw `ProviderContainer` (per MEMORY.md learning)
- **`Animate.restartOnHotReload = false` in test setup**: If any scheduling widgets use `flutter_animate`, tests need this guard (per MEMORY.md)
- **Push notification deep links**: `state.extra` casts can crash — always add redirect guard (per MEMORY.md GoRouter learning)

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Dashboard Loads Successfully**
```gherkin
Given: Manager is authenticated with roleId <= 2
And: Store has scheduling access enabled
When: Manager navigates to scheduling dashboard
Then: Today's stats (scheduled, clocked in, late, absent) are displayed
And: Pending request counts show with type breakdown
And: Labor cost summary shows hours, cost, and budget variance
And: Quick action cards navigate to correct screens
```

**Scenario 2: Batch Request Approval (Happy Path)**
```gherkin
Given: Pending requests screen shows 5 time-off requests
When: Manager long-presses first request
And: Selects 3 requests of same type
And: Taps "Approve All"
And: Confirms in dialog
Then: 3 requests are removed from list optimistically
And: API batch call succeeds
And: Success SnackBar shows "3 requests approved"
And: Selection mode exits
```

**Scenario 3: Batch Approval Partial Failure**
```gherkin
Given: Manager selects 3 requests and taps "Approve All"
When: API returns 2 success, 1 failure (already processed)
Then: 2 requests removed from list
And: 1 failed request restored to list
And: Warning SnackBar shows "2 approved, 1 failed" with "Details" action
And: Details dialog shows failure reason
```

**Scenario 4: Schedule Publishing**
```gherkin
Given: Manager views weekly schedule with 24 unpublished shifts
When: Manager taps "Publish" button
Then: Confirmation dialog shows "Publish 24 shifts? 8 employees will be notified"
When: Manager confirms
Then: Success SnackBar shows "Schedule published — 8 employees notified"
And: Schedule shows green "Published" indicator
And: Publish button is hidden
```

**Scenario 5: Clock Override**
```gherkin
Given: Manager opens clock override form
When: Manager selects employee, punch type "Clock In", sets time, enters reason
And: Submits form
Then: Success feedback shows punch ID
And: Override appears in employee's status
```

**Scenario 6: Notification Preference Toggle**
```gherkin
Given: Manager opens notification preferences for current store
When: Manager toggles "Shift Changes" push notification OFF
Then: Toggle updates immediately (optimistic)
And: API call succeeds in background
When: Manager switches to different store
Then: That store's preferences load independently
```

**Scenario 7: Network Failure During Request Approval**
```gherkin
Given: Manager approves a pending request
When: Network call fails
Then: Request reappears in list (rollback)
And: Error SnackBar shows "Failed to approve request"
And: Manager can retry
```

**Scenario 8: Concurrent Manager Conflict**
```gherkin
Given: Manager A views request #123
And: Manager B approves request #123 simultaneously
When: Manager A taps "Approve" on request #123
Then: API returns 409 (already processed)
And: UI shows "This request has already been processed"
And: Request removed from Manager A's pending list
```

### Test Coverage Requirements

- **Business Logic**: All provider methods tested — approve, deny, batch, publish, copy week, override, preference toggle. Guard clauses tested (null typeNum returns silently)
- **User Interface**: All 12+ screens have widget tests — loading state, error state, empty state, data state, tap interactions, pull-to-refresh
- **Integration Points**: Auth context bridge tested. Push notification deep linking tested. Store navigation badge count tested
- **Edge Cases**: Already-processed requests (409), partial batch failures, overnight shifts, null budget, empty schedule, 100+ pending requests
- **Performance**: List scrolling tests with 100 items. No `pumpAndSettle` timeouts from animations
- **Security**: Router redirect tests for roleId > 2. Error message sanitization (no stack traces in UI)

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| typeNum | Store identifier string (e.g., "bk01") | Used as family provider key and API path parameter |
| Pending Request | Time-off, shift swap, or clock override request awaiting manager decision | Core workflow — dashboard count, request list, approval flow |
| Open Shift | A shift with no employee assigned | Created by managers for coverage gaps; employees claim via Team App |
| Clock Override | Manual punch record created by a manager | Audit-required — needs reason note, creates real time entry |
| Publish | One-way operation making a week's schedule visible to employees | Cannot un-publish; notifies employees via push |
| Position Color | Hex color string from backend for employee positions | Renders on shift cards — e.g., "#4CAF50" for cashier |
| Budget Variance | Difference between actual labor cost and budget target | Null when store has no budget configured |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| Family Provider | Riverpod provider parameterized by a key (typeNum) | All scheduling providers use this for store-scoped state |
| Optimistic UI | Update UI immediately before API confirms, rollback on failure | Used for all mutations — approvals, denials, toggles |
| Idempotency Key | UUID sent with mutation requests to prevent duplicate processing | Generated per-request in provider, sent to backend |
| Audit Context | Device ID, GPS, fingerprint, timestamp sent with sensitive operations | Attached to request approvals and clock overrides |
| UnifiedAuthInterceptor | Dio interceptor that injects JWT Bearer tokens | Shared between main app and scheduling API clients |

### API/Interface Terms

| Term | Definition | Context |
|------|------------|---------|
| POST-only convention | All backend endpoints use POST method, even for read operations | Scheduling API follows this pattern — `POST /{typeNum}/manager/dashboard` |
| BatchProcessResult | Response from batch operations with per-item success/failure | `{ processed: 3, failed: [{ requestId, error }] }` |
| WeeklySummary | Aggregated schedule data for a week including all shifts | Primary data structure for weekly schedule view |
