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:
@@ -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>
|
||||
Reference in New Issue
Block a user