docs(03): complete overlay refactor phase

Phase 3 verified: 9/9 must-haves, 6/6 OVRL requirements satisfied.
OverlayService wrapper, staleness detection, error surfacing all verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-02-11 19:35:15 +09:00
parent 9fb91f6e3f
commit d0427219a2
3 changed files with 141 additions and 20 deletions
+13 -13
View File
@@ -30,12 +30,12 @@ Requirements for the restructure milestone. Each maps to roadmap phases.
### Overlay reliability ### Overlay reliability
- [ ] **OVRL-01**: Overlay receives continuous debounced data updates (not one-shot push + heartbeat) - [x] **OVRL-01**: Overlay receives continuous debounced data updates (not one-shot push + heartbeat)
- [ ] **OVRL-02**: Overlay errors surfaced to user via snackbar/toast (no silent catch blocks) - [x] **OVRL-02**: Overlay errors surfaced to user via snackbar/toast (no silent catch blocks)
- [ ] **OVRL-03**: Overlay creation verified before setting `isOverlayActive` flag - [x] **OVRL-03**: Overlay creation verified before setting `isOverlayActive` flag
- [ ] **OVRL-04**: Messages include timestamp for staleness detection in overlay - [x] **OVRL-04**: Messages include timestamp for staleness detection in overlay
- [ ] **OVRL-05**: Overlay displays "--" when data is stale (no updates for >2 seconds) - [x] **OVRL-05**: Overlay displays "--" when data is stale (dims at 3s, dashes at 10s per user decision)
- [ ] **OVRL-06**: `OverlayService` encapsulates all `FlutterOverlayWindow` platform calls - [x] **OVRL-06**: `OverlayService` encapsulates all `FlutterOverlayWindow` platform calls
### GPS performance ### GPS performance
@@ -113,12 +113,12 @@ Deferred to future release. Tracked but not in current roadmap.
| STATE-05 | Phase 2 | Complete | | STATE-05 | Phase 2 | Complete |
| STATE-06 | Phase 2 | Complete | | STATE-06 | Phase 2 | Complete |
| STATE-07 | Phase 2 | Complete | | STATE-07 | Phase 2 | Complete |
| OVRL-01 | Phase 3 | Pending | | OVRL-01 | Phase 3 | Complete |
| OVRL-02 | Phase 3 | Pending | | OVRL-02 | Phase 3 | Complete |
| OVRL-03 | Phase 3 | Pending | | OVRL-03 | Phase 3 | Complete |
| OVRL-04 | Phase 3 | Pending | | OVRL-04 | Phase 3 | Complete |
| OVRL-05 | Phase 3 | Pending | | OVRL-05 | Phase 3 | Complete |
| OVRL-06 | Phase 3 | Pending | | OVRL-06 | Phase 3 | Complete |
| GPS-01 | Phase 4 | Pending | | GPS-01 | Phase 4 | Pending |
| GPS-02 | Phase 4 | Pending | | GPS-02 | Phase 4 | Pending |
| GPS-03 | Phase 4 | Pending | | GPS-03 | Phase 4 | Pending |
@@ -140,4 +140,4 @@ Deferred to future release. Tracked but not in current roadmap.
--- ---
*Requirements defined: 2026-02-09* *Requirements defined: 2026-02-09*
*Last updated: 2026-02-10 (Phase 2 requirements marked Complete)* *Last updated: 2026-02-11 (Phase 3 requirements marked Complete)*
+7 -7
View File
@@ -13,8 +13,8 @@ This roadmap transforms a 1,133-line monolithic Flutter app into a maintainable
Decimal phases appear between their surrounding integers in numeric order. Decimal phases appear between their surrounding integers in numeric order.
- [x] **Phase 1: Foundation** - Establish directory structure and extract models (COMPLETE 2026-02-09) - [x] **Phase 1: Foundation** - Establish directory structure and extract models (COMPLETE 2026-02-09)
- [ ] **Phase 2: Provider Migration** - Replace setState with proper state management - [x] **Phase 2: Provider Migration** - Replace setState with proper state management (COMPLETE 2026-02-10)
- [ ] **Phase 3: Overlay Refactor** - Fix overlay communication reliability - [x] **Phase 3: Overlay Refactor** - Fix overlay communication reliability (COMPLETE 2026-02-11)
- [ ] **Phase 4: GPS & Power Optimization** - Implement adaptive precision and lifecycle management - [ ] **Phase 4: GPS & Power Optimization** - Implement adaptive precision and lifecycle management
## Phase Details ## Phase Details
@@ -72,7 +72,7 @@ Plans:
**Success Criteria** (what must be TRUE): **Success Criteria** (what must be TRUE):
1. Overlay receives continuous GPS updates without going stale 1. Overlay receives continuous GPS updates without going stale
2. Overlay displays "--" when data is more than 2 seconds old 2. Overlay dims display after 3 seconds of no data, shows "--" after 10 seconds
3. Overlay creation failures surface to user via snackbar instead of silent failure 3. Overlay creation failures surface to user via snackbar instead of silent failure
4. OverlayService encapsulates all flutter_overlay_window platform calls 4. OverlayService encapsulates all flutter_overlay_window platform calls
5. Heartbeat timer only runs when overlay active and app backgrounded 5. Heartbeat timer only runs when overlay active and app backgrounded
@@ -81,8 +81,8 @@ Plans:
**Plans**: 2 plans **Plans**: 2 plans
Plans: Plans:
- [ ] 03-01-PLAN.md — OverlayService wrapper, OverlayMessage timestamp, staleness/grace period constants - [x] 03-01-PLAN.md — OverlayService wrapper, OverlayMessage timestamp, staleness/grace period constants (4.0 min)
- [ ] 03-02-PLAN.md — OverlayProvider refactor, overlay staleness UI, error snackbar surfacing - [x] 03-02-PLAN.md — OverlayProvider refactor, overlay staleness UI, error snackbar surfacing (4.25 min)
### Phase 4: GPS & Power Optimization ### Phase 4: GPS & Power Optimization
@@ -114,7 +114,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
|-------|----------------|--------|-----------| |-------|----------------|--------|-----------|
| 1. Foundation | 2/2 | **COMPLETE** | 2026-02-09 | | 1. Foundation | 2/2 | **COMPLETE** | 2026-02-09 |
| 2. Provider Migration | 2/2 | **COMPLETE** | 2026-02-10 | | 2. Provider Migration | 2/2 | **COMPLETE** | 2026-02-10 |
| 3. Overlay Refactor | 0/2 | Not started | - | | 3. Overlay Refactor | 2/2 | **COMPLETE** | 2026-02-11 |
| 4. GPS & Power Optimization | 0/TBD | Not started | - | | 4. GPS & Power Optimization | 0/TBD | Not started | - |
## Notes ## Notes
@@ -130,4 +130,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
--- ---
*Roadmap created: 2026-02-09* *Roadmap created: 2026-02-09*
*Last updated: 2026-02-10 (Phase 2 complete - 11.5 min total, 2/2 plans)* *Last updated: 2026-02-11 (Phase 3 complete - 8.25 min total, 2/2 plans)*
@@ -0,0 +1,121 @@
---
phase: 03-overlay-refactor
verified: 2026-02-11T10:29:03Z
status: passed
score: 9/9 must-haves verified
re_verification: false
---
# Phase 3: Overlay Refactor Verification Report
**Phase Goal:** Fix overlay data staleness and communication reliability issues
**Verified:** 2026-02-11T10:29:03Z
**Status:** PASSED
**Re-verification:** No - initial verification
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Overlay receives continuous GPS updates without going stale | VERIFIED | OverlayProvider subscribes to GpsDataManager.dataStream and forwards every GPS tick via _pushDataToOverlay (lines 55-59) |
| 2 | Overlay displays dashes when data is more than 10 seconds old (3s dim threshold) | VERIFIED | Staleness timer checks every 1s shows dashes after STALENESS_DASH_THRESHOLD (10s) dims at STALENESS_DIM_THRESHOLD (3s) using opacity |
| 3 | Overlay creation failures surface to user via snackbar | VERIFIED | OverlayProvider calls onError callback on permission denial (line 82) and creation failure (line 112) SpeedometerScreen shows SnackBar (lines 91-97) |
| 4 | OverlayService encapsulates all flutter_overlay_window platform calls | VERIFIED | All platform calls isolated in OverlayService OverlayProvider has zero direct FlutterOverlayWindow usage |
| 5 | Heartbeat timer only runs when overlay active AND app backgrounded | VERIFIED | Timer checks both _isInBackground AND overlay.isOverlayActive (speedometer_screen.dart line 126) |
| 6 | Overlay shows current speed and heading that matches main app display | VERIFIED | OverlayProvider pushes OverlayMessage.updateDisplay with speedText heading unitIndex themeIndex on every GPS tick and settings change |
| 7 | Permission errors show snackbar with Settings action button | VERIFIED | _handleOverlayError creates SnackBarAction for OverlayError.permission that calls openAppSettings() (lines 78-81) |
| 8 | Recovery from staleness is immediate snap-back (no animation) | VERIFIED | Staleness opacity applied directly via color.withValues(alpha:) no AnimatedOpacity used (overlay_screen.dart lines 173 188 216 229) |
| 9 | GPS grace period timer (30s) created on overlay close | VERIFIED | _gracePeriodTimer starts on _handleOverlayClose (line 251) cancelled on showOverlay (line 74) |
**Score:** 9/9 truths verified
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| lib/services/overlay_service.dart | Platform channel wrapper with retry logic | VERIFIED | 97 lines methods: showOverlay closeOverlay shareData isActive overlayListener checkPermission. RetryOptions with 3 attempts and 200ms delay |
| lib/models/overlay_message.dart | Timestamp field with serialization | VERIFIED | timestamp field (line 13) auto-populated in factories (lines 49 57 65) serialized as millisecondsSinceEpoch (line 80) deserialized with fallback (lines 96-98) |
| lib/config/overlay_constants.dart | Staleness thresholds (3s dim 10s dash) | VERIFIED | STALENESS_DIM_THRESHOLD = 3s (line 39) STALENESS_DASH_THRESHOLD = 10s (line 42) STALENESS_DIM_OPACITY = 0.5 (line 45) STALENESS_CHECK_INTERVAL = 1s (line 48) |
| lib/config/timing_constants.dart | GPS_GRACE_PERIOD constant | VERIFIED | GPS_GRACE_PERIOD = 30s (line 18) |
| lib/providers/overlay_provider.dart | OverlayService integration with error callback | VERIFIED | OverlayService dependency (line 16) onError callback field (line 34) permission checks (line 79) verified creation (lines 105-114) grace period timer (lines 74 250-254) |
| lib/screens/overlay_screen.dart | Staleness detection timer and UI | VERIFIED | _stalenessCheckTimer (line 27) _lastUpdateTime tracking (line 83) staleness monitoring (lines 105-116) opacity calculation (lines 125-127) opacity applied to all display elements |
| lib/screens/speedometer_screen.dart | Error snackbar surfacing | VERIFIED | onError callback registration (line 35) _handleOverlayError method (lines 69-98) SnackBar with Settings action for permission errors |
| lib/main.dart | OverlayService in provider tree | VERIFIED | OverlayService registered (line 20) injected into OverlayProvider constructor (line 28) |
| pubspec.yaml | retry package dependency | VERIFIED | retry 3.1.2 present |
### Key Link Verification
| From | To | Via | Status | Details |
|------|-----|-----|--------|---------|
| OverlayProvider | OverlayService | Constructor injection all platform calls delegated | WIRED | _overlayService field (line 16) used in showOverlay (lines 79 94 105) closeOverlay (line 133) shareData (line 167) overlayListener (line 180) isActive (line 221) |
| OverlayProvider | SpeedometerScreen | onError callback for snackbar surfacing | WIRED | onError field set by SpeedometerScreen (speedometer_screen.dart line 35) called on permission/creation errors (overlay_provider.dart lines 82 112) |
| OverlayScreen | OverlayMessage.timestamp | Reads timestamp for staleness calculation | WIRED | _lastUpdateTime = message.timestamp (line 83) age calculated in staleness check (line 109) and build (line 125) |
| OverlayScreen | OverlayConfig constants | Uses staleness thresholds for visual feedback | WIRED | STALENESS_CHECK_INTERVAL (line 106) STALENESS_DASH_THRESHOLD (line 110) STALENESS_DIM_THRESHOLD (line 126) STALENESS_DIM_OPACITY (line 127) |
| main.dart | OverlayService | Provider registration and injection | WIRED | Provider OverlayService created (line 20) passed to OverlayProvider constructor (line 28) |
| OverlayService | flutter_overlay_window | Platform channel delegation with retry | WIRED | showOverlay (lines 24 38) closeOverlay (line 57) shareData (line 71) isActive (line 81) overlayListener (line 90) |
| OverlayService | retry package | RetryOptions for showOverlay closeOverlay | WIRED | RetryOptions configured (lines 10-12) used in showOverlay (line 22) and closeOverlay (line 55) |
| GpsDataManager | OverlayProvider | Stream subscription for continuous updates | WIRED | _gpsSubscription listens to dataStream (line 55) calls _pushDataToOverlay on each tick (line 57) |
### Requirements Coverage
| Requirement | Status | Blocking Issue |
|-------------|--------|----------------|
| OVRL-01: Continuous debounced data updates | SATISFIED | GPS stream forwarded on every tick via _gpsSubscription.listen to _pushDataToOverlay |
| OVRL-02: Overlay errors surfaced to user | SATISFIED | onError callback mechanism connects OverlayProvider to SpeedometerScreen for snackbar display |
| OVRL-03: Overlay creation verified | SATISFIED | showOverlay checks return value from OverlayService (which calls isActive after platform showOverlay) |
| OVRL-04: Messages include timestamp | SATISFIED | OverlayMessage.timestamp field auto-populated and serialized as millisecondsSinceEpoch |
| OVRL-05: Overlay displays dashes when stale | SATISFIED | Staleness timer shows dashes after 10s (STALENESS_DASH_THRESHOLD) dims at 3s (STALENESS_DIM_THRESHOLD) |
| OVRL-06: OverlayService encapsulates platform calls | SATISFIED | All FlutterOverlayWindow calls isolated in OverlayService zero direct usage in OverlayProvider |
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| lib/screens/overlay_screen.dart | 73 | Direct FlutterOverlayWindow.overlayListener usage in overlay isolate | Info | Expected - overlay screen runs in separate isolate and must use direct platform channel for receiving messages |
| lib/screens/overlay_screen.dart | 257 268 | Direct FlutterOverlayWindow calls in long press handler | Info | Expected - overlay screen cannot use OverlayService (separate isolate) must use direct platform channel |
**No blocker anti-patterns found.**
### Human Verification Required
**None required.** All success criteria are structurally verifiable through code analysis.
Optional manual testing to confirm runtime behavior:
1. Launch overlay - verify it appears and shows current speed/heading
2. Close main app to background - verify overlay continues receiving GPS updates
3. Wait 3 seconds without GPS movement - verify overlay dims speed display
4. Wait 10 seconds without GPS movement - verify overlay shows dashes
5. Move to trigger GPS update - verify overlay immediately shows new speed (no fade animation)
6. Deny overlay permission - verify snackbar appears with Settings button
7. Press Settings button - verify app settings page opens
8. Close overlay while app backgrounded - verify GPS continues for 30 seconds (grace period)
## Summary
**Phase 3 goal ACHIEVED.**
All 6 OVRL requirements satisfied:
- OVRL-01: Continuous GPS updates via stream subscription
- OVRL-02: Error surfacing via onError callback to SnackBar
- OVRL-03: Verified overlay creation via isActive check
- OVRL-04: Timestamp field in OverlayMessage
- OVRL-05: Two-stage staleness (3s dim 10s dashes)
- OVRL-06: OverlayService encapsulates all platform calls
**Key improvements delivered:**
1. Overlay communication is reliable (retry logic + verification)
2. Staleness detection provides clear visual feedback
3. Errors surface to user with actionable guidance
4. GPS grace period infrastructure ready for Phase 4
5. Clean separation of concerns (OverlayService isolates platform code)
**No gaps no blockers ready to proceed to Phase 4.**
**NOTE:** ROADMAP.md incorrectly states "2 seconds" for success criterion #2. The user explicitly decided on 3 seconds in CONTEXT.md and plan must-haves. Code correctly implements 3s dim / 10s dash thresholds per user decision.
---
*Verified: 2026-02-11T10:29:03Z*
*Verifier: Claude (gsd-verifier)*