- "App compiles with flutter analyze showing zero errors"
- "All existing functionality preserved — speed display, heading, themes, units, overlay all work identically"
- "Logger utility exists with 4 verbosity levels (error, warn, info, debug) guarded by kDebugMode"
- "Behavioral constants centralized in config/ with SCREAMING_SNAKE naming in abstract static classes"
- "ProcessedGpsData model lives in models/ as separate file with immutable fields and copyWith()"
- "OverlayMessage typed class exists with toMap()/fromMap() for overlay IPC serialization"
- "ThemeModel exists with internal palette storage, current palette exposure, and rotate() method"
- "SpeedUnit enum lives in config/speed_unit.dart"
- "ColorTheme/ColorThemes classes live in config/color_themes.dart"
artifacts:
- path: "lib/services/logger.dart"
provides: "Logger utility with error/warn/info/debug levels"
contains: "class Logger"
- path: "lib/config/gps_constants.dart"
provides: "GPS behavioral constants"
contains: "abstract class GpsConfig"
- path: "lib/config/timing_constants.dart"
provides: "Timer and interval constants"
contains: "abstract class TimingConfig"
- path: "lib/config/overlay_constants.dart"
provides: "Overlay sizing and display constants"
contains: "abstract class OverlayConfig"
- path: "lib/config/speed_unit.dart"
provides: "Speed unit enum with conversion"
contains: "enum SpeedUnit"
- path: "lib/config/color_themes.dart"
provides: "Color theme definitions"
contains: "class ColorTheme"
- path: "lib/models/processed_gps_data.dart"
provides: "Immutable GPS data model"
contains: "class ProcessedGpsData"
- path: "lib/models/overlay_message.dart"
provides: "Typed overlay IPC message"
contains: "class OverlayMessage"
- path: "lib/models/theme_model.dart"
provides: "Theme model with palette rotation"
contains: "class ThemeModel"
key_links:
- from: "lib/services/gps_data_manager.dart"
to: "lib/models/processed_gps_data.dart"
via: "import and usage of ProcessedGpsData"
pattern: "import.*models/processed_gps_data"
- from: "lib/services/gps_data_manager.dart"
to: "lib/config/gps_constants.dart"
via: "import and usage of GpsConfig constants"
pattern: "import.*config/gps_constants"
- from: "lib/main.dart"
to: "lib/config/speed_unit.dart"
via: "import SpeedUnit from new location"
pattern: "import.*config/speed_unit"
- from: "lib/main.dart"
to: "lib/config/color_themes.dart"
via: "import ColorThemes from new location"
pattern: "import.*config/color_themes"
---
<objective>
Create foundational infrastructure (directories, logger, constants) and extract all data models and config files to their proper locations in the new modular structure.
Purpose: Establishes the architectural skeleton that all subsequent Phase 1 and Phase 2 work depends on. Models, constants, and logger are the lowest-dependency pieces that everything else imports from.
Output: Logger utility, 3 constants files, 3 model files, 2 moved config files — all with updated imports across the codebase. App compiles and runs identically.
Use SCREAMING_SNAKE naming per CONTEXT.md decisions. Include a comment at the top: `// GPS behavioral constants — SCREAMING_SNAKE per project convention`
4. Create `lib/config/timing_constants.dart` — extract timer intervals from main.dart:
Do NOT update any imports in main.dart or gps_data_manager.dart yet — that happens in Task 2 and Task 3.
</action>
<verify>
- Run `flutter analyze` in the project root — should show zero new errors (new files are standalone, not imported yet)
- Verify all 6 directories exist under lib/
- Verify logger.dart, gps_constants.dart, timing_constants.dart, overlay_constants.dart all exist
</verify>
<done>
Directory structure created. Logger utility exists with 4 levels guarded by kDebugMode. Three constants files exist with all behavioral magic numbers extracted using SCREAMING_SNAKE in abstract static classes.
</done>
</task>
<task type="auto">
<name>Task 2: Move config files and extract data models</name>
<files>
lib/config/speed_unit.dart
lib/config/color_themes.dart
lib/models/processed_gps_data.dart
lib/models/overlay_message.dart
lib/models/theme_model.dart
</files>
<action>
1. Create `lib/config/speed_unit.dart` — copy the SpeedUnit enum from `lib/speed_units.dart` exactly as-is. Same content, new location. Do NOT delete the old file yet.
2. Create `lib/config/color_themes.dart` — copy both ColorTheme and ColorThemes classes from `lib/color_themes.dart` exactly as-is. Same content, new location. Do NOT delete the old file yet.
3. Create `lib/models/processed_gps_data.dart` — extract the ProcessedGpsData class from `lib/services/gps_data_manager.dart` (lines 6-40) into its own file. The class is already immutable with copyWith(). No changes to the class itself, just move it to its own file. Add the appropriate import for `package:flutter/foundation.dart` if needed (it isn't — this is a pure Dart class, no Flutter imports needed).
4. Create `lib/models/overlay_message.dart` — a NEW typed class replacing the unvalidated Map data currently used for overlay IPC. Analyze the current overlay message maps in main.dart to determine fields:
**Design rationale — delegation IS "holding":** The user's mental model says ThemeModel "holds all possible colour palettes, exposing only the current set of colours (with their nametags), informative 'current set name', and a method for rotating themes." ThemeModel satisfies this by delegating palette storage to ColorThemes (which holds the static list of all palettes). From the consumer's perspective, ThemeModel IS the single object that holds palettes, exposes `current` (the active ColorTheme with named colors), `currentSetName` (the label), and `rotate()` (circular cycling). The delegation to ColorThemes is an implementation detail — callers never interact with ColorThemes directly. This avoids duplicating the palette data while giving the user exactly the API they described.
Do NOT update imports in main.dart or gps_data_manager.dart yet — that is Task 3.
</action>
<verify>
- Run `flutter analyze` — zero new errors (new files not imported yet by existing code)
- Verify all 5 model/config files exist in their new locations
- Verify ProcessedGpsData in models/ matches the original in gps_data_manager.dart
</verify>
<done>
SpeedUnit and ColorThemes copied to config/. ProcessedGpsData extracted to models/. OverlayMessage typed class created with toMap()/fromMap(). ThemeModel created with palette rotation.
</done>
</task>
<task type="auto">
<name>Task 3: Rewire all imports, replace print() with Logger, delete old files</name>
<files>
lib/services/gps_data_manager.dart
lib/gps_service.dart
lib/main.dart
lib/speed_units.dart (delete)
lib/color_themes.dart (delete)
</files>
<action>
This is the critical wiring task. Every import must be updated in a single pass. The app must compile after this task completes.
**Why this is one task (not split):** All changes here are interdependent — you cannot update imports without also replacing the constants they point to, and you cannot delete old files until all imports have been rewired. Splitting into "rewire imports" + "replace constants" + "delete files" would create intermediate broken states where imports point to non-existent symbols. The changes span 3 files but are mechanically repetitive (find/replace patterns), not intellectually complex. A single atomic pass is safer.
- **Delete the `customDebugPrint()` function definition from main.dart** (lines 16-20, the `void customDebugPrint(String message)` function body). This function is fully replaced by Logger. Remove the entire function, not just its body.
- Replace overlay IPC Maps with OverlayMessage typed class:
- In `_pushDataToOverlay()`: replace `FlutterOverlayWindow.shareData({...map...})` with `FlutterOverlayWindow.shareData(OverlayMessage.updateDisplay(...).toMap())`
- In `_showFloatingWindow()`: replace the shareData map with `OverlayMessage.updateDisplay(...).toMap()`
- In overlay `_listenToMainAppMessages()`: parse incoming data with `OverlayMessage.fromMap(data)`
- In overlay long press: replace `FlutterOverlayWindow.shareData({'action': 'longPressClose'})` with `FlutterOverlayWindow.shareData(OverlayMessage.longPressClose().toMap())`
- In `_startListeningToOverlayMessages()`: parse incoming data using OverlayMessage.fromMap() instead of raw Map access
- In overlay constants: replace `_systemScreenWidth = 400` default with `OverlayConfig.DEFAULT_SCREEN_WIDTH`, etc.
4. **Delete old files:**
- Delete `lib/speed_units.dart` (moved to config/speed_unit.dart)
- Delete `lib/color_themes.dart` (moved to config/color_themes.dart)
**IMPORTANT:** Do not change any business logic, UI layout, gesture handling, or widget structure. Only change imports, constant references, Map→OverlayMessage conversions, and print→Logger replacements. The app must behave identically.
</action>
<verify>
- Run `flutter analyze` — zero errors
- Verify `lib/speed_units.dart` and `lib/color_themes.dart` no longer exist
- Grep for bare `print(` in lib/ — should find zero occurrences (all replaced with Logger)
- Grep for `customDebugPrint(` in lib/ — should find zero occurrences (all replaced with Logger)
- Grep for `import 'speed_units.dart'` — should find zero occurrences
- Grep for `import 'color_themes.dart'` — should find zero occurrences (only config/color_themes.dart)
- Verify main.dart no longer contains the customDebugPrint function definition
</verify>
<done>
All imports rewired to new file locations. All print()/customDebugPrint() replaced with Logger calls. All behavioral magic numbers replaced with named constants. OverlayMessage typed class used for all overlay IPC. Old speed_units.dart and color_themes.dart deleted. App compiles with zero analyzer errors.
</done>
</task>
</tasks>
<verification>
After all 3 tasks complete:
1. `flutter analyze` returns zero errors
2. No bare `print()` calls remain in lib/ (grep confirms)
3. No `customDebugPrint()` calls remain in lib/ (grep confirms)
4. Old files deleted: lib/speed_units.dart, lib/color_themes.dart