docs(01): create phase plan

Phase 01: Foundation
- 2 plan(s) in 2 wave(s)
- 2 autonomous, 0 checkpoint-only
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-02-09 21:08:34 +09:00
parent c3b38c25a2
commit 7f8075b76d
3 changed files with 730 additions and 4 deletions
+5 -4
View File
@@ -34,10 +34,11 @@ Decimal phases appear between their surrounding integers in numeric order.
4. Debug logging uses customDebugPrint instead of unconditional print statements 4. Debug logging uses customDebugPrint instead of unconditional print statements
5. App compiles and runs with identical functionality after reorganization 5. App compiles and runs with identical functionality after reorganization
**Plans**: TBD (1-2 plans expected) **Plans**: 2 plans
Plans: Plans:
- [ ] 01-01: [To be planned] - [ ] 01-01-PLAN.md — Infrastructure, Logger, Constants, Models extraction
- [ ] 01-02-PLAN.md — Screen extraction (SpeedometerScreen + OverlaySpeedometer)
### Phase 2: Provider Migration ### Phase 2: Provider Migration
@@ -109,7 +110,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
| Phase | Plans Complete | Status | Completed | | Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------| |-------|----------------|--------|-----------|
| 1. Foundation | 0/TBD | Not started | - | | 1. Foundation | 0/2 | Planned | - |
| 2. Provider Migration | 0/TBD | Not started | - | | 2. Provider Migration | 0/TBD | Not started | - |
| 3. Overlay Refactor | 0/TBD | Not started | - | | 3. Overlay Refactor | 0/TBD | Not started | - |
| 4. GPS & Power Optimization | 0/TBD | Not started | - | | 4. GPS & Power Optimization | 0/TBD | Not started | - |
@@ -127,4 +128,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
--- ---
*Roadmap created: 2026-02-09* *Roadmap created: 2026-02-09*
*Last updated: 2026-02-09* *Last updated: 2026-02-09 (Phase 1 planned)*
@@ -0,0 +1,481 @@
---
phase: 01-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- lib/services/logger.dart
- lib/config/gps_constants.dart
- lib/config/timing_constants.dart
- lib/config/overlay_constants.dart
- 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
- lib/services/gps_data_manager.dart
- lib/main.dart
- lib/gps_service.dart
autonomous: true
must_haves:
truths:
- "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.
</objective>
<execution_context>
@C:\Users\SKSup\.claude/get-shit-done/workflows/execute-plan.md
@C:\Users\SKSup\.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
Source files to read before modifying:
@lib/main.dart
@lib/gps_service.dart
@lib/speed_units.dart
@lib/color_themes.dart
@lib/services/gps_data_manager.dart
</context>
<tasks>
<task type="auto">
<name>Task 1: Create directory structure, Logger utility, and constants files</name>
<files>
lib/screens/ (directory)
lib/widgets/ (directory)
lib/providers/ (directory)
lib/models/ (directory)
lib/config/ (directory)
lib/services/logger.dart
lib/config/gps_constants.dart
lib/config/timing_constants.dart
lib/config/overlay_constants.dart
</files>
<action>
1. Create all six directories under lib/: screens/, widgets/, providers/, models/, config/. The services/ directory already exists.
2. Create `lib/services/logger.dart` — a Logger utility class with 4 static methods:
- `Logger.error(String message, [String? caller])`
- `Logger.warn(String message, [String? caller])`
- `Logger.info(String message, [String? caller])`
- `Logger.debug(String message, [String? caller])`
Implementation requirements:
- All methods guarded by `if (kDebugMode)` from `package:flutter/foundation.dart`
- Use `debugPrint()` (not `print()`) to avoid log truncation
- Include ANSI color codes: red for error, yellow for warn, blue for info, gray for debug
- Include ISO 8601 timestamp in output
- Include caller tag in brackets when provided: `[ERROR] 2026-02-09T12:00:00 [GpsDataManager] message`
- Reset ANSI color after each line with `\x1B[0m`
This replaces the existing `customDebugPrint()` function in main.dart.
3. Create `lib/config/gps_constants.dart` — extract GPS behavioral constants from gps_data_manager.dart:
```dart
abstract class GpsConfig {
static const Duration STALE_DATA_THRESHOLD = Duration(seconds: 4);
static const Duration INITIAL_FIX_TIMEOUT = Duration(seconds: 20);
static const Duration UPDATE_TIMEOUT = Duration(seconds: 2);
static const double VALID_HEADING_MIN = 0.0;
static const double VALID_HEADING_MAX = 360.0;
static const double LOW_SPEED_THRESHOLD = 1.0;
}
```
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:
```dart
abstract class TimingConfig {
static const Duration HEARTBEAT_INTERVAL = Duration(seconds: 5);
static const Duration OVERLAY_STATUS_CHECK_INTERVAL = Duration(milliseconds: 1000);
static const Duration TAP_CLOSE_DELAY = Duration(milliseconds: 500);
static const Duration LONG_PRESS_SIGNAL_DELAY = Duration(milliseconds: 50);
}
```
5. Create `lib/config/overlay_constants.dart` — extract overlay sizing constants from main.dart:
```dart
abstract class OverlayConfig {
static const double WIDTH_PERCENTAGE = 0.45;
static const double ASPECT_RATIO = 0.6;
static const double BACKGROUND_OPACITY = 0.85;
static const double BORDER_OPACITY = 0.3;
static const double FONT_SIZE_RATIO = 0.2;
static const double UNIT_FONT_RATIO = 0.6;
static const double ICON_SIZE_RATIO = 0.72;
static const double HEADING_FONT_RATIO = 0.4;
static const double DEFAULT_SCREEN_WIDTH = 400.0;
static const double DEFAULT_OVERLAY_WIDTH = 280.0;
static const double DEFAULT_OVERLAY_HEIGHT = 140.0;
}
```
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:
From `_pushDataToOverlay()` (line 403-411): action, speedText, unitText, headingText, heading, unitIndex, themeIndex
From `_showFloatingWindow()` (line 441-450): adds overlayWidth, overlayHeight
From overlay long press (line 1063-1064): action 'longPressClose'
Create the class:
```dart
import 'dart:convert';
class OverlayMessage {
final String action;
final String? speedText;
final String? unitText;
final String? headingText;
final double? heading;
final int? unitIndex;
final int? themeIndex;
final double? overlayWidth;
final double? overlayHeight;
const OverlayMessage({
required this.action,
this.speedText,
this.unitText,
this.headingText,
this.heading,
this.unitIndex,
this.themeIndex,
this.overlayWidth,
this.overlayHeight,
});
// Factory constructors for common message types
factory OverlayMessage.updateDisplay({
required String speedText,
required String unitText,
required String headingText,
required double heading,
required int unitIndex,
required int themeIndex,
double? overlayWidth,
double? overlayHeight,
}) {
return OverlayMessage(
action: 'updateDisplay',
speedText: speedText,
unitText: unitText,
headingText: headingText,
heading: heading,
unitIndex: unitIndex,
themeIndex: themeIndex,
overlayWidth: overlayWidth,
overlayHeight: overlayHeight,
);
}
factory OverlayMessage.longPressClose() {
return const OverlayMessage(action: 'longPressClose');
}
factory OverlayMessage.overlayClosed() {
return const OverlayMessage(action: 'overlayClosed');
}
Map<String, dynamic> toMap() {
final map = <String, dynamic>{'action': action};
if (speedText != null) map['speedText'] = speedText;
if (unitText != null) map['unitText'] = unitText;
if (headingText != null) map['headingText'] = headingText;
if (heading != null) map['heading'] = heading;
if (unitIndex != null) map['unitIndex'] = unitIndex;
if (themeIndex != null) map['themeIndex'] = themeIndex;
if (overlayWidth != null) map['overlayWidth'] = overlayWidth;
if (overlayHeight != null) map['overlayHeight'] = overlayHeight;
return map;
}
factory OverlayMessage.fromMap(Map<dynamic, dynamic> map) {
return OverlayMessage(
action: map['action'] as String? ?? 'unknown',
speedText: map['speedText'] as String?,
unitText: map['unitText'] as String?,
headingText: map['headingText'] as String?,
heading: (map['heading'] as num?)?.toDouble(),
unitIndex: map['unitIndex'] as int?,
themeIndex: map['themeIndex'] as int?,
overlayWidth: (map['overlayWidth'] as num?)?.toDouble(),
overlayHeight: (map['overlayHeight'] as num?)?.toDouble(),
);
}
OverlayMessage copyWith({...all fields...}) { ... }
}
```
Include `toMap()` and `fromMap()` because this model crosses the overlay IPC boundary via `FlutterOverlayWindow.shareData()` which requires Map data.
5. Create `lib/models/theme_model.dart` — a new ThemeModel that wraps the existing ColorThemes:
```dart
import '../config/color_themes.dart';
class ThemeModel {
final int _currentIndex;
const ThemeModel({int currentIndex = 0}) : _currentIndex = currentIndex;
// Current palette access
ColorTheme get current => ColorThemes.getTheme(_currentIndex);
String get currentSetName => current.name;
int get currentIndex => _currentIndex;
// Rotate to next theme (circular)
ThemeModel rotate() {
return ThemeModel(
currentIndex: ColorThemes.getNextThemeIndex(_currentIndex),
);
}
ThemeModel copyWith({int? currentIndex}) {
return ThemeModel(
currentIndex: currentIndex ?? _currentIndex,
);
}
}
```
ThemeModel delegates palette storage to ColorThemes (which holds the static list). ThemeModel only tracks current selection and provides rotation. This matches the CONTEXT.md mental model: "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." The palettes are held via delegation to ColorThemes, not duplicated.
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.
1. **Update `lib/services/gps_data_manager.dart`:**
- Remove the ProcessedGpsData class definition (lines 6-40) — it now lives in models/
- Add imports:
```dart
import '../models/processed_gps_data.dart';
import '../config/speed_unit.dart';
import '../config/gps_constants.dart';
import '../services/logger.dart';
```
- Change import `'../speed_units.dart'` to `'../config/speed_unit.dart'`
- Replace `_staleDataThreshold` constant with `GpsConfig.STALE_DATA_THRESHOLD`
- Replace the Duration(seconds: 20) timeout with `GpsConfig.INITIAL_FIX_TIMEOUT`
- Replace heading validation magic numbers (0, 360) with `GpsConfig.VALID_HEADING_MIN`, `GpsConfig.VALID_HEADING_MAX`
- Replace ALL `print()` calls (there are ~27) with appropriate Logger calls:
- `print('[GpsDataManager] 🚀 ...')` → `Logger.info('Starting GPS manager initialization...', 'GpsDataManager')`
- `print('[GpsDataManager] ❌ ...')` → `Logger.error('GPS service disabled', 'GpsDataManager')`
- `print('[GpsDataManager] ⚠️ ...')` → `Logger.warn('Already initialized, skipping...', 'GpsDataManager')`
- `print('[GpsDataManager] 📡 ...')` → `Logger.debug('Raw GPS update: ...', 'GpsDataManager')`
- `print('[GpsDataManager] ✅ ...')` → `Logger.info('GPS manager initialized successfully', 'GpsDataManager')`
- Use error for failures/exceptions, warn for recoverable issues, info for lifecycle events, debug for data flow
- Remove emoji prefixes from log messages — the Logger's color coding replaces them
2. **Update `lib/gps_service.dart`:**
- No import changes needed (it only imports dart:async, geolocator, permission_handler)
- Replace heading validation magic numbers (0, 360) with GpsConfig constants:
```dart
import 'config/gps_constants.dart';
```
Change `heading >= 0 && heading <= 360` to `heading >= GpsConfig.VALID_HEADING_MIN && heading <= GpsConfig.VALID_HEADING_MAX`
3. **Update `lib/main.dart`:**
- Change `import 'speed_units.dart'` to `import 'config/speed_unit.dart'`
- Change `import 'color_themes.dart'` to `import 'config/color_themes.dart'`
- Add imports:
```dart
import 'config/timing_constants.dart';
import 'config/overlay_constants.dart';
import 'models/overlay_message.dart';
import 'services/logger.dart';
```
- Remove the `customDebugPrint()` function definition (lines 16-20) — replaced by Logger
- Replace ALL `customDebugPrint()` calls with appropriate `Logger.*()` calls:
- Pattern: `customDebugPrint('[Main] emoji message')` → `Logger.debug('message', 'Main')` (or info/warn/error as appropriate)
- Use Logger.error for catch blocks
- Use Logger.warn for unexpected states
- Use Logger.info for lifecycle transitions
- Use Logger.debug for data flow, state changes, gesture events
- Replace ALL remaining `print()` calls (lines 337-373 in _initializeGpsManager and _onGpsDataUpdate) with Logger calls
- Replace magic numbers in timer durations:
- `Duration(seconds: 5)` in heartbeat → `TimingConfig.HEARTBEAT_INTERVAL`
- `Duration(milliseconds: 1000)` in overlay check → `TimingConfig.OVERLAY_STATUS_CHECK_INTERVAL`
- `Duration(milliseconds: 500)` in tap close → `TimingConfig.TAP_CLOSE_DELAY`
- `Duration(milliseconds: 50)` in long press delay → `TimingConfig.LONG_PRESS_SIGNAL_DELAY`
- Replace magic numbers in overlay sizing:
- `0.45` width percentage → `OverlayConfig.WIDTH_PERCENTAGE`
- `0.6` aspect ratio → `OverlayConfig.ASPECT_RATIO`
- `0.85` background opacity → `OverlayConfig.BACKGROUND_OPACITY`
- `0.3` border opacity → `OverlayConfig.BORDER_OPACITY`
- 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
5. Directory structure exists: lib/screens/, lib/widgets/, lib/providers/, lib/models/, lib/config/, lib/services/
6. Files exist in new locations:
- lib/services/logger.dart
- lib/config/gps_constants.dart, timing_constants.dart, overlay_constants.dart
- lib/config/speed_unit.dart, color_themes.dart
- lib/models/processed_gps_data.dart, overlay_message.dart, theme_model.dart
7. gps_data_manager.dart imports ProcessedGpsData from models/ and uses GpsConfig constants
8. main.dart imports SpeedUnit from config/ and uses TimingConfig/OverlayConfig constants
9. Overlay IPC uses OverlayMessage.toMap()/fromMap() instead of raw Maps
Requirements coverage: ORG-01 (partial — dirs created), ORG-02, ORG-03, ORG-04, ORG-05, ORG-06
</verification>
<success_criteria>
- App compiles cleanly with `flutter analyze` showing zero errors
- All 8 requirements (ORG-01 through ORG-06) addressed by this plan are satisfied
- Zero bare print() or customDebugPrint() calls remain in codebase
- All behavioral constants use named references from config/ files
- OverlayMessage typed class handles all overlay IPC serialization
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation/01-01-SUMMARY.md`
</output>
@@ -0,0 +1,244 @@
---
phase: 01-foundation
plan: 02
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
- lib/screens/speedometer_screen.dart
- lib/screens/overlay_screen.dart
- lib/main.dart
autonomous: true
must_haves:
truths:
- "App compiles with flutter analyze showing zero errors"
- "main.dart contains only entry points (main, overlayMain), SpeedoApp widget, and imports — under 60 lines"
- "SpeedometerScreen lives in screens/speedometer_screen.dart with all its state and methods intact"
- "OverlaySpeedometer lives in screens/overlay_screen.dart with all its state and methods intact"
- "All existing functionality preserved — speed display, heading, themes, units, overlay, haptics, lifecycle all work identically"
artifacts:
- path: "lib/screens/speedometer_screen.dart"
provides: "Main speedometer screen widget and state"
contains: "class SpeedometerScreen"
- path: "lib/screens/overlay_screen.dart"
provides: "Overlay floating window widget and state"
contains: "class OverlaySpeedometer"
- path: "lib/main.dart"
provides: "App entry points and SpeedoApp widget only"
min_lines: 15
key_links:
- from: "lib/main.dart"
to: "lib/screens/speedometer_screen.dart"
via: "import and use as MaterialApp home"
pattern: "import.*screens/speedometer_screen"
- from: "lib/main.dart"
to: "lib/screens/overlay_screen.dart"
via: "import and use in overlayMain()"
pattern: "import.*screens/overlay_screen"
- from: "lib/screens/speedometer_screen.dart"
to: "lib/services/gps_data_manager.dart"
via: "GPS data subscription and display"
pattern: "import.*services/gps_data_manager"
- from: "lib/screens/speedometer_screen.dart"
to: "lib/models/overlay_message.dart"
via: "Overlay IPC messaging"
pattern: "import.*models/overlay_message"
- from: "lib/screens/overlay_screen.dart"
to: "lib/config/color_themes.dart"
via: "Theme rendering in overlay"
pattern: "import.*config/color_themes"
---
<objective>
Extract SpeedometerScreen and OverlaySpeedometer from the monolithic main.dart into dedicated screen files, leaving main.dart as a slim entry point.
Purpose: Completes the Phase 1 code organization by splitting the 1133-line main.dart into focused, single-responsibility files. This is the final structural change — after this, ORG-07 and ORG-08 are satisfied and the foundation phase is complete.
Output: speedometer_screen.dart (~780 lines), overlay_screen.dart (~240 lines), slimmed main.dart (~50 lines).
</objective>
<execution_context>
@C:\Users\SKSup\.claude/get-shit-done/workflows/execute-plan.md
@C:\Users\SKSup\.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-01-SUMMARY.md
Source files (post Plan 01):
@lib/main.dart
</context>
<tasks>
<task type="auto">
<name>Task 1: Extract SpeedometerScreen to screens/speedometer_screen.dart</name>
<files>
lib/screens/speedometer_screen.dart
lib/main.dart
</files>
<action>
1. Create `lib/screens/speedometer_screen.dart` by extracting the `SpeedometerScreen` widget class and its `_SpeedometerScreenState` class from main.dart.
**What to move (everything from the SpeedometerScreen class through end of _SpeedometerScreenState):**
- `class SpeedometerScreen extends StatefulWidget` and its state
- All state variables, methods, build methods, layout builders
- The `_getOverlaySize()` method
- All timer management, lifecycle observer, overlay messaging, GPS subscription
- The `_buildSpeedDisplay()`, `_buildPortraitLayout()`, `_buildLandscapeLayout()` methods
**Imports needed in the new file** (derive from what SpeedometerScreen actually uses):
```dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // HapticFeedback
import 'dart:async';
import 'dart:math' as math;
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:bg_launcher/bg_launcher.dart';
import '../config/speed_unit.dart';
import '../config/color_themes.dart';
import '../config/timing_constants.dart';
import '../config/overlay_constants.dart';
import '../models/overlay_message.dart';
import '../models/processed_gps_data.dart';
import '../services/gps_data_manager.dart';
import '../services/logger.dart';
```
**Do NOT include** in this file:
- `void main()` — stays in main.dart
- `void overlayMain()` — stays in main.dart
- `class SpeedoApp` — stays in main.dart
- `class OverlaySpeedometer` — goes to overlay_screen.dart in Task 2
**Critical: preserve ALL business logic, method signatures, and widget structure exactly.** This is a pure file move, not a refactor. Every line of SpeedometerScreen code must appear in the new file unchanged (except import paths).
2. Remove SpeedometerScreen class and _SpeedometerScreenState from main.dart. Leave only: imports, main(), overlayMain(), SpeedoApp, and OverlaySpeedometer (OverlaySpeedometer will be moved in Task 2).
3. Update main.dart imports to add:
```dart
import 'screens/speedometer_screen.dart';
```
4. Verify main.dart still references SpeedometerScreen correctly in SpeedoApp.build().
</action>
<verify>
- Run `flutter analyze` — zero errors
- Verify SpeedometerScreen class exists in lib/screens/speedometer_screen.dart
- Verify SpeedometerScreen class does NOT exist in lib/main.dart
- Verify main.dart still has SpeedoApp referencing SpeedometerScreen
</verify>
<done>
SpeedometerScreen fully extracted to screens/speedometer_screen.dart with all state, methods, and layout builders. main.dart imports it correctly. App compiles.
</done>
</task>
<task type="auto">
<name>Task 2: Extract OverlaySpeedometer to screens/overlay_screen.dart and slim down main.dart</name>
<files>
lib/screens/overlay_screen.dart
lib/main.dart
</files>
<action>
1. Create `lib/screens/overlay_screen.dart` by extracting the `OverlaySpeedometer` widget class and its `_OverlaySpeedometerState` class from main.dart.
**What to move:**
- `class OverlaySpeedometer extends StatefulWidget` and its state
- All state variables: _speedText, _unitText, _headingText, _heading, _currentThemeIndex, _systemScreenWidth, _overlayWidth, _overlayHeight
- All methods: _getSystemScreenSize(), _listenToMainAppMessages(), build()
- The entire gesture detection layer and overlay UI
**Imports needed in the new file:**
```dart
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import '../config/color_themes.dart';
import '../config/overlay_constants.dart';
import '../models/overlay_message.dart';
import '../services/logger.dart';
```
**Critical: preserve ALL widget structure, gesture handling, and overlay communication exactly.** Pure file move.
2. Remove OverlaySpeedometer class and _OverlaySpeedometerState from main.dart.
3. Update main.dart to add import and reference overlay_screen.dart in overlayMain():
```dart
import 'screens/overlay_screen.dart';
```
4. **Clean up main.dart** to be a slim entry point. After extraction, main.dart should contain ONLY:
- Package imports (flutter/material.dart, flutter/foundation.dart)
- Local imports (screens/speedometer_screen.dart, screens/overlay_screen.dart, services/logger.dart)
- `void main()` function
- `@pragma("vm:entry-point") void overlayMain()` function
- `class SpeedoApp extends StatelessWidget`
The file should be approximately 40-55 lines. Remove any now-unused imports (dart:async, wakelock_plus, bg_launcher, speed_units, color_themes, gps_data_manager, dart:math, flutter/services.dart — these are all used by SpeedometerScreen which is now in its own file).
5. Verify the overlayMain() function references OverlaySpeedometer correctly.
</action>
<verify>
- Run `flutter analyze` — zero errors
- Verify OverlaySpeedometer class exists in lib/screens/overlay_screen.dart
- Verify OverlaySpeedometer class does NOT exist in lib/main.dart
- Verify main.dart is under 60 lines
- Verify main.dart contains only: main(), overlayMain(), SpeedoApp class
- Grep for `class ` in main.dart — should find only SpeedoApp
</verify>
<done>
OverlaySpeedometer fully extracted to screens/overlay_screen.dart. main.dart slimmed to ~50 lines containing only entry points and SpeedoApp widget. All imports correct. App compiles with zero errors.
</done>
</task>
<task type="checkpoint:human-verify" gate="non-blocking">
<what-built>Complete Phase 1 restructure: monolithic main.dart split into modular file structure with extracted models, typed overlay messages, centralized constants, and proper logger.</what-built>
<how-to-verify>
1. Build and run the app on a physical Android device: `flutter run`
2. Verify speed display works (shows current GPS speed or '--' when stationary)
3. Tap the speed number to cycle through themes (Yuzuki, Yuuparo, Yuzuki Light, Yuuparo Light)
4. Tap the unit label to cycle units (km/h, mph, kts)
5. Tap the compass/navigation area to launch the floating overlay window
6. Verify overlay shows speed and heading data
7. Long-press the overlay to close it
8. Rotate device to verify landscape layout works
9. Check debug console for Logger output format (colored tags, timestamps, caller names)
</how-to-verify>
<resume-signal>Type "approved" if all functionality works identically to before restructure, or describe any issues.</resume-signal>
</task>
</tasks>
<verification>
After both tasks complete:
1. `flutter analyze` returns zero errors
2. main.dart is under 60 lines, containing only entry points and SpeedoApp
3. SpeedometerScreen lives in lib/screens/speedometer_screen.dart
4. OverlaySpeedometer lives in lib/screens/overlay_screen.dart
5. No widget classes remain in main.dart except SpeedoApp
6. All imports resolve correctly (no "Target of URI doesn't exist" errors)
7. Grep for `class SpeedometerScreen` — found only in screens/speedometer_screen.dart
8. Grep for `class OverlaySpeedometer` — found only in screens/overlay_screen.dart
Requirements coverage: ORG-07 (SpeedometerScreen extracted), ORG-08 (OverlaySpeedometer extracted), ORG-01 (complete — all directories populated)
</verification>
<success_criteria>
- App compiles cleanly with `flutter analyze` showing zero errors
- main.dart is a slim entry point under 60 lines
- All screen widgets live in dedicated files under screens/
- Combined with Plan 01, ALL Phase 1 requirements (ORG-01 through ORG-08) are satisfied
- App runs on physical device with identical behavior to pre-restructure
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`
</output>