A community-driven mobile application for locating, managing, and reporting on community fridges. Built with Flutter using modern state management, clean architecture, and reactive programming patterns.
Status: Active Development | Version: 1.0.0+10 | Platform: iOS & Android | Language: Dart/Flutter 3.8+
- Quick Start
- Prerequisites
- Installation & Setup
- Project Structure
- Architecture
- Features
- Development Workflow
- Testing
- Troubleshooting
- Contributing
- Resources & Documentation
Get the app running locally in 4 steps:
# 0. Navigate to the Flutter app directory
cd fridgefinder_flutter
# 1. Install dependencies
flutter pub get
# 2. Generate code (freezed models, Riverpod providers, JSON serialization)
dart run build_runner build
# 3. Run the app
flutter runThat's it! The app will launch with dev environment settings by default.
- Flutter: >=3.8.0 <4.0.0
- Dart SDK: >=3.8.0 <4.0.0
- iOS: Xcode 14+ with iOS 14.0+ support
- Android: Android Studio with Android API 21+ (for geolocator)
- Xcode Command Line Tools (Mac)
- Git (version control)
- VS Code with Flutter extension (development)
- Android Emulator or iOS Simulator (testing)
- Physical devices (hardware testing)
# Check Flutter & Dart versions
flutter --version
# Check environment setup
flutter doctor
# All checks should pass (green checkmarks) before proceedinggit clone <repository-url>
cd CFM_Mobile/fridgefinder_flutterflutter pub getThis installs all packages listed in pubspec.yaml. Key dependencies:
- State Management: Riverpod 3.0+ with code generation (reactive, type-safe)
- Navigation: GoRouter (type-safe, persistent navigation)
- Mapping: flutter_map with OpenStreetMap tiles
- Marker Clustering: flutter_map_marker_cluster for grouping nearby markers
- Tile Caching: flutter_map_cache with persistent disk cache (200MB, survives app restarts)
- HTTP Client: Dio with interceptors and connectivity checks
- Local Storage: Hive (user settings, filter state)
- Immutable Models: Freezed with JSON serialization (code-generated)
- Logging: logger package for structured logging
- Connectivity: connectivity_plus for network status
The project uses code generation for:
- Freezed: Immutable data models with
copyWith,toString,==, andhashCode - Riverpod: Type-safe provider generators with
@riverpodannotation - JSON Serialization: Automatic
fromJson/toJsonmethods
dart run build_runner build --delete-conflicting-outputsRun this after modifying any:
- Freezed models (with
@freezedannotation) - Riverpod providers (with
@riverpodannotation) - JSON serializable classes (with
@JsonSerializableannotation)
The app uses a dual environment configuration:
- Fridge Data API: DEV environment by default (
api-dev.communityfridgefinder.com) - Firebase Services: PRODUCTION environment (always)
To switch fridge API environment:
- Open the app
- Go to Profile β Settings
- Toggle API Environment between Dev/Prod
This persists to local storage via Hive.
Note: Firebase services (Auth, Messaging, Database, Cloud Functions) always use production. See ENVIRONMENT_CONFIGURATION.md for details.
# Run on default device/emulator
flutter run
# Run on specific device
flutter run -d <device-id>
# Run in release mode (optimized)
flutter run --release
# Enable verbose logging
flutter run -vfridgefinder_flutter/
βββ lib/ # Application source code
β βββ main.dart # Entry point, initializes Hive & Riverpod
β βββ app.dart # Root widget, theme/routing configuration
β βββ src/
β βββ common_widgets/ # Shared UI components (reusable)
β β βββ main_shell.dart # Layout shell with bottom nav, header, drawer
β β βββ bottom_nav_bar.dart # Navigation tabs
β β βββ loading_indicator.dart # Loading state UI
β β βββ error_view.dart # Error state with retry
β β βββ empty_state.dart # Empty list state
β β
β βββ core/ # Core infrastructure (non-feature-specific)
β β βββ constants/
β β β βββ api_constants.dart # API endpoints (dev/prod), timeouts
β β βββ providers/
β β β βββ dio_provider.dart # HTTP client with interceptors
β β β βββ environment_provider.dart # API environment selection
β β β βββ location_provider.dart # Geolocation services
β β β βββ theme_provider.dart # Theme mode (light/dark/system)
β β β βββ map_cache_provider.dart # Map tile caching provider
β β βββ theme/
β β β βββ app_theme.dart # Material Design 3 theme definitions
β β βββ extensions/
β β β βββ theme_extensions.dart # Theme utility extensions
β β βββ utils/
β β β βββ fuzzy_search.dart # Text matching algorithm
β β β βββ distance_calculator.dart # Haversine distance formula
β β β βββ fridge_icon_utils.dart # Marker icon/color mapping
β β βββ exceptions/
β β βββ app_exceptions.dart # Custom exception hierarchy
β β
β βββ features/ # Feature modules (independent, scalable)
β β βββ map/ # Map view feature
β β β βββ data/
β β β β βββ fridge_repository.dart # API client (real)
β β β β βββ mock_fridge_repository.dart # Mock for testing
β β β βββ domain/
β β β β βββ fridge_domain.dart # Freezed data models & enums
β β β βββ presentation/
β β β βββ controllers/
β β β β βββ fridge_list_controller.dart # Data & selection state
β β β β βββ map_filter_controller.dart # Filter state (persistent)
β β β β βββ filter_condition.dart # Filter matching logic
β β β βββ screens/
β β β β βββ map_screen.dart # Main map UI
β β β βββ widgets/
β β β βββ fridge_marker.dart # Custom map markers
β β β βββ fridge_cluster_widget.dart # Cluster marker widget
β β β βββ map_filter_panel.dart # Filter UI
β β β βββ filter_pills_row.dart # Filter buttons row
β β β βββ filter_pill_button.dart # Individual filter button
β β β βββ user_location_indicator.dart # User position marker
β β β βββ filter_status_indicator.dart # Active filters badge
β β β
β β βββ list/ # List view feature
β β β βββ presentation/
β β β βββ screens/
β β β β βββ list_screen.dart # List view with distance sorting
β β β βββ widgets/
β β β βββ fridge_card.dart # Fridge list item
β β β
β β βββ profile/ # User settings & details
β β β βββ presentation/
β β β βββ screens/
β β β β βββ profile_screen.dart # Settings screen
β β β βββ widgets/
β β β βββ fridge_profile_sheet.dart # Fridge details
β β β βββ status_update_form.dart # Report submission form
β β β
β β βββ auth/ # Placeholder for authentication
β β βββ notifications/ # Placeholder for push notifications
β β βββ search/ # Placeholder for advanced search
β β β
β β βββ Note: Favorites feature removed from v1.0 (will be added in v1.1 with user accounts)
β β
β βββ routing/
β βββ router.dart # GoRouter configuration with ShellRoute
β
βββ test/ # Test suite (mirrors lib structure)
β βββ core/utils/
β βββ features/
β βββ shared/widgets/
β βββ integration/
β βββ fixtures/fridge_fixtures.dart # Mock data for tests
β βββ helpers/test_helpers.dart # Test utilities
β
βββ android/ # Android native code & config
βββ ios/ # iOS native code & config
βββ assets/
β βββ icons/ # SVG icons for markers
β βββ images/ # App images
βββ pubspec.yaml # Dependency definitions
βββ analysis_options.yaml # Linter rules
βββ README.md # This file
- Feature-Based Architecture: Each feature (map, list, profile) is an independent module with data/domain/presentation layers
- Shared Infrastructure: Core services, utilities, and common widgets are centralized
- Clear Boundaries: Features can be developed in parallel with minimal dependencies
- Scalability: New features can be added by creating new feature directories following the established pattern
FridgeFinder uses Clean Architecture with Riverpod state management, organized into feature modules:
Presentation Layer (UI)
β
Domain Layer (Business Logic)
β
Data Layer (API, Local Storage)
Riverpod provides reactive, composable state management without BuildContext:
// Data fetching (async)
@riverpod
Future<List<FridgeDomain>> fridgeList(FridgeListRef ref) async {
final repository = ref.watch(fridgeRepositoryProvider);
return repository.getFridges();
}
// Mutable state (local)
@riverpod
class MapFilter extends _$MapFilter {
@override
MapFilterState build() {
// State loaded from Hive or default
return MapFilterState();
}
void updateFilter(FilterCondition condition) {
// State updates trigger rebuilds of watching widgets
state = state.copyWith(...); // Freezed copyWith
}
}
// Computed derived state
@riverpod
List<FridgeDomain> mapFilteredFridges(MapFilteredFridgesRef ref) {
final fridgesAsync = ref.watch(fridgeListProvider);
final filters = ref.watch(mapFilterProvider);
return fridgesAsync.whenOrNull(
data: (fridges) => fridges.where((f) => filters.matchesFridge(f)).toList(),
) ?? [];
}Benefits:
- No BuildContext required
- Fine-grained reactivity (widget rebuilds only when needed)
- Easy testing (mock providers directly)
- Composable, reusable logic
- Automatic caching & request deduplication
Each feature module has three distinct layers:
Data Layer (/data)
- Repository pattern implementing abstract interfaces (dependency inversion)
- API client (Dio) with connectivity checks and interceptors
- Local storage (Hive) for persistent state
- Structured logging with logger package
- Error handling with custom exception hierarchy
Domain Layer (/domain)
- Freezed immutable models with automatic
copyWith,toString,==, andhashCode - Automatic JSON serialization via
json_serializable(code-generatedfromJson/toJson) - Precise API mapping (snake_case β camelCase) via
@JsonKeyannotations - Custom methods for computed properties (e.g.,
markerColor,fullAddress) - Domain-specific enums (e.g.,
FridgeCondition) - Repository interfaces (IFridgeRepository) for dependency inversion
- Business logic separated from data access
Presentation Layer (/presentation)
- Screens (full-page widgets)
- Controllers (state management via Riverpod)
- Widgets (reusable UI components)
- Separation: logic in controllers, UI in widgets
All dependencies are injected via Riverpod providers in core/providers/:
// HTTP client
@riverpod
Dio dio(DioRef ref) {
// Configuration, interceptors, error handling
}
// Repository
@riverpod
FridgeRepository fridgeRepository(FridgeRepositoryRef ref) {
final dioClient = ref.watch(dioProvider);
return FridgeRepository(dioClient);
}
// Usage in widgets/controllers
final repository = ref.watch(fridgeRepositoryProvider);Advantages:
- Easy to mock for testing
- Centralized configuration
- Consistent dependencies across app
- Environment-specific setup (dev/prod)
Type-safe, persistent navigation:
GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) => MainShell(child: child),
routes: [
GoRoute(path: '/', builder: ...), // Map screen
GoRoute(path: '/list', builder: ...), // List screen
// More routes...
],
),
],
)Features:
- ShellRoute maintains bottom nav while switching screens
- Named routes with type-safe parameters
- Slide transitions between screens
- Deep linking ready
1. MapScreen watches mapFilteredFridgesProvider
β
2. mapFilteredFridgesProvider depends on:
- fridgeListProvider (API data)
- mapFilterProvider (user filters)
β
3. When user applies filter:
- mapFilterProvider updates state β Hive persists
- mapFilteredFridgesProvider recomputes
- MapScreen rebuilds with new list
Custom exception hierarchy for type-safe error handling:
abstract class AppException implements Exception {
final String message;
AppException(this.message);
}
class NetworkException extends AppException { }
class ServerException extends AppException { }
class NotFoundException extends AppException { }
class LocationException extends AppException { }Errors are caught in repositories and exposed as AsyncValue in Riverpod:
@riverpod
Future<List<FridgeDomain>> fridgeList(FridgeListRef ref) async {
try {
return await repository.getFridges();
} on AppException catch (e) {
throw AsyncError(e, StackTrace.current);
}
}
// In UI:
final fridges = ref.watch(fridgeListProvider);
fridges.when(
data: (list) => FridgeListView(list),
loading: () => LoadingIndicator(),
error: (error, st) => ErrorView(error: error),
);Path: lib/src/features/map/
Interactive OpenStreetMap displaying community fridges with advanced filtering.
Key Features:
- Real-time Mapping: flutter_map with MapTiler Streets tiles
- Tile Caching: Map tiles cached persistently to disk for faster loading and offline support (200MB cache, 30-day expiry, survives app restarts)
- Marker Clustering: Groups nearby markers for cleaner display (configurable radius: 40px)
- User Location: Real-time GPS tracking with permission controls (optimized to prevent unnecessary map rebuilds)
- Filter Panel: Condition-based filtering + fuzzy text search
- Distance Sorting: Shows distance from user location
- Fridge Details: Tap marker β bottom sheet with full details
- Status Indicators: Color-coded markers (good=green, dirty=orange, broken=red)
Filtering System:
- Food Level Filters: Full (β₯75%), Many Items (50-74%), Few Items (1-49%), Empty (0%)
- Condition Filters: Needs Cleaning, Needs Servicing, Not at Location
- Filter Logic: No filters selected = show all fridges
- Search: Real-time fuzzy matching on fridge names + location search with geocoding
- Persistence: Filters saved locally (restored on app reopen)
State Management:
fridgeListProvider- Fetches all fridges from APImapFilterProvider- Manages filter state (persisted with Hive)mapFilteredFridgesProvider- Computed list of filtered fridges
Path: lib/src/features/list/
Scrollable list of community fridges with integrated filtering.
Key Features:
- Distance Sorting: Ordered by proximity to user location
- Unified Filters: Uses same filter pills as map view
- Location Search: Search for a location, shows fridges within 1.5km with removable pill indicator
- Fridge Cards: Visual cards showing name, status, distance, last report
- Responsive Design: Adapts to different screen sizes
- Pull-to-Refresh: Easy data refresh (via AsyncValue)
State Management:
- Shares
mapFilterProviderwith map view (consistent filtering) - Watches
mapFilteredFridgesProviderfor filtered results
Path: lib/src/features/profile/
User preferences and fridge details.
Settings:
- Theme Mode: Light / Dark / System (Material Design 3)
- Location Toggle: Enable/disable GPS access
- API Environment: Switch between dev/prod servers
- Persistence: All settings saved with Hive
Fridge Details (Bottom Sheet):
- Full fridge information (name, address, maintainer contact)
- Latest status report (condition, food percentage, timestamp)
- Report Form: Submit status updates with radio button condition selection (Good, Dirty, Out of Order, Not at Location)
- Photo Upload: Upload condition photos from camera or gallery
- Share & Directions: Native share and map app chooser (Google Maps, Apple Maps, Waze)
State Management:
themeModeProvider- Theme preferenceenvironmentProvider- API environmentlocationAccessProvider- Location toggle
Path: lib/src/features/auth/
Complete Firebase Authentication system with user profiles and account management.
Key Features:
-
Multi-Method Sign-In:
- Phone number authentication with SMS verification
- Google Sign-In (OAuth2)
- Automatic re-authentication handling
- Profile creation flow for new users
-
User Profiles:
- Firebase Realtime Database integration
- Username generation with uniqueness checking
- Volunteer/non-volunteer role selection
- Zip code collection (volunteers only, for funding tracking)
- User settings (notifications, geofencing, notification frequency)
-
Account Management:
- Full profile display in sidebar and profile page
- Sign out with provider invalidation
- Account deletion with data cleanup
- Session persistence across app launches
-
Points System:
- Automatic points awarding for volunteers
- +10 points for status reports
- +20 points for cleaning dirty fridges
- +30 points for stocking food
- Real-time points display in profile
State Management:
authUserProvider- Current Firebase useruserProfileProvider- User profile from databaseisAuthenticatedProvider- Authentication stateuserPointsProvider- Volunteer points trackingauthRepositoryProvider- Authentication operations
Path: lib/src/core/services/ and Cloud Functions
Complete notification system with Firebase Cloud Messaging and geofencing.
Key Features:
-
Firebase Cloud Messaging (FCM):
- Automatic token generation and registration
- Token saved to user profile in database
- Token refresh handling
- Foreground and background message handling
-
Cloud Functions (Deployed):
onFridgeStatusUpdate- Sends notifications when fridge status changesonUserSubscribe- Handles new fridge subscriptionscheckRoutineValidation- Daily scheduled check for fridges needing updates- Notification batching based on user preferences
-
Local Notifications:
- Notification display and interaction
- Payload-based navigation to fridge details
- Notification channels (Android)
- Permission handling (iOS)
-
Notification Navigation:
- Tap notification β Opens fridge details
- Deep linking support
- Provider-based navigation state
-
Subscription Management:
- Subscribe to individual fridges
- Custom notification preferences per subscription
- Edit notification preferences - Edit button in fridge profile for customizing which updates to receive
- 6 configurable notification types per fridge
- Different defaults for volunteers vs non-volunteers
- Permission requests on first subscription
- Visual indicators - Green glow on map markers and green icons in list view for subscribed fridges
State Management:
fcmServiceProvider- FCM service lifecyclenotificationNavigationProvider- Navigation from notificationssubscriptionManagerProvider- Fridge subscription managementsubscribedFridgesProvider- List of user's subscriptions
Path: lib/src/core/services/geofencing_service.dart + Cloud Functions
Production-grade background location monitoring with Firebase Cloud Messaging integration.
π― VOLUNTEER-ONLY FEATURE: Geofencing is exclusively for volunteer users. Regular users (food finders) never see geofencing options or prompts and only need "While Using" location permission for map features.
Key Features:
-
Background Location Monitoring:
- Automatic monitoring when enabled (volunteers only)
- Battery-optimized location tracking (2-minute intervals)
- 4-block radius geofencing (~400m)
- Checks proximity to ALL fridges (not just subscribed ones)
- Notifications sent for ANY nearby fridge needing attention
-
Production FCM Integration:
- Dual notification system:
- Primary: FCM via Cloud Function (works when app is closed/killed)
- Fallback: Local notification (if FCM fails)
- Cloud Function:
sendGeofencingNotification - User authentication and settings verification
- Reliable delivery even in background
- Dual notification system:
-
Smart Proximity Detection:
- Three notification types (prioritized):
- Cleaning: Fridge needs cleaning (20-50 points)
- Stocking: Fridge is empty (30-60 points)
- Routine: >2 days since last update (10 points)
- Personalized messages with distance in feet and point ranges
- Once-per-day limit: Maximum one notification per fridge per day (prevents spam)
- Notification history tracked both client-side and server-side
- Three notification types (prioritized):
-
Permission Management:
- Volunteer users: Prompted for "Always Allow" permission on first subscription (opt-in)
- Regular users: Only need "While Using" permission (never prompted for background access)
- Hybrid Geolocator + permission_handler approach for iOS compatibility
- Automatic upgrade prompt from "While Using" to "Always Allow"
- Fallback "Open Settings" dialog with direct link when iOS blocks automatic upgrade
- Permission status tracking in user profile
- Toggle geofencing in profile settings (volunteers only)
-
Notification Examples:
- Cleaning: "This fridge 328 feet away needs cleaning! Earn 20-50 points by heading there and taking care of it"
- Stocking: "This fridge 142 feet away could use some food- be a hero and earn 30-60 points by stocking it and posting a status update"
- Routine: "This fridge super closeby hasn't been updated recently- snap a quick pic and send a status report to keep the neighborhood informed and fed :) Earn 10 points!"
-
Daily Notification Policy:
- Each fridge can trigger at most one notification per day
- Separate tracking for each notification type (cleaning, stocking, routine)
- Example: If you get a "cleaning" notification at 9 AM, you won't get another "cleaning" notification for that fridge until tomorrow
- Different notification types can still trigger (e.g., "cleaning" in morning, "stocking" in evening if fridge becomes empty)
- History persists across app restarts and is synced to Firebase backend
- Automatic cleanup of notification records older than 7 days
State Management:
geofencingServiceProvider- Service lifecycle- User profile
settings.geofencingEnabled- Toggle state - Cloud Function
sendGeofencingNotification- Backend FCM delivery + daily limit enforcement - Firebase database path:
users/{userId}/geofencing/lastNotifications
Firebase Services:
- Authentication: User sign-in and account management
- Realtime Database: User profiles, subscriptions, points
- Cloud Messaging: Push notifications
- Cloud Functions: Background processing and notifications
Database Structure:
users/
{userId}/
userId: string
email: string
phoneNumber: string
username: string
isVolunteer: boolean
zipCode: string (if volunteer)
points: number
fcmToken: string
settings:
notificationsEnabled: boolean
geofencingEnabled: boolean
notificationFrequency: "immediate" | "daily" | "weekly"
subscribedFridges/
{fridgeId}/
notificationPreferences:
updatedWithFood: boolean
runningLow: boolean
empty: boolean
needsCleaning: boolean
needsServicing: boolean
routineValidation: boolean
Testing:
- Firebase Emulator Suite configured
- Auth, Database, and Functions emulators
- Emulator test helpers in
test/helpers/firebase_emulator_helpers.dart - Start emulators:
./start_emulators.sh
Path: lib/src/features/search/
- Feature module structure ready
- Fuzzy search utilities already built
- Ready for dedicated search screen
-
Update dependencies (if needed)
flutter pub upgrade
-
Start development with hot reload
flutter run
-
Make changes to Dart files
- Hot reload updates UI instantly (Ctrl/Cmd + S)
- Hot restart for deeper changes (Ctrl/Cmd + Shift + S)
-
Generate code when modifying models/providers
dart run build_runner build
-
Run tests before committing
flutter test -
Commit your changes
git add . git commit -m "Descriptive message about changes"
Following the established architecture:
-
Create feature directory structure
lib/src/features/my_feature/ βββ data/ β βββ my_repository.dart β βββ my_model.dart βββ domain/ β βββ my_domain.dart βββ presentation/ βββ controllers/ β βββ my_controller.dart βββ screens/ β βββ my_screen.dart βββ widgets/ -
Create data models with Freezed
import 'package:freezed_annotation/freezed_annotation.dart'; part 'my_domain.freezed.dart'; part 'my_domain.g.dart'; @freezed class MyDomain with _$MyDomain { const MyDomain._(); // Required for custom methods const factory MyDomain({ required String id, required String name, @Default(false) bool verified, }) = _MyDomain; factory MyDomain.fromJson(Map<String, dynamic> json) => _$MyDomainFromJson(json); }
-
Create repository
class MyRepository { final Dio dio; Future<List<MyDomain>> getItems() async { // API call } }
-
Create providers
@riverpod MyRepository myRepository(MyRepositoryRef ref) { return MyRepository(ref.watch(dioProvider)); } @riverpod Future<List<MyDomain>> myList(MyListRef ref) async { return ref.watch(myRepositoryProvider).getItems(); }
-
Create screens and widgets
class MyScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final items = ref.watch(myListProvider); return items.when( data: (list) => MyListView(list), loading: () => LoadingIndicator(), error: (err, st) => ErrorView(error: err), ); } }
-
Add route in GoRouter (
lib/src/routing/router.dart)GoRoute( path: '/my-feature', builder: (context, state) => MyScreen(), ),
-
Generate code
dart run build_runner build
Map/List Changes:
- Controllers are in
lib/src/features/map/presentation/controllers/ - Widgets are in
lib/src/features/map/presentation/widgets/ - Repository is in
lib/src/features/map/data/
Adding API Endpoints:
- Add method to
FridgeRepositoryinlib/src/features/map/data/ - Create Riverpod provider to call it
- Call from screen/widget via
ref.watch()
UI Customization:
- Edit theme in
lib/src/core/theme/app_theme.dart - Modify common widgets in
lib/src/common_widgets/ - Component-specific styling in feature widgets
The project uses multiple code generators:
# Full build
dart run build_runner build
# Watch for changes (continuous generation)
dart run build_runner watch
# Clean old generated code
dart run build_runner cleanTriggers code generation for:
@freezedclasses β*.freezed.dart(immutability, copyWith, equality)@riverpodproviders β*.g.dart(type-safe providers)@JsonSerializablemodels β*.g.dart(JSON serialization)
Important: Always run --delete-conflicting-outputs flag on first build:
dart run build_runner build --delete-conflicting-outputsFor iOS builds, ensure code generation runs before building:
dart run build_runner build --delete-conflicting-outputs
flutter build iosTests mirror the lib structure:
test/
βββ core/utils/ # Utility tests (fuzzy search, etc.)
βββ features/
β βββ map/presentation/ # Map screen & controller tests
β β βββ widgets/ # Widget tests (fridge_marker, fridge_cluster_widget)
β βββ list/presentation/ # List screen tests
βββ shared/widgets/ # Common widget tests
βββ integration/ # Full app workflow tests
β βββ app_navigation_integration_test.dart
β βββ fridge_detail_integration_test.dart
β βββ list_screen_integration_test.dart
βββ fixtures/ # Mock data (FridgeFixtures)
βββ helpers/ # Test utilities (test_helpers.dart)
Test Status: 271 tests passing, 5 navigation integration tests remaining (timing-related). New tests added for marker clustering (21 tests), map caching (5 tests), and filter improvements.
# Run all tests
flutter test
# Run specific test file
flutter test test/core/utils/fuzzy_search_test.dart
# Run with coverage
flutter test --coverage
# Run integration tests
flutter test integration_test/
# Watch for changes
flutter test --watchUnit Test Example (Utility):
void main() {
group('FuzzySearch', () {
test('matches exact strings', () {
final result = isFuzzyMatch('apple', 'apple');
expect(result, true);
});
test('fuzzy matches partial strings', () {
final result = isFuzzyMatch('appl', 'apple');
expect(result, true);
});
});
}Widget Test Example:
void main() {
group('FridgeCard', () {
testWidgets('displays fridge name', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: FridgeCard(fridge: mockFridge),
),
),
);
expect(find.text('Community Fridge #42'), findsOneWidget);
});
});
}Riverpod Provider Test Example (Code-Generated):
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fridgefinder_app/src/features/map/presentation/controllers/fridge_list_controller.dart';
import 'package:fridgefinder_app/src/core/providers/map_cache_provider.dart';
import '../../helpers/test_helpers.dart';
void main() {
group('FridgeListProvider', () {
test('fetches fridges from repository', () async {
final container = createTestProviderContainer(
overrides: [
fridgeRepositoryProvider.overrideWithValue(mockRepository),
],
);
final result = await container.read(fridgeListProvider.future);
expect(result, mockFridges);
});
});
group('CachedTileProvider', () {
test('creates cached tile provider', () async {
final container = createTestProviderContainer();
final cachedTileProvider = container.read(cachedTileProviderProvider);
expect(cachedTileProvider, isA<CachedTileProvider>());
});
});
}Note: Tests use createTestProviderContainer() helper which sets up all required overrides (Dio, Hive, etc.) automatically.
- Test utilities independently - Distance calculations, fuzzy search
- Mock repositories - Don't make real API calls in tests
- Use fixtures - Reusable mock data in
test/fixtures/ - Test providers - Override dependencies for unit testing
- Golden tests - Screenshot tests for UI consistency (optional)
flutter clean
flutter pub get
dart run build_runner build# Hot restart instead
flutter run -r
# Or restart from scratch
flutter run --no-fast-startdart run build_runner clean
dart run build_runner build- iOS: Check
ios/Runner/Info.plistfor location permissions - Android: Check
android/app/src/main/AndroidManifest.xml - Solution: Grant location permissions in app settings or reinstall app
- Ensure internet connection (MapTiler tiles are fetched from network on first load)
- Map tiles are cached persistently to disk (200MB cache) - cached tiles load instantly across app sessions
- Cache persists between app restarts - tiles only downloaded once
- Check Dio configuration in
lib/src/core/providers/dio_provider.dart - Clear cache: Delete app and reinstall, or clear app data
- Map cache provider:
lib/src/core/providers/map_cache_provider.dart(uses HiveCacheStore for persistent storage)
- This occurs when Hive databases don't exist (first run is normal)
- Solution: Clear app data and reinstall, or manually open boxes
- Check
lib/main.dartfor Hive initialization
- Check API environment setting (Profile β Settings)
- Verify API endpoints in
lib/src/core/constants/api_constants.dart - Check network connectivity
- Increase timeout values if needed
- Usually occurs during hot restart with Riverpod
- Solution: Use full restart or run
flutter run -r
# Enable verbose logging
flutter run -v
# Check device logs
flutter logs
# Inspect widget hierarchy
DevTools (in VS Code: Flutter: Open DevTools)
# Profile performance
DevTools β Performance tabWe welcome contributions! Here's how to contribute to FridgeFinder:
- Check existing issues - Don't duplicate work
- Review the architecture - Understand feature-based structure
- Read this README - Familiarize yourself with the codebase
- Set up development environment - Follow Installation & Setup section
-
Create a feature branch
git checkout -b feature/your-feature-name # or git checkout -b fix/bug-description -
Make your changes
- Follow the architecture patterns (feature-based structure)
- Keep commits atomic and descriptive
- Use meaningful commit messages (e.g., "Add fuzzy search to map filter")
-
Generate code if needed
dart run build_runner build
-
Test your changes
flutter test flutter analyze # Check code quality
-
Push to your branch
git push origin feature/your-feature-name
-
Create a Pull Request
- Clear title describing the change
- Description of what changed and why
- Reference any related issues
- Include test results
- Formatting: Run
dart format lib/before committing - Analysis: Fix all issues from
flutter analyze - Naming: Follow Dart naming conventions (camelCase for variables/functions)
- Comments: Document complex logic and public APIs
- Tests: Include tests for new features
- Models: Use
@freezedfor immutable data classes - Providers: Use
@riverpodfor state management
- Code follows project standards
- No
flutter analyzewarnings - Tests added/updated
- Code generation completed (
dart run build_runner build) - Commit messages are clear
- Works on both iOS and Android (if applicable)
- No console errors or warnings
- Ask questions in pull request comments
- Check existing issues for solutions
- Review code examples in similar features
- Reference Flutter/Riverpod documentation
- Architecture Details: See Architecture section above
- Feature Guides: Each feature folder contains implementation details
- API Constants:
lib/src/core/constants/api_constants.dart - Test Examples:
test/directory
- Flutter Docs: https://flutter.dev/docs
- Dart Docs: https://dart.dev/guides
- Riverpod Guide: https://riverpod.dev
- GoRouter Documentation: https://pub.dev/packages/go_router
- flutter_map Guide: https://github.com/fleaflet/flutter_map
- Freezed Documentation: https://pub.dev/packages/freezed
| Package | Purpose | Documentation |
|---|---|---|
| flutter_riverpod | State management | https://riverpod.dev |
| go_router | Navigation | https://pub.dev/packages/go_router |
| flutter_map | Mapping | https://github.com/fleaflet/flutter_map |
| flutter_map_marker_cluster | Marker clustering | https://pub.dev/packages/flutter_map_marker_cluster |
| flutter_map_cache | Map tile caching | https://pub.dev/packages/flutter_map_cache |
| dio | HTTP client | https://pub.dev/packages/dio |
| freezed | Code generation | https://pub.dev/packages/freezed |
| hive | Local storage | https://docs.hivedb.dev |
| geolocator | Location services | https://pub.dev/packages/geolocator |
- Material Design 3: https://m3.material.io
- Community Fridge Project: [Project Website/Repository]
- Flutter Community: https://flutter.dev/community
β Complete:
Core Features:
- Map view with filtering and clustering
- Marker clustering - Groups nearby markers for cleaner display (configurable radius)
- Map tile caching - 50MB in-memory cache with 30-day expiry for faster loading
- List view with distance-based sorting
- User location tracking
- Fuzzy search across fridges
- Filter persistence (Hive)
- Status reporting form with points integration
- Photo upload capability
- Theme customization (light/dark/system)
- Multi-environment support (dev/prod)
Firebase Integration (NEW):
-
β Authentication System:
- Phone number authentication with SMS verification
- Google Sign-In (OAuth2)
- User profile management (Firebase Realtime Database)
- Account deletion functionality
- Session persistence
-
β User Profiles & Points:
- Volunteer/non-volunteer role system
- Username generation with uniqueness checking
- Points system (+10 reports, +20 cleaning, +30 stocking)
- Profile display in sidebar and settings
- Zip code collection for volunteers
-
β Push Notifications:
- Firebase Cloud Messaging integration
- FCM token management (auto-save to database)
- Local notification display
- Notification navigation to fridge details
- Background and foreground message handling
-
β Cloud Functions (Deployed):
onFridgeStatusUpdate- Status change notificationsonUserSubscribe- Subscription handlingcheckRoutineValidation- Daily scheduled checks- Node.js 20 runtime
-
β Subscription System:
- Subscribe to individual fridges
- Custom notification preferences per fridge
- Edit notification preferences with dedicated dialog UI
- 6 toggleable notification types per subscription
- Permission requests on first subscription
- Notification and geofencing permission flows
- Visual indicators - Green glow on subscribed fridge markers and green icons in list view
-
β Geofencing:
- Background location monitoring
- 2-block radius proximity notifications
- Battery-optimized tracking
- Permission management
-
β Firebase Emulator Suite:
- Auth, Database, Functions emulators configured
- Test helpers for local development
- Automated setup script
Architecture:
- Freezed immutable models (all domain models)
- Riverpod code generation (all providers use
@riverpod) - Repository pattern with dependency inversion
- Structured logging and error handling
- Network connectivity checks
- App icons - Generated for iOS (blue background) and Android (transparent)
- Comprehensive test suite (270+ tests)
- App store readiness (privacy policy, proper permissions, etc.)
π§ In Development:
- Advanced search (structure ready)
- Comprehensive test coverage for Firebase features
β³ Planned:
- Favorites feature (v1.2 - user can favorite fridges)
- Offline support expansion (cached data access)
- User history and analytics
- Community features (ratings, comments)
- Notification batching (daily/weekly digests)
For questions, issues, or suggestions:
- GitHub Issues: Create an issue with detailed description
- Pull Requests: Submit improvements directly
- Documentation: Refer to sections above
- Code Examples: Check
lib/src/features/for implementation patterns
[Add your license here - e.g., MIT, Apache 2.0, etc.]
Built with β€οΈ by the Community Fridge Finder team and contributors.
Key Technologies:
- Flutter & Dart
- Riverpod
- GoRouter
- flutter_map
- Firebase
Last Updated: January 2025 | Version: 1.0.0+10 | Maintainers: [Your Team]
Problem: Hit MapTiler API 100k request limit in < 1 week of beta testing due to inefficient tile caching and unnecessary widget rebuilds.
Solution: Implemented persistent tile caching and optimized location stream handling.
-
β Persistent Tile Caching:
- Switched from
MemCacheStore(volatile memory) toHiveCacheStore(persistent disk) - Increased cache size from 50MB to 200MB
- Tiles now persist between app sessions - one-time download per tile
- Cache location: Application documents directory (
map_tile_cache/) - Expected Impact: 80-90% reduction in MapTiler API requests for returning users
- Switched from
-
β Widget Rebuild Optimization:
- Fixed location stream causing full map widget rebuilds every 10 meters
- Isolated location updates to only rebuild user location marker (Consumer widget)
- TileLayer no longer rebuilds unnecessarily during user movement
- Expected Impact: 50%+ reduction in tile re-requests during active use
-
β Combined Impact:
- Before: ~100k requests in <1 week (3M/month projected)
- After: ~15k requests/week for 10 beta testers (85-90% reduction)
- Scales to: ~60k requests/week for 50 users (within reasonable paid tier limits)
-
β Package Changes:
- Added
dio_cache_interceptor_hive_store: ^3.2.2for persistent caching - Added
path_provider: ^2.1.5for application directories - Downgraded
flutter_map_cacheto ^1.5.2 for compatibility - Downgraded
dio_cache_interceptorto ^3.5.0 for compatibility
- Added
-
β Testing:
- All map cache provider tests passing (5/5)
- iOS build successful (55.5MB)
- Android build successful (61.8MB)
- Zero analyzer errors in modified code
-
β Files Modified:
- map_cache_provider.dart - Switched to HiveCacheStore
- map_screen.dart - Fixed location stream rebuilds
- pubspec.yaml - Added persistent cache dependencies
- map_cache_provider_test.dart - Updated for async provider
Technical Details:
- Cache implementation uses Hive's efficient binary storage
- 30-day tile expiration policy (configurable)
- Async provider initialization for cache setup
- Graceful fallback to network tiles if cache fails
- Test-friendly with mocked cache provider for CI/CD
-
β Daily Notification Limits:
- Maximum one notification per fridge per day (prevents spam)
- Separate tracking for each notification type (cleaning, stocking, routine)
- Client-side tracking with Map<String, DateTime> (in-memory)
- Server-side enforcement in Cloud Function (persists to Firebase)
- Automatic cleanup of records older than 7 days
- Daily cleanup timer runs every 24 hours
-
β Database Structure:
- Firebase path:
users/{userId}/geofencing/lastNotifications/{fridgeId}_{type} - Stores ISO 8601 timestamp of last notification
- Cloud Function checks date before sending
- Returns
already_notified_todayif same-day notification exists
- Firebase path:
-
β Firebase Cloud Functions Integration:
- New Cloud Function:
sendGeofencingNotification(callable) - Backend FCM notification delivery (works when app closed/killed)
- Dual notification system: FCM primary, local fallback
- User authentication and settings verification in backend
- Production-grade error handling and logging
- Daily notification limit enforcement in backend
- New Cloud Function:
-
β Enhanced Geofencing Logic:
- Notifies for ALL fridges within 400m radius (not just subscribed)
- Personalized notification messages:
- Distance displayed in feet (converted from meters)
- Point ranges shown (20-50, 30-60, or 10 points)
- Encouraging, specific call-to-action text
- Three notification types (prioritized):
- Cleaning (if dirty)
- Stocking (if empty only, not "running low")
- Routine (if >2 days since update)
-
β Dual Environment Configuration:
- Fridge Data API: DEV by default (
api-dev.communityfridgefinder.com) - Firebase Services: PRODUCTION always
- Comprehensive documentation:
ENVIRONMENT_CONFIGURATION.md - Clear separation in codebase with comments
- No emulator mode active in production builds
- Fridge Data API: DEV by default (
-
β Dependencies:
- Added
cloud_functions: ^6.0.3package - All Firebase services verified to use production
- Added
-
β New Files:
ENVIRONMENT_CONFIGURATION.md- Detailed dual environment setup guide- Updated README with production FCM features
- Clear comments in all Firebase service files
-
β Code Documentation:
- "PRODUCTION ENVIRONMENT ONLY" comments added to:
auth_repository.dart- Firebase Authfcm_service.dart- Cloud Messagingdatabase_provider.dart- Realtime Databasegeofencing_service.dart- Cloud Functions
- Environment notes in
api_constants.dart
- "PRODUCTION ENVIRONMENT ONLY" comments added to:
- β Build Status: Clean compilation (128.9s)
- β Test Status: 397 passing / 528 total (75% pass rate)
- β Analyzer: Zero errors in modified files
- β Known Issues: 131 test failures are Firebase initialization issues in integration tests (not related to new features)
-
β Black Screen on Sign-Up Fixed:
- Fixed double-pop navigation issue in sign-in flow
SignInWidgetalready handles dialog dismissal, removed redundantNavigator.pop()in callback- Proper separation of responsibilities: widget handles lifecycle, callback handles next action
- No more empty navigation stack causing black screen
-
β Geofencing Permission Errors Fixed:
- Added
ref.mountedchecks after all async operations insubscriptionManagerProvider - Prevents "Cannot use Ref after disposed" errors during subscription flow
- Graceful exit when provider disposed during async gaps (permission requests, FCM token)
- Follows Riverpod best practices for async provider lifecycle management
- Added
-
β iOS Background Location Configuration:
- Added
locationtoUIBackgroundModesin Info.plist - Required for geofencing to work when app is not in foreground
- Complies with iOS App Store requirements
- Added
-
β Geofencing Now Volunteer-Only:
- Regular users (food finders): Never see geofencing prompts, only need "While Using" location
- Volunteer users: Opt-in geofencing on first subscription with clear explanation
- Geofencing toggle only visible in Profile settings for volunteers
- Reduces permission fatigue for users who just want to find food
- Better App Store compliance - background location only requested when truly needed
-
β Improved iOS Permission Flow:
- Hybrid Geolocator + permission_handler approach for better iOS compatibility
- Automatic attempt to upgrade "While Using" to "Always Allow"
- Detects when iOS blocks automatic upgrade and shows helpful dialog
- "Open Settings" button with direct link when manual upgrade needed
- Clear instructions: "Change location to 'Always'" with step-by-step guidance
- Comprehensive logging at each permission check step for debugging
-
β App Store Documentation:
- Created
APP_STORE_PERMISSIONS.mdwith detailed permission justifications - Explains volunteer-only nature of background location
- Testing instructions for App Store reviewers
- Privacy policy summary and user control documentation
- iOS and Android platform-specific permission details
- Created
- β
Geofencing Tests Updated:
- Added volunteer-only annotations to geofencing test suite
- Helper functions now include
isVolunteerparameter - Test documentation reflects new permission flow
-
β Subscribed Pill Filter:
- New filter pill for showing only subscribed fridges
- Only visible when user is authenticated AND has subscriptions
- Green transparent background (#4CAF50, 85% opacity) with white text/icon
- Positioned first (leftmost) in filter pills row
- Green glow when selected
- Filter persists across app restarts (Hive storage)
- Works seamlessly with other pill filters and search
-
β Enhanced Subscription Indicators:
- Green pulsing glow on subscribed fridge icon in profile sheet
- Consistent 1500ms animation cycle across all views
- Matches map markers and list view indicators
-
β Edit Notifications Dialog Fixed:
- Fixed loading indicator hanging forever on open
- Changed from sync
ref.read()to asyncref.read().future - Fixed loading indicator hanging after submit
- Captured ScaffoldMessenger reference before popping dialog
- Proper loading dialog management with context checking
- Dialog now opens, edits, and submits successfully
-
β List View Filtering Fixed:
- Added missing subscribed filter logic to list view
- Both map and list views now respect subscribed filter
- Filter order: pill conditions β subscribed β location β search
-
β Profile Sheet Overflow Fixed:
- Replaced rigid
Rowwith flexibleWrapwidget - Status text and distance now wrap to multiple lines naturally
- No more RenderFlex overflow errors
- Cleaner, more aesthetic layout
- Replaced rigid
- β
Compact Button Layout:
- Edit and Unsubscribe buttons now stack vertically
- Reduced button sizes (14px icons, 12px text, 28px height)
- Shortened "Unsubscribe" to "Unsub" for compact display
- Better use of limited header space in profile sheet
- β Test Status: 153 passing / 164 total (93.3% pass rate)
- β Build Status: Clean analyzer (0 errors)
- β Known Issues: 11 failing tests due to pre-existing Firebase/plugin initialization (not related to recent changes)
-
β Account Deletion Fixed:
- Fixed toast notification hanging during account deletion
- Properly captures ScaffoldMessenger reference before deletion
- Firebase Auth state listeners now handle updates automatically
- No more manual provider invalidation needed
-
β Geofencing Permission Loop Fixed:
- Switched from
permission_handlertoGeolocator - Now properly detects "Always Allow" location permission on iOS
- Handles iOS two-step permission flow (While Using β Always)
- No more infinite permission request loops
- Switched from
-
β Black Screen After Sign-Up Fixed:
- Removed manual provider invalidation causing race conditions
- Uses
postFrameCallbackfor proper navigation timing - Firebase Auth state changes handled naturally
- Fixed for both phone and Google authentication flows
-
β Firebase Type Casting Fixed:
- Created
convertFirebaseMap()utility for recursive conversion - Handles nested
Map<Object?, Object?>from Firebase Realtime Database - Fixes multiple black screen issues caused by type mismatches
- Properly converts all nested maps and lists
- Created
-
β Notification Preferences Editing:
- Edit button added next to Unsubscribe in fridge profile
- Customize notifications per subscribed fridge
- 6 toggleable notification types:
- Updated with Food
- Running Low
- Empty
- Needs Cleaning
- Needs Servicing
- Routine Validation
- Preferences persist to Firebase Realtime Database
- Clean dialog UI with icons and descriptions
-
β Visual Indicators for Subscribed Fridges:
- Green glowing effect on map markers for subscribed fridges
- Green icons in "My Fridges" list view
- Uses consistent green color (#4CAF50) across app
- Double-shadow effect for depth and visibility
- O(1) lookup performance with Set-based subscription checking
- β Build Status: Clean analyzer (0 errors, 20 pre-existing warnings)
- β Unit Tests: 267 passing (26 pre-existing failures in navigation tests)
- β Debug Build: Successful compilation
- β
Comprehensive Testing Guide: Created
TESTING_GUIDE.mdwith step-by-step testing instructions
-
β Authentication System:
- Implemented phone number authentication with SMS verification
- Integrated Google Sign-In (OAuth2)
- Fixed Navigator exception causing black screen after auth
- Fixed sign-up form showing for existing users on re-sign-in
- Proper provider invalidation on sign-out and account deletion
-
β User Profiles:
- Complete user profile management in Firebase Realtime Database
- Username generation with uniqueness validation
- Volunteer role system with zip code collection
- Profile display in sidebar and settings page
- Fixed user info not displaying properly
-
β Points System:
- Integrated with status report submission
- Automatic point awarding (+10 reports, +20 cleaning, +30 stocking)
- Real-time points display for volunteers
- Database tracking and persistence
-
β Push Notifications:
- Firebase Cloud Messaging fully implemented
- FCM token management (auto-register, save, refresh)
- Local notification service with navigation
- Notification tap β fridge details navigation
- Background and foreground message handling
-
β Cloud Functions:
- Deployed 3 functions to Firebase (Node.js 20)
onFridgeStatusUpdate- Notifies subscribers of status changesonUserSubscribe- Handles new subscriptionscheckRoutineValidation- Daily scheduled routine checks- ESLint configuration and code quality
-
β Subscription System:
- Subscribe to individual fridges
- Custom notification preferences per subscription
- Fixed subscribe dialog errors
- Fixed wrong checkbox defaults
- Permission requests on first subscription
-
β Geofencing:
- Background location monitoring implemented
- Permission requests with user-friendly explanations
- 2-block radius proximity alerts
- Integration with notification preferences
-
β Testing Infrastructure:
- Firebase Emulator Suite configured (Auth, Database, Functions)
- Test helpers for emulator integration
- Startup script for local development
- Firebase-specific test utilities
-
β Build & Deploy:
- Android APK builds successfully (66.6MB)
- iOS builds successfully (53.0MB)
- Cloud Functions deployed to production
- All critical bugs fixed
-
β Filter System Redesign:
- 7 filter categories: Full, Many Items, Few Items, Empty, Needs Cleaning, Needs Servicing, Not at Location
- Inverted filter logic: No filters selected = show all fridges
- Color coding based ONLY on food level (green/yellow/pink/white), conditions shown via icon decorations
- Filters always visible at top of map view
- Filter state persists with version tracking for migration
- Fixed "unknown food level" fridges appearing in filters
-
β Location Search:
- Map view: Enter location and press Enter to move map, clears search bar automatically
- List view: Shows fridges within 1.5km of searched location with removable blue pill indicator
- Prevents location text from being used in fuzzy name search
-
β Map Interactions:
- Map remains fully interactive while search bar is active
- Health bars now display for all fridge conditions (not just "good")
- Filter status indicator only shows when filters are active
-
β Status Reporting:
- Radio button UI for condition selection (cleaner than segmented buttons)
- Removed "Ghost" option from user-facing reports
- Photo upload: "Camera" and "Gallery" buttons (compact text)
- Fixed dialog width issue for photo upload buttons
-
β Directions & Sharing:
- Map app chooser shows available apps (Google Maps, Apple Maps, Waze)
- User selects preferred navigation app
- Native share functionality for fridge locations
-
β Dark Mode Improvements:
- Location pill now clearly visible in dark mode (blue color)
- Semi-transparent backgrounds for map overlays (0.5 alpha)
- Better contrast for filter pills and search elements
- β
Freezed Implementation: All domain models (
FridgeDomain,FridgeLocationDomain,FridgeMaintainerDomain,FridgeReportDomain,UserLocation,MapFilterState) now use Freezed for immutability - β
Riverpod Code Generation: All providers converted to code generation with
@riverpodannotation for type safety - β
Marker Clustering: Implemented
flutter_map_marker_clusterfor grouping nearby markers, improving map performance and UX - β
Map Tile Caching: Added
flutter_map_cachewith in-memory LRU cache (50MB) for faster map loading and reduced data usage - β Test Coverage: Comprehensive test suite with 271 passing tests
- β App Store Readiness: Privacy policy created, navigation cleaned up, proper permissions configured, app icons generated
- β
Const Optimization: Applied
dart fix --applyfor performance improvements - β Navigation Cleanup: Removed Favorites placeholder to ensure app store approval
- β Provider Overrides: Improved test helpers with proper provider mocking
- β Comprehensive Testing: Added tests for marker clustering (21), map caching (5), and filter improvements
- β Fixed filter initialization (v2 storage schema with automatic migration)
- β Fixed health bars not showing for dirty/out-of-order fridges
- β Fixed map overlay blocking interactions
- β Fixed "not showing" text appearing with no filters selected
- β Fixed list view showing no fridges by default
- β Fixed photo upload dialog width issue
- β Fixed unknown food level fridges matching filters
- β Privacy Policy: Created HTML privacy policy ready for hosting
- β
Documentation: Complete guide for remaining submission tasks (
REMAINING_TASKS_GUIDE.md) - β Configuration: iOS PrivacyInfo.xcprivacy, Android permissions properly configured
For details on remaining app store submission tasks, see REMAINING_TASKS_GUIDE.md in the project root.