docs(03-01): complete overlay foundation plan

Tasks completed: 2/2
- Task 1: Create OverlayService and add retry dependency
- Task 2: Add timestamp field to OverlayMessage model

SUMMARY: .planning/phases/03-overlay-refactor/03-01-SUMMARY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-02-11 19:16:21 +09:00
parent 9e3decc91a
commit acfbeb85a2
2 changed files with 288 additions and 21 deletions
+33 -21
View File
@@ -5,23 +5,23 @@
See: .planning/PROJECT.md (updated 2026-02-09) See: .planning/PROJECT.md (updated 2026-02-09)
**Core value:** Reliable, lag-free GPS speed display that coexists politely with other Android apps **Core value:** Reliable, lag-free GPS speed display that coexists politely with other Android apps
**Current focus:** Phase 2 - Provider Migration (COMPLETE) **Current focus:** Phase 3 - Overlay Refactor (IN PROGRESS)
## Current Position ## Current Position
Phase: 2 of 4 (Provider Migration) Phase: 3 of 4 (Overlay Refactor)
Plan: 2 of 2 complete Plan: 1 of 2 complete
Status: Phase complete — ready for Phase 3 planning Status: In progress
Last activity: 2026-02-10 — Completed 02-02-PLAN.md Last activity: 2026-02-11 — Completed 03-01-PLAN.md
Progress: [███░░░░░░░] 50% (Phase 2: 2/2 plans) Progress: [████░░░░░░] 55% (Phase 3: 1/2 plans)
## Performance Metrics ## Performance Metrics
**Velocity:** **Velocity:**
- Total plans completed: 4 - Total plans completed: 5
- Average duration: 7.6 minutes - Average duration: 6.9 minutes
- Total execution time: 0.51 hours - Total execution time: 0.58 hours
**By Phase:** **By Phase:**
@@ -29,10 +29,11 @@ Progress: [███░░░░░░░] 50% (Phase 2: 2/2 plans)
|-------|-------|-------|----------| |-------|-------|-------|----------|
| 1. Foundation | 2 | 19.6 min | 9.8 min | | 1. Foundation | 2 | 19.6 min | 9.8 min |
| 2. Provider Migration | 2 | 11.5 min | 5.75 min | | 2. Provider Migration | 2 | 11.5 min | 5.75 min |
| 3. Overlay Refactor | 1 | 4.0 min | 4.0 min |
**Recent Trend:** **Recent Trend:**
- Last 5 plans: 01-01 (12.8 min), 01-02 (6.8 min), 02-01 (7.7 min), 02-02 (3.8 min) - Last 5 plans: 01-02 (6.8 min), 02-01 (7.7 min), 02-02 (3.8 min), 03-01 (4.0 min)
- Trend: Accelerating (50% faster on plan 4 vs plan 1) - Trend: Continuing acceleration (4.0 min on latest plan)
*Updated after each plan completion* *Updated after each plan completion*
@@ -60,6 +61,11 @@ Recent decisions affecting current work:
| 02 | 02 | Speed Selector uses context.read<SettingsProvider>() | Implemented | | 02 | 02 | Speed Selector uses context.read<SettingsProvider>() | Implemented |
| 02 | 02 | Background heartbeat stays in screen (not provider) | Implemented | | 02 | 02 | Background heartbeat stays in screen (not provider) | Implemented |
| 02 | 02 | _isInBackground direct assignment (no setState) | Implemented | | 02 | 02 | _isInBackground direct assignment (no setState) | Implemented |
| 03 | 01 | OverlayService wraps all FlutterOverlayWindow calls | Implemented |
| 03 | 01 | showOverlay verifies with isActive() post-call | Implemented |
| 03 | 01 | shareData fire-and-forget (no retry, warn logging) | Implemented |
| 03 | 01 | Staleness thresholds: 3s dim, 10s dash | Implemented |
| 03 | 01 | Timestamp auto-populated in OverlayMessage factories | Implemented |
| 04 | TBD | Speed-adaptive GPS precision (~10 km/h threshold) | Pending | | 04 | TBD | Speed-adaptive GPS precision (~10 km/h threshold) | Pending |
| 04 | TBD | Background GPS only when overlay visible | Pending | | 04 | TBD | Background GPS only when overlay visible | Pending |
@@ -78,8 +84,12 @@ Recent decisions affecting current work:
- [ ] Manual testing: Settings persistence, theme/unit cycling, overlay toggle, background heartbeat - [ ] Manual testing: Settings persistence, theme/unit cycling, overlay toggle, background heartbeat
- [ ] Manual testing: Verify no "setState after dispose" or "ChangeNotifier after dispose" errors - [ ] Manual testing: Verify no "setState after dispose" or "ChangeNotifier after dispose" errors
**Phase 3 Pending:** **Phase 3 In Progress:**
- [ ] Plan Phase 3 (Overlay Refactor) next - [x] 03-01 complete — Overlay foundation infrastructure (4.0 min)
- [x] OverlayService wrapper with retry logic created
- [x] OverlayMessage enhanced with timestamp tracking
- [x] Staleness constants and GPS grace period defined
- [ ] 03-02 next — Integrate OverlayService and staleness detection
### Blockers/Concerns ### Blockers/Concerns
@@ -96,11 +106,13 @@ Recent decisions affecting current work:
- RESOLVED: Zero setState calls for shared state (theme, unit, GPS data, overlay status) - RESOLVED: Zero setState calls for shared state (theme, unit, GPS data, overlay status)
- INFO: Manual testing needed to verify settings persistence and provider lifecycle - INFO: Manual testing needed to verify settings persistence and provider lifecycle
**Phase 3 (Overlay Refactor) — Next:** **Phase 3 (Overlay Refactor) — In Progress:**
- MEDIUM RISK: flutter_overlay_window 0.5.0 has known communication issues - RESOLVED: OverlayService foundation complete with retry logic
- May need testing on multiple Android versions for reliability - RESOLVED: Timestamp tracking in OverlayMessage for staleness detection
- Overlay status polling alternative needs research (event-based vs 5s polling) - RESOLVED: Staleness constants defined (3s dim, 10s dash, 30s GPS grace)
- Data staleness issue to fix (data stops updating after running for a while) - MEDIUM RISK: flutter_overlay_window 0.5.0 has known communication issues (mitigated by retry + verification)
- INFO: Plan 03-02 will integrate OverlayService and implement staleness UI feedback
- INFO: Manual testing needed after 03-02 to verify overlay reliability improvements
**Phase 4 (GPS Optimization):** **Phase 4 (GPS Optimization):**
- Speed thresholds (8/12 km/h) need field testing validation - Speed thresholds (8/12 km/h) need field testing validation
@@ -108,7 +120,7 @@ Recent decisions affecting current work:
## Session Continuity ## Session Continuity
Last session: 2026-02-10 (Plan 02-02 execution complete) Last session: 2026-02-11 (Plan 03-01 execution complete)
Stopped at: Completed 02-02-PLAN.md — Phase 2 complete Stopped at: Completed 03-01-PLAN.md — Phase 3 Plan 1 complete
Resume file: None Resume file: None
Next action: Plan Phase 3 (Overlay Refactor) Next action: Execute 03-02-PLAN.md (Overlay integration)
@@ -0,0 +1,255 @@
---
phase: 03-overlay-refactor
plan: 01
subsystem: overlay-infrastructure
tags: [service-layer, ipc, staleness-detection, retry-logic]
requires:
- 02-02: Provider pattern infrastructure for overlay state management
provides:
- OverlayService wrapper isolating all flutter_overlay_window platform calls
- Timestamp tracking in OverlayMessage for staleness detection
- Staleness threshold constants (3s dim, 10s dash) for UI feedback
- GPS grace period constant (30s) for background GPS lifecycle
affects:
- 03-02: OverlayProvider will use OverlayService instead of direct FlutterOverlayWindow calls
- 03-02: Overlay screen will consume staleness constants for visual feedback
- 03-02: SpeedometerScreen will use GPS_GRACE_PERIOD for background GPS management
tech-stack:
added:
- retry: ^3.1.2
patterns:
- Service wrapper pattern for platform channel isolation
- Exponential backoff retry (3 attempts, 200ms delay factor)
- Fire-and-forget with warn-level logging for non-critical failures
- Timestamp-based staleness detection via millisecondsSinceEpoch serialization
key-files:
created:
- lib/services/overlay_service.dart
modified:
- lib/models/overlay_message.dart
- lib/config/overlay_constants.dart
- lib/config/timing_constants.dart
- pubspec.yaml
decisions:
- id: OVL-01
choice: OverlayService wraps all FlutterOverlayWindow calls
rationale: Isolate platform-specific code, centralize retry logic and error handling
status: implemented
- id: OVL-02
choice: showOverlay verifies overlay opened via isActive() call
rationale: Platform channel may succeed but overlay fail to appear - explicit verification required
status: implemented
- id: OVL-03
choice: shareData is fire-and-forget with warn-level logging
rationale: User decision - send every GPS tick, staleness detection handles drops, no retry needed
status: implemented
- id: OVL-04
choice: Staleness thresholds are 3s dim, 10s dash (NOT 2s/10s)
rationale: User specified 3 seconds for dim threshold in plan
status: implemented
- id: OVL-05
choice: Timestamp auto-populated by DateTime.now() in factories
rationale: Timestamp is metadata, not user data - automatic population prevents caller errors
status: implemented
- id: OVL-06
choice: Remove const from OverlayMessage constructors
rationale: DateTime.now() is runtime value, cannot be const - necessary for timestamp field
status: implemented
metrics:
duration: 4.0 minutes
completed: 2026-02-11
---
# Phase 03 Plan 01: Overlay Foundation Infrastructure Summary
**One-liner:** OverlayService wrapper with retry logic, timestamp-enhanced OverlayMessage, and staleness constants (3s dim, 10s dash, 30s GPS grace period).
## Objective Achieved
Created the foundation building blocks for overlay refactor by:
1. Isolating all flutter_overlay_window platform calls behind OverlayService with retry logic
2. Adding timestamp tracking to OverlayMessage for staleness detection
3. Defining staleness thresholds (3s dim, 10s dash) and GPS grace period (30s) as named constants
These components provide the service layer, enhanced model, and constants that Plan 02 will integrate into OverlayProvider, overlay screen, and SpeedometerScreen.
## Tasks Completed
| Task | Description | Commit | Files Modified |
|------|-------------|--------|----------------|
| 1 | Create OverlayService and add retry dependency | 505219c | overlay_service.dart, overlay_constants.dart, timing_constants.dart, pubspec.yaml |
| 2 | Add timestamp field to OverlayMessage model | 9e3decc | overlay_message.dart |
## Implementation Details
### OverlayService Architecture
**Platform Channel Wrapper:**
- `showOverlay({width, height})` - Retry logic with post-verification via isActive()
- `closeOverlay()` - Retry logic for robust cleanup
- `shareData(Map)` - Fire-and-forget with warn-level logging (no retry)
- `isActive()` - Error-safe overlay status check
- `overlayListener` - Direct stream delegation
- `checkPermission()` - Permission status helper
**Retry Configuration:**
- 3 attempts maximum
- 200ms delay factor (exponential backoff)
- Removed unnecessary `retryIf` checks (analyzer warnings fixed)
**Error Handling Strategy:**
- showOverlay/closeOverlay: Logger.error after exhausted retries, return false
- shareData: Logger.warn on failure, no retry (staleness detection handles drops)
- isActive: Logger.error + return false on exception
### OverlayMessage Enhancements
**Timestamp Field:**
- Auto-populated via `DateTime.now()` in all factory constructors
- No manual timestamp passing required from callers
- Serialized as `millisecondsSinceEpoch` (int) in toMap()
- Deserialized with fallback to `DateTime.now()` in fromMap() for backward compatibility
**Breaking Change Mitigation:**
- Removed `const` modifiers (DateTime.now() is runtime value)
- Added timestamp to copyWith() for completeness
- All existing factory APIs unchanged (timestamp is additive metadata)
### Constants Configuration
**Staleness Thresholds (overlay_constants.dart):**
- `STALENESS_DIM_THRESHOLD`: 3 seconds (dim speed display)
- `STALENESS_DASH_THRESHOLD`: 10 seconds (show dashes instead of speed)
- `STALENESS_DIM_OPACITY`: 0.5 (dimmed state opacity)
- `STALENESS_CHECK_INTERVAL`: 1 second (check frequency)
**GPS Grace Period (timing_constants.dart):**
- `GPS_GRACE_PERIOD`: 30 seconds (continue GPS after overlay close when backgrounded)
## Verification Results
**flutter pub get:**
- retry package (^3.1.2) installed successfully
- All dependencies resolved
**flutter analyze:**
- 26 info-level warnings (SCREAMING_SNAKE constants - expected per project convention)
- 0 errors or warnings in new code
- Fixed 2 unnecessary type check warnings in OverlayService retry logic
**Code Quality:**
- OverlayService: 93 lines with comprehensive error handling
- OverlayMessage timestamp integration: 15 lines added across all methods
- All must-have requirements satisfied
## Deviations from Plan
None - plan executed exactly as written.
## Decisions Made
**OVL-01: Service Wrapper Pattern**
- All flutter_overlay_window calls isolated in OverlayService
- Benefits: Centralized retry logic, single error handling point, easier testing
- Impact: Plan 02 will migrate OverlayProvider to use OverlayService
**OVL-02: Post-Verification in showOverlay**
- Added isActive() verification after FlutterOverlayWindow.showOverlay() completes
- Addresses: Platform channel may report success but overlay fails to appear
- Prevents: False positive overlay status in OverlayProvider
**OVL-03: Fire-and-Forget shareData**
- No retry logic for data sharing (user decision: forward every tick)
- Staleness detection handles dropped messages via timestamp tracking
- Warn-level logging prevents error log noise from expected occasional failures
**OVL-04: Staleness Timing**
- 3 seconds for dim (NOT 2 seconds as in some research notes)
- 10 seconds for dashes (consistent across all references)
- User explicitly specified 3s in plan MUST_HAVES
**OVL-05: Automatic Timestamp Population**
- DateTime.now() called in factory constructors, not passed by callers
- Prevents caller errors (forgetting timestamp, using wrong time source)
- Ensures consistency (all timestamps from same clock source)
**OVL-06: Remove const Modifiers**
- Required to allow DateTime.now() runtime values
- Trade-off: Lose compile-time const optimization for timestamp functionality
- Impact: Negligible (OverlayMessage instances are short-lived IPC data)
## Technical Debt
**None identified.**
All code follows project conventions:
- SCREAMING_SNAKE for behavioral constants
- Logger with caller tags for all logging
- Error handling with graceful degradation
- Type-safe OverlayMessage serialization
## Integration Readiness
**Ready for Plan 02:**
- OverlayService provides all needed platform operations
- OverlayMessage timestamp field ready for staleness calculation
- Constants defined and accessible for UI feedback logic
**Migration Path for Plan 02:**
1. Replace FlutterOverlayWindow.showOverlay in OverlayProvider with OverlayService.showOverlay
2. Replace FlutterOverlayWindow.closeOverlay with OverlayService.closeOverlay
3. Replace FlutterOverlayWindow.shareData with OverlayService.shareData
4. Add staleness detection timer in overlay screen using STALENESS_CHECK_INTERVAL
5. Implement dim/dash logic using STALENESS_DIM_THRESHOLD and STALENESS_DASH_THRESHOLD
6. Add GPS grace period logic in SpeedometerScreen using GPS_GRACE_PERIOD
## Artifacts Generated
**Service Layer:**
- `lib/services/overlay_service.dart` (93 lines)
**Enhanced Model:**
- `lib/models/overlay_message.dart` (+18 lines, -3 lines)
**Configuration:**
- `lib/config/overlay_constants.dart` (+12 lines)
- `lib/config/timing_constants.dart` (+3 lines)
**Dependencies:**
- `pubspec.yaml` (+3 lines - retry package)
## Next Steps
Plan 02 will:
1. Integrate OverlayService into OverlayProvider
2. Add staleness detection timer and UI feedback in overlay screen
3. Implement GPS grace period logic in SpeedometerScreen
4. Replace all direct FlutterOverlayWindow calls with OverlayService methods
**Estimated complexity:** Medium (refactor existing OverlayProvider and overlay screen)
**Blockers:** None - all foundation infrastructure complete
## Commits
1. **505219c** - feat(03-01): create OverlayService and add retry dependency
- Added retry package (^3.1.2)
- Created OverlayService wrapper with showOverlay, closeOverlay, shareData, isActive, overlayListener, checkPermission
- Implemented 3-attempt retry with 200ms exponential backoff
- Added staleness constants: 3s dim, 10s dash, 0.5 opacity, 1s check interval
- Added GPS_GRACE_PERIOD constant (30 seconds)
2. **9e3decc** - feat(03-01): add timestamp field to OverlayMessage model
- Added timestamp field with auto-population via DateTime.now()
- Updated all factory constructors (updateDisplay, longPressClose, overlayClosed)
- Serialized timestamp as millisecondsSinceEpoch in toMap()
- Deserialized with fallback to DateTime.now() in fromMap()
- Added timestamp parameter to copyWith()
- Removed const modifiers for runtime timestamp values