ionic-planb-logistic-app-fl.../CLAUDE.md
Mathias Beaulieu-Duncan edb106a7fd Refactor theme system and remove unused platforms
- Overhaul theme system with Svrnty design and WCAG AAA compliance
- Remove android, macos, and web platform files (iOS-only focus)
- Update components with improved dark mode map and UI refinements
- Enhance settings page with additional configuration options
- Add theme system documentation in lib/theme/README.md
- Update CLAUDE.md with comprehensive theme guidelines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 14:47:51 -05:00

13 KiB

CLAUDE.md - Plan B Logistics Flutter App

This file provides guidance to Claude Code when working with this Flutter/Dart project.

Project Overview

Plan B Logistics Flutter is a complete refactor of the Ionic Angular delivery management app into Flutter/Dart, maintaining all functionality while applying Svrnty design system (colors, typography, and Material Design 3).

Key Features:

  • OAuth2/OIDC authentication with Keycloak
  • CQRS pattern for API integration with Result error handling
  • Delivery route and delivery management
  • Photo upload for delivery proof
  • i18n support (French/English)
  • Native features: Camera, Phone calls, Maps

Essential Commands

Setup & Dependencies

flutter pub get
flutter pub upgrade
flutter pub run build_runner build --delete-conflicting-outputs

Development

flutter run -d chrome          # Web
flutter run -d macos           # macOS
flutter run -d ios             # iOS simulator
flutter run -d android         # Android emulator

Testing & Analysis

flutter test
flutter analyze
flutter test --coverage

Build

flutter build web
flutter build ios
flutter build android

Code Architecture

Core Structure

lib/
├── api/                    # CQRS API client and types
│   ├── types.dart         # Result<T>, Serializable, ApiError
│   ├── client.dart        # CqrsApiClient implementation
│   └── openapi_config.dart
├── models/                # Data models (strict typing)
│   ├── delivery.dart
│   ├── delivery_route.dart
│   ├── user_profile.dart
│   └── ...
├── services/              # Business logic
│   └── auth_service.dart
├── providers/             # Riverpod state management
│   └── providers.dart
├── pages/                 # Screen widgets
│   ├── login_page.dart
│   ├── routes_page.dart
│   ├── deliveries_page.dart
│   └── settings_page.dart
├── components/            # Reusable UI components
├── l10n/                  # i18n translations (*.arb files)
├── utils/                 # Utility functions
├── theme.dart             # Svrnty theme configuration
└── main.dart              # App entry point

Design System (Svrnty)

Primary Colors:

  • Primary (Crimson): #C91F37 (light) / #FF5A6D (dark)
  • Secondary (Slate Blue): #2D3843 (light) / #A5B6C8 (dark)
  • Tertiary (Green): #16803D (light) / #5EE890 (dark)
  • Error: #D32F2F (light) / #FF8A80 (dark)

Typography:

  • Primary Font: Montserrat (all weights 300-700)
  • Monospace Font: IBMPlexMono
  • Material Design 3 text styles with explicit color assignments

Theme System:

  • 2 Theme Variants: Light and Dark (forest green background)
  • WCAG AAA Compliant: All text meets 7:1 contrast minimum
  • Dark Theme Background: Forest Green (#0C1410) - unique branding
  • No Hardcoded Colors: All components use ColorScheme properties
  • Theme Files:
    • lib/theme.dart - Material 3 theme with 2 ColorScheme variants
    • lib/theme/color_system.dart - Svrnty color constants
    • lib/theme/status_colors.dart - Status color utilities
    • lib/theme/component_themes.dart - Component-specific themes
    • lib/theme/README.md - Complete theme documentation

Core Patterns & Standards

1. Strict Typing (MANDATORY)

NO dynamic, NO untyped var

// FORBIDDEN:
var data = fetchData();
dynamic result = api.call();

// REQUIRED:
DeliveryRoute data = fetchData();
Result<DeliveryRoute> result = api.call();

2. Serializable Interface

All models must implement Serializable:

class Delivery implements Serializable {
  final int id;
  final String name;

  const Delivery({required this.id, required this.name});

  factory Delivery.fromJson(Map<String, dynamic> json) {
    return Delivery(
      id: json['id'] as int,
      name: json['name'] as String,
    );
  }

  @override
  Map<String, Object?> toJson() => {
    'id': id,
    'name': name,
  };
}

3. Error Handling with Result

NEVER use try-catch for API calls. Use Result pattern:

final result = await apiClient.executeQuery<DeliveryRoute>(
  endpoint: 'deliveryRoutes',
  query: GetRoutesQuery(),
  fromJson: DeliveryRoute.fromJson,
);

result.when(
  success: (route) => showRoute(route),
  error: (error) {
    switch (error.type) {
      case ApiErrorType.network:
        showSnackbar('No connection');
      case ApiErrorType.timeout:
        showSnackbar('Request timeout');
      case ApiErrorType.validation:
        showValidationErrors(error.details);
      case ApiErrorType.http when error.statusCode == 401:
        navigateToLogin();
      default:
        showSnackbar('Error: ${error.message}');
    }
  },
);

4. CQRS API Integration

Query (Read Operations):

final result = await client.executeQuery<Delivery>(
  endpoint: 'simpleDeliveriesQueryItems',
  query: GetDeliveriesQuery(routeFragmentId: 123),
  fromJson: Delivery.fromJson,
);

Command (Write Operations):

final result = await client.executeCommand(
  endpoint: 'completeDelivery',
  command: CompleteDeliveryCommand(deliveryId: 123),
);

5. Riverpod State Management

Providers Pattern:

final authServiceProvider = Provider<AuthService>((ref) {
  return AuthService();
});

final userProfileProvider = FutureProvider<UserProfile?>((ref) async {
  final authService = ref.watch(authServiceProvider);
  final token = await authService.getToken();
  return token != null ? authService.decodeToken(token) : null;
});

// Usage in widget:
final userProfile = ref.watch(userProfileProvider);
userProfile.when(
  data: (profile) => Text(profile?.fullName ?? ''),
  loading: () => const CircularProgressIndicator(),
  error: (error, stackTrace) => Text('Error: $error'),
);

6. Authentication Flow

  1. Login: AuthService.login() triggers OAuth2/OIDC flow with Keycloak
  2. Token Storage: Secure storage with flutter_secure_storage
  3. Token Validation: Check expiration with JwtDecoder.isExpired()
  4. Auto Refresh: Implement token refresh on 401 responses
  5. Logout: Clear tokens from secure storage

Keycloak Configuration:

7. No Emojis Rule

MANDATORY: NO emojis in code, comments, or commit messages

// FORBIDDEN:
// Bug fix for delivery issues
void completeDelivery(int id) { ... } // Done

// REQUIRED:
// Bug fix for delivery completion logic
void completeDelivery(int id) { ... }

8. Theme System (MANDATORY)

Standard Color Access Pattern (use everywhere):

final colorScheme = Theme.of(context).colorScheme;

// Primary UI
color: colorScheme.primary        // Primary brand color
color: colorScheme.onPrimary      // Text on primary

// Text colors
color: colorScheme.onSurface      // Primary text
color: colorScheme.onSurfaceVariant // Secondary text

// Backgrounds
color: colorScheme.surface        // Page background
color: colorScheme.surfaceContainer // Card background

// Shadows
color: colorScheme.scrim.withValues(alpha: 0.2)

FORBIDDEN Patterns:

// NEVER use these in component files:
color: Colors.white        // FORBIDDEN
color: Colors.black        // FORBIDDEN
color: Color(0xFFXXXXXX)  // FORBIDDEN (except in theme files)
color: SvrntyColors.crimsonRed // FORBIDDEN - use colorScheme.primary

Dark Theme: The app uses a forest green dark theme (#0C1410) for unique branding. All text colors automatically adapt with WCAG AAA compliance (7:1 minimum contrast).

Status Colors:

import '../theme/status_colors.dart';

// Theme-aware status colors (preferred)
StatusColorScheme.getStatusColorFromTheme('completed', colorScheme)

// Hardcoded status colors (fallback)
StatusColorScheme.getStatusColor('completed')

Modifying Theme: To change brand colors, edit /lib/theme.dart:

  • lightScheme() - Light theme ColorScheme
  • darkScheme() - Dark theme ColorScheme

Documentation: See /lib/theme/README.md for complete theme system documentation.

API Integration

Base URLs

const String queryBaseUrl = 'https://api-route.goutezplanb.com/api/query';
const String commandBaseUrl = 'https://api-route.goutezplanb.com/api/command';

Key Endpoints

  • Query: /api/query/simpleDeliveriesQueryItems
  • Query: /api/query/simpleDeliveryRouteQueryItems
  • Command: /api/command/completeDelivery
  • Command: /api/command/markDeliveryAsUncompleted
  • Upload: /api/delivery/uploadDeliveryPicture

Authorization

All requests to API base URL must include Bearer token:

final authClient = CqrsApiClient(
  config: ApiClientConfig(
    baseUrl: 'https://api-route.goutezplanb.com',
    defaultHeaders: {'Authorization': 'Bearer $token'},
  ),
);

Internationalization (i18n)

File Structure

lib/l10n/
├── app_en.arb    # English translations
└── app_fr.arb    # French translations

ARB Format

{
  "appTitle": "Plan B Logistics",
  "loginButton": "Login with Keycloak",
  "deliveryStatus": "Delivery #{id} is {status}",
  "@deliveryStatus": {
    "placeholders": {
      "id": {"type": "int"},
      "status": {"type": "String"}
    }
  }
}

Usage in Code

AppLocalizations.of(context)!.appTitle
AppLocalizations.of(context)!.deliveryStatus(id: 123, status: 'completed')

Native Features

Camera Integration

  • Package: image_picker
  • Use: Photo capture for delivery proof
  • Platforms: iOS, Android, Web
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.camera);
if (pickedFile != null) {
  // Upload to server
}

Phone Calls

  • Package: url_launcher
  • Use: Call customer from delivery details
final Uri phoneUri = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(phoneUri)) {
  await launchUrl(phoneUri);
}

Maps Integration

  • Package: url_launcher
  • Use: Open maps app to show delivery address
final Uri mapUri = Uri(
  scheme: 'https',
  host: 'maps.google.com',
  queryParameters: {'q': '${address.latitude},${address.longitude}'},
);
if (await canLaunchUrl(mapUri)) {
  await launchUrl(mapUri);
}

File Naming Conventions

  • Files: snake_case (e.g., delivery_route.dart)
  • Classes: PascalCase (e.g., DeliveryRoute)
  • Variables/Functions: camelCase (e.g., deliveryId, completeDelivery())
  • Constants: camelCase or UPPER_SNAKE_CASE (e.g., kPrimaryColor or MAX_RETRIES)
  • Private members: Prefix with underscore (e.g., _secureStorage)

Git Conventions

  • Author: Svrnty
  • Co-Author: Jean-Philippe Brule jp@svrnty.io
  • Commits: Clear, concise messages describing the "why"
  • NO emojis in commits

Example:

Implement OAuth2/OIDC authentication with Keycloak

Adds AuthService with flutter_appauth integration, JWT token
management with secure storage, and automatic token refresh.

Co-Authored-By: Claude <noreply@anthropic.com>

Common Development Tasks

Adding a New Page

  1. Create widget file in lib/pages/[name]_page.dart
  2. Extend ConsumerWidget for Riverpod access
  3. Use strict typing for all parameters and variables
  4. Apply Svrnty colors from theme
  5. Handle loading/error states with .when()

Adding a New Data Model

  1. Create in lib/models/[name].dart
  2. Implement Serializable interface
  3. Add fromJson factory constructor
  4. Implement toJson() method
  5. Use explicit types (no dynamic)

Implementing API Call

  1. Create Query/Command class implementing Serializable
  2. Use CqrsApiClient.executeQuery() or .executeCommand()
  3. Handle Result with .when() pattern
  4. Never use try-catch for API calls
  5. Provide proper error messages to user

Adding i18n Support

  1. Add key to app_en.arb and app_fr.arb
  2. Use AppLocalizations.of(context)!.keyName in widgets
  3. For parameterized strings, define placeholders in ARB
  4. Test both English and French text

Testing

  • Unit tests: test/ directory
  • Widget tests: test/ directory with widget_test suffix
  • Use Riverpod's testing utilities for provider testing
  • Mock API client for service tests
  • Maintain >80% code coverage for business logic

Deployment Checklist

  • All strict typing rules followed
  • No dynamic or untyped var
  • All API calls use Result pattern
  • i18n translations complete for both languages
  • Theme colors correctly applied
  • No emojis in code or commits
  • Tests passing (flutter test)
  • Static analysis clean (flutter analyze)
  • No secrets in code (tokens, keys, credentials)
  • APK/IPA builds successful

Key Dependencies

  • flutter_riverpod: State management
  • flutter_appauth: OAuth2/OIDC
  • flutter_secure_storage: Token storage
  • jwt_decoder: JWT token parsing
  • http: HTTP client
  • image_picker: Camera/photo access
  • url_launcher: Phone calls and maps
  • animate_do: Animations (from Svrnty)
  • lottie: Loading animations
  • iconsax: Icon set
  • intl: Internationalization

Support & Documentation

  • Theme: See lib/theme.dart for complete Svrnty design system
  • API Types: See lib/api/types.dart for Result and error handling
  • Models: See lib/models/ for data structure examples
  • Providers: See lib/providers/providers.dart for state management setup
  • Auth: See lib/services/auth_service.dart for OAuth2/OIDC flow