diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index b02322a..da75179 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -78,10 +78,11 @@ Plans:
5. Heartbeat timer only runs when overlay active and app backgrounded
6. Overlay shows current speed and heading that matches main app display
-**Plans**: TBD (1-2 plans expected)
+**Plans**: 2 plans
Plans:
-- [ ] 03-01: [To be planned]
+- [ ] 03-01-PLAN.md — OverlayService wrapper, OverlayMessage timestamp, staleness/grace period constants
+- [ ] 03-02-PLAN.md — OverlayProvider refactor, overlay staleness UI, error snackbar surfacing
### Phase 4: GPS & Power Optimization
@@ -113,7 +114,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
|-------|----------------|--------|-----------|
| 1. Foundation | 2/2 | **COMPLETE** | 2026-02-09 |
| 2. Provider Migration | 2/2 | **COMPLETE** | 2026-02-10 |
-| 3. Overlay Refactor | 0/TBD | Not started | - |
+| 3. Overlay Refactor | 0/2 | Not started | - |
| 4. GPS & Power Optimization | 0/TBD | Not started | - |
## Notes
diff --git a/.planning/phases/03-overlay-refactor/03-01-PLAN.md b/.planning/phases/03-overlay-refactor/03-01-PLAN.md
new file mode 100644
index 0000000..0845707
--- /dev/null
+++ b/.planning/phases/03-overlay-refactor/03-01-PLAN.md
@@ -0,0 +1,208 @@
+---
+phase: 03-overlay-refactor
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - lib/services/overlay_service.dart
+ - lib/models/overlay_message.dart
+ - lib/config/overlay_constants.dart
+ - lib/config/timing_constants.dart
+ - pubspec.yaml
+autonomous: true
+
+must_haves:
+ truths:
+ - "OverlayService class encapsulates all FlutterOverlayWindow platform calls"
+ - "OverlayMessage includes timestamp field serialized as millisecondsSinceEpoch"
+ - "Staleness thresholds (3s dim, 10s dashes) and grace period (30s) defined as named constants"
+ - "retry package added to dependencies for exponential backoff"
+ artifacts:
+ - path: "lib/services/overlay_service.dart"
+ provides: "Platform channel wrapper with retry logic for show/close/shareData/isActive/overlayListener"
+ min_lines: 60
+ - path: "lib/models/overlay_message.dart"
+ provides: "Timestamp field in OverlayMessage model with toMap/fromMap serialization"
+ contains: "timestamp"
+ - path: "lib/config/overlay_constants.dart"
+ provides: "STALENESS_DIM_THRESHOLD, STALENESS_DASH_THRESHOLD, STALENESS_DIM_OPACITY constants"
+ contains: "STALENESS"
+ - path: "lib/config/timing_constants.dart"
+ provides: "GPS_GRACE_PERIOD constant (30 seconds)"
+ contains: "GPS_GRACE_PERIOD"
+ - path: "pubspec.yaml"
+ provides: "retry package dependency"
+ contains: "retry"
+ key_links:
+ - from: "lib/services/overlay_service.dart"
+ to: "flutter_overlay_window"
+ via: "import and method delegation"
+ pattern: "FlutterOverlayWindow"
+ - from: "lib/services/overlay_service.dart"
+ to: "retry"
+ via: "RetryOptions for showOverlay and closeOverlay"
+ pattern: "RetryOptions"
+ - from: "lib/models/overlay_message.dart"
+ to: "overlay_service.dart consumers"
+ via: "timestamp field in toMap/fromMap"
+ pattern: "millisecondsSinceEpoch"
+---
+
+
+Create the foundation building blocks for overlay refactor: OverlayService wrapper, timestamp-enhanced OverlayMessage, and staleness/grace period constants.
+
+Purpose: Isolate all flutter_overlay_window platform calls behind a service with retry logic and error handling, add timestamp tracking to overlay messages for staleness detection, and define the constants that Plan 02 will use for staleness UI and GPS grace period.
+
+Output: Three new/modified files providing the service layer, enhanced model, and constants that Plan 02 will integrate into OverlayProvider, overlay screen, and SpeedometerScreen.
+
+
+
+@C:\Users\SKSup\.claude/get-shit-done/workflows/execute-plan.md
+@C:\Users\SKSup\.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/03-overlay-refactor/03-CONTEXT.md
+@.planning/phases/03-overlay-refactor/03-RESEARCH.md
+@lib/services/overlay_service.dart
+@lib/models/overlay_message.dart
+@lib/config/overlay_constants.dart
+@lib/config/timing_constants.dart
+@lib/services/logger.dart
+@pubspec.yaml
+
+
+
+
+
+ Task 1: Create OverlayService and add retry dependency
+
+ lib/services/overlay_service.dart
+ lib/config/overlay_constants.dart
+ lib/config/timing_constants.dart
+ pubspec.yaml
+
+
+1. Add `retry: ^3.1.2` to pubspec.yaml under dependencies (after shared_preferences). Run `flutter pub get` to install.
+
+2. Create `lib/services/overlay_service.dart` — a service class that wraps all FlutterOverlayWindow platform calls:
+
+ - **Constructor:** No parameters needed. Create a static `RetryOptions` with maxAttempts: 3 and delayFactor: Duration(milliseconds: 200).
+
+ - **showOverlay({required int width, required int height})** → Future:
+ - Uses retry to call FlutterOverlayWindow.showOverlay with enableDrag: true, overlayTitle: "Speedometer", overlayContent: 'Speedo overlay active', flag: OverlayFlag.defaultFlag, visibility: NotificationVisibility.visibilityPublic, positionGravity: PositionGravity.none, plus width/height params.
+ - After showOverlay completes, calls FlutterOverlayWindow.isActive() to verify overlay actually opened.
+ - Returns true if verified active, false if not.
+ - On exception after retries exhausted, logs via Logger.error and returns false.
+ - retryIf should catch Exception (broad, since platform exceptions vary).
+
+ - **closeOverlay()** → Future:
+ - Uses retry to call FlutterOverlayWindow.closeOverlay().
+ - Returns true on success, false on failure after retries.
+ - Logs errors via Logger.error.
+
+ - **shareData(Map data)** → Future:
+ - Calls FlutterOverlayWindow.shareData(data).
+ - Catches exceptions and logs via Logger.warn (NOT error — data loss is expected and handled by staleness).
+ - Does NOT retry — fire-and-forget by design (user decision: forward every tick, staleness handles drops).
+
+ - **isActive()** → Future:
+ - Delegates to FlutterOverlayWindow.isActive() with try-catch.
+ - Returns false on error.
+
+ - **get overlayListener** → Stream:
+ - Returns FlutterOverlayWindow.overlayListener (direct delegation).
+
+ - **checkPermission()** → Future:
+ - Uses Permission.systemAlertWindow.isGranted from permission_handler.
+ - Returns the boolean result.
+
+ Import: flutter_overlay_window, retry, permission_handler, logger.dart.
+ Use Logger with caller tag 'OverlayService' for all log calls.
+
+3. Add staleness constants to `lib/config/overlay_constants.dart`:
+ - `static const Duration STALENESS_DIM_THRESHOLD = Duration(seconds: 3);` — time after which overlay dims speed display (user decision: 3 seconds, NOT 2)
+ - `static const Duration STALENESS_DASH_THRESHOLD = Duration(seconds: 10);` — time after which overlay shows "--" dashes
+ - `static const double STALENESS_DIM_OPACITY = 0.5;` — opacity value for dimmed stale state
+ - `static const Duration STALENESS_CHECK_INTERVAL = Duration(seconds: 1);` — how often overlay checks for staleness
+
+4. Add GPS grace period constant to `lib/config/timing_constants.dart`:
+ - `static const Duration GPS_GRACE_PERIOD = Duration(seconds: 30);` — GPS continues 30s after overlay close when backgrounded (user decision)
+
+
+Run `flutter pub get` to confirm retry dependency resolves. Run `flutter analyze` to confirm no analysis errors in new/modified files. Verify overlay_service.dart imports compile correctly.
+
+
+OverlayService exists with showOverlay, closeOverlay, shareData, isActive, overlayListener, and checkPermission methods. retry package installed. Staleness constants (3s/10s/0.5 opacity/1s check) and GPS_GRACE_PERIOD (30s) defined in their respective config files. No analysis errors.
+
+
+
+
+ Task 2: Add timestamp field to OverlayMessage model
+ lib/models/overlay_message.dart
+
+Extend the existing OverlayMessage class to include a timestamp field:
+
+1. Add `final DateTime timestamp;` field to the class.
+
+2. Update the **primary constructor** to include `required this.timestamp` as a required parameter. Remove `const` from the constructor since DateTime.now() is not a compile-time constant.
+
+3. Update **OverlayMessage.updateDisplay** factory:
+ - Add `timestamp: DateTime.now()` in the returned OverlayMessage.
+ - No changes to parameters (timestamp is auto-generated, not passed in).
+
+4. Update **OverlayMessage.longPressClose** factory:
+ - Remove `const` from the returned value.
+ - Add `timestamp: DateTime.now()`.
+
+5. Update **OverlayMessage.overlayClosed** factory:
+ - Remove `const` from the returned value.
+ - Add `timestamp: DateTime.now()`.
+
+6. Update **toMap()**:
+ - Add `map['timestamp'] = timestamp.millisecondsSinceEpoch;` (always include — timestamp is never null).
+
+7. Update **fromMap()**:
+ - Parse timestamp: `timestamp: DateTime.fromMillisecondsSinceEpoch(map['timestamp'] as int? ?? DateTime.now().millisecondsSinceEpoch)`.
+ - Fallback to DateTime.now() if timestamp missing (backward compatibility with any in-flight messages during upgrade).
+
+8. Update **copyWith()**:
+ - Add `DateTime? timestamp` parameter.
+ - Add `timestamp: timestamp ?? this.timestamp` in the returned OverlayMessage.
+
+Do NOT change any existing field names, types, or factory parameter signatures. The timestamp is purely additive.
+
+
+Run `flutter analyze` to confirm no analysis errors. Verify OverlayMessage can be constructed with all three factories. Verify toMap() includes 'timestamp' key. Verify fromMap() parses timestamp correctly and handles missing timestamp gracefully.
+
+
+OverlayMessage has timestamp field, auto-populated by all factory constructors via DateTime.now(), serialized as int via millisecondsSinceEpoch in toMap(), parsed with fallback in fromMap(), and included in copyWith(). No breaking changes to existing API.
+
+
+
+
+
+
+1. `flutter pub get` succeeds with retry dependency added
+2. `flutter analyze` shows no new errors or warnings (existing SCREAMING_SNAKE info-level warnings are expected)
+3. lib/services/overlay_service.dart exists and contains OverlayService class
+4. lib/models/overlay_message.dart contains `timestamp` field
+5. lib/config/overlay_constants.dart contains STALENESS_DIM_THRESHOLD, STALENESS_DASH_THRESHOLD, STALENESS_DIM_OPACITY, STALENESS_CHECK_INTERVAL
+6. lib/config/timing_constants.dart contains GPS_GRACE_PERIOD
+7. pubspec.yaml contains retry dependency
+
+
+
+- OverlayService wraps all FlutterOverlayWindow calls with retry for show/close and error handling for all methods
+- OverlayMessage timestamp field serializes/deserializes correctly
+- All staleness and grace period constants defined with user-decision values (3s dim, 10s dashes, 30s grace)
+- No compilation errors, no breaking changes to existing API
+
+
+
diff --git a/.planning/phases/03-overlay-refactor/03-02-PLAN.md b/.planning/phases/03-overlay-refactor/03-02-PLAN.md
new file mode 100644
index 0000000..4041b5d
--- /dev/null
+++ b/.planning/phases/03-overlay-refactor/03-02-PLAN.md
@@ -0,0 +1,287 @@
+---
+phase: 03-overlay-refactor
+plan: 02
+type: execute
+wave: 2
+depends_on: ["03-01"]
+files_modified:
+ - lib/providers/overlay_provider.dart
+ - lib/screens/overlay_screen.dart
+ - lib/screens/speedometer_screen.dart
+ - lib/main.dart
+autonomous: true
+
+must_haves:
+ truths:
+ - "Overlay receives continuous GPS updates without going stale (every tick forwarded)"
+ - "Overlay dims speed display after 3 seconds of no data, shows '--' after 10 seconds"
+ - "Recovery from staleness is immediate snap-back (no animation)"
+ - "Overlay creation failures surface to user via auto-dismissing snackbar"
+ - "Permission errors show snackbar with 'Settings' action button that opens app settings"
+ - "OverlayProvider verifies overlay is actually active before setting isOverlayActive flag"
+ - "Heartbeat timer only runs when overlay active AND app backgrounded"
+ - "GPS continues for 30 seconds after overlay close when app is backgrounded"
+ - "Theme and unit settings sync in real-time from main app to overlay"
+ artifacts:
+ - path: "lib/providers/overlay_provider.dart"
+ provides: "OverlayService integration, error callback, verified overlay creation, GPS grace period"
+ contains: "OverlayService"
+ - path: "lib/screens/overlay_screen.dart"
+ provides: "Staleness detection timer, two-stage dim/dash UI, timestamp-based freshness"
+ contains: "STALENESS_DIM_THRESHOLD"
+ - path: "lib/screens/speedometer_screen.dart"
+ provides: "Error snackbar surfacing with permission Settings action"
+ contains: "SnackBar"
+ - path: "lib/main.dart"
+ provides: "OverlayService injected into provider tree"
+ contains: "OverlayService"
+ key_links:
+ - from: "lib/providers/overlay_provider.dart"
+ to: "lib/services/overlay_service.dart"
+ via: "Constructor injection, delegates show/close/share"
+ pattern: "_overlayService"
+ - from: "lib/providers/overlay_provider.dart"
+ to: "lib/screens/speedometer_screen.dart"
+ via: "onError callback for snackbar surfacing"
+ pattern: "onError"
+ - from: "lib/screens/overlay_screen.dart"
+ to: "lib/models/overlay_message.dart"
+ via: "Reads message.timestamp for staleness calculation"
+ pattern: "timestamp"
+ - from: "lib/screens/overlay_screen.dart"
+ to: "lib/config/overlay_constants.dart"
+ via: "Uses STALENESS_DIM_THRESHOLD and STALENESS_DASH_THRESHOLD"
+ pattern: "STALENESS"
+ - from: "lib/main.dart"
+ to: "lib/services/overlay_service.dart"
+ via: "Provider registration in MultiProvider"
+ pattern: "OverlayService"
+---
+
+
+Wire the overlay refactor: integrate OverlayService into OverlayProvider with error callbacks, add staleness detection to the overlay screen, and surface errors via snackbars in SpeedometerScreen.
+
+Purpose: This is the integration plan that makes the overlay communication reliable. OverlayProvider delegates to OverlayService (retry + verification), overlay screen detects stale data visually (dim at 3s, dashes at 10s), and SpeedometerScreen shows errors to the user instead of swallowing them silently.
+
+Output: Fully wired overlay refactor satisfying all OVRL-01 through OVRL-06 requirements with user-decided thresholds and behaviors.
+
+
+
+@C:\Users\SKSup\.claude/get-shit-done/workflows/execute-plan.md
+@C:\Users\SKSup\.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/03-overlay-refactor/03-CONTEXT.md
+@.planning/phases/03-overlay-refactor/03-RESEARCH.md
+@.planning/phases/03-overlay-refactor/03-01-SUMMARY.md
+@lib/providers/overlay_provider.dart
+@lib/screens/overlay_screen.dart
+@lib/screens/speedometer_screen.dart
+@lib/main.dart
+@lib/services/overlay_service.dart
+@lib/models/overlay_message.dart
+@lib/config/overlay_constants.dart
+@lib/config/timing_constants.dart
+
+
+
+
+
+ Task 1: Refactor OverlayProvider to use OverlayService with error callback and GPS grace period
+
+ lib/providers/overlay_provider.dart
+ lib/main.dart
+
+
+**OverlayProvider refactor (lib/providers/overlay_provider.dart):**
+
+1. **Add OverlayService dependency:**
+ - Add `final OverlayService _overlayService;` field.
+ - Update constructor: `OverlayProvider(this._overlayService);`
+ - Import overlay_service.dart.
+
+2. **Add error callback mechanism:**
+ - Define enum at top of file (or in separate model if preferred):
+ ```dart
+ enum OverlayError { permission, creationFailed, communicationDegraded }
+ ```
+ - Add field: `void Function(OverlayError)? onError;`
+ - This is set by SpeedometerScreen after provider is available (not constructor param — allows late binding since screen needs BuildContext for snackbar).
+
+3. **Refactor showOverlay():**
+ - BEFORE calling showOverlay, check permission via `_overlayService.checkPermission()`. If denied, call `onError?.call(OverlayError.permission)` and return early.
+ - Replace direct FlutterOverlayWindow.showOverlay call with `_overlayService.showOverlay(width: ..., height: ...)`.
+ - Check the return value (bool). If false, call `onError?.call(OverlayError.creationFailed)` and return early.
+ - ONLY set `_isOverlayActive = true` AFTER showOverlay returns true (verified by OverlayService.isActive check inside).
+ - Keep the initial data push (shareData via _overlayService.shareData instead of direct FlutterOverlayWindow.shareData).
+ - Keep _startListeningToOverlayMessages() and _startOverlayStatusCheck() calls.
+
+4. **Refactor closeOverlay():**
+ - Replace direct FlutterOverlayWindow.closeOverlay with `_overlayService.closeOverlay()`.
+ - Keep existing _handleOverlayClose() call.
+
+5. **Refactor _pushDataToOverlay():**
+ - Replace direct FlutterOverlayWindow.shareData with `_overlayService.shareData(message.toMap())`.
+ - The OverlayMessage.updateDisplay factory already auto-stamps DateTime.now() (from Plan 01 Task 2).
+
+6. **Refactor _startListeningToOverlayMessages():**
+ - Replace `FlutterOverlayWindow.overlayListener` with `_overlayService.overlayListener`.
+
+7. **Add GPS grace period to _handleOverlayClose():**
+ - Add `Timer? _gracePeriodTimer;` field.
+ - In _handleOverlayClose: After setting _isOverlayActive = false, start a 30-second grace period timer using `TimingConfig.GPS_GRACE_PERIOD`.
+ - The grace period timer just logs when it expires: `Logger.info('GPS grace period expired', 'OverlayProvider')`.
+ - Cancel grace period in dispose().
+ - Note: Actual GPS subscription lifecycle management is Phase 4 scope. The timer is created here as infrastructure but does not yet control GPS subscription start/stop. This avoids scope creep.
+
+8. **Cancel grace period when overlay reopens:**
+ - In showOverlay(), cancel `_gracePeriodTimer` if running (user reopened overlay within grace period).
+
+9. **Refactor _startOverlayStatusCheck():**
+ - Replace `FlutterOverlayWindow.isActive()` with `_overlayService.isActive()`.
+
+**main.dart update:**
+
+10. Update MultiProvider in main.dart:
+ - Add `Provider(create: (_) => OverlayService()),` BEFORE the OverlayProvider.
+ - Update OverlayProvider creation to pass OverlayService:
+ ```dart
+ ChangeNotifierProxyProvider2(
+ create: (context) => OverlayProvider(context.read()),
+ update: (_, gpsManager, settings, overlay) => overlay!
+ ..updateDependencies(
+ gpsManager: gpsManager,
+ currentUnit: settings.currentUnit,
+ currentThemeIndex: settings.currentThemeIndex,
+ ),
+ ),
+ ```
+ - Import overlay_service.dart.
+
+
+Run `flutter analyze` to confirm no errors. Verify OverlayProvider no longer directly imports or calls FlutterOverlayWindow (all calls go through _overlayService). Verify showOverlay checks permission, verifies creation success, and only sets _isOverlayActive after verification. Verify grace period timer is created and cancelled properly.
+
+
+OverlayProvider delegates all platform calls to OverlayService. Error callback mechanism exists for permission and creation failures. Overlay creation is verified before setting active flag. GPS grace period timer (30s) starts on overlay close. OverlayService is registered in MultiProvider and injected into OverlayProvider. No direct FlutterOverlayWindow usage remains in OverlayProvider.
+
+
+
+
+ Task 2: Add staleness detection to overlay screen and error surfacing to SpeedometerScreen
+
+ lib/screens/overlay_screen.dart
+ lib/screens/speedometer_screen.dart
+
+
+**Overlay screen staleness (lib/screens/overlay_screen.dart):**
+
+1. **Add staleness monitoring:**
+ - Import overlay_constants.dart (for STALENESS_DIM_THRESHOLD, STALENESS_DASH_THRESHOLD, STALENESS_DIM_OPACITY, STALENESS_CHECK_INTERVAL).
+ - Add field: `DateTime _lastUpdateTime = DateTime.now();`
+ - Add field: `Timer? _stalenessCheckTimer;`
+
+2. **Start staleness check timer in initState():**
+ - After existing _listenToMainAppMessages() call, add _startStalenessMonitoring().
+ - _startStalenessMonitoring() creates a Timer.periodic with OverlayConfig.STALENESS_CHECK_INTERVAL (1 second).
+ - Timer callback checks `if (!mounted) return;`, then:
+ - Calculates age: `DateTime.now().difference(_lastUpdateTime)`
+ - If age > STALENESS_DASH_THRESHOLD (10s) AND _speedText is not already '--': `setState(() => _speedText = '--');`
+ - The dimming (3s threshold) is handled in build() via opacity, no setState needed for it.
+
+3. **Update _listenToMainAppMessages() to track timestamp:**
+ - When processing 'updateDisplay' messages, extract timestamp from message:
+ ```dart
+ _lastUpdateTime = message.timestamp;
+ ```
+ - This uses the timestamp from OverlayMessage (added in Plan 01 Task 2).
+ - On receiving fresh data, _speedText gets the actual speed value — this is the "immediate snap-back" (no animation, just setState with new value).
+
+4. **Update build() for staleness dimming:**
+ - At the start of build(), calculate:
+ ```dart
+ final age = DateTime.now().difference(_lastUpdateTime);
+ final isStale = age >= OverlayConfig.STALENESS_DIM_THRESHOLD;
+ final staleOpacity = isStale ? OverlayConfig.STALENESS_DIM_OPACITY : 1.0;
+ ```
+ - Apply staleOpacity to the speed text color: change `color: currentTheme.speedText` to `color: currentTheme.speedText.withValues(alpha: staleOpacity)` for the speed Text widget.
+ - Apply staleOpacity to the unit text color similarly.
+ - Apply staleOpacity to the heading icon and heading text colors.
+ - Do NOT use AnimatedOpacity — user decision is "immediate snap-back, no fade-in animation". Direct color alpha is correct.
+
+5. **Cancel staleness timer in dispose():**
+ - Add `_stalenessCheckTimer?.cancel();` before super.dispose().
+
+**SpeedometerScreen error surfacing (lib/screens/speedometer_screen.dart):**
+
+6. **Register error callback on OverlayProvider:**
+ - Import permission_handler (for openAppSettings).
+ - In initState() addPostFrameCallback, after _initializeGpsManager(), set up the error callback:
+ ```dart
+ context.read().onError = _handleOverlayError;
+ ```
+
+7. **Implement _handleOverlayError(OverlayError error):**
+ - Check `if (!mounted) return;`
+ - Determine message and action based on error type:
+ - `OverlayError.permission`: message = 'Overlay permission required', add SnackBarAction with label 'Settings' that calls `openAppSettings()` (from permission_handler).
+ - `OverlayError.creationFailed`: message = 'Failed to create overlay window', no action button.
+ - `OverlayError.communicationDegraded`: message = 'Overlay communication degraded', no action button.
+ - Show snackbar:
+ ```dart
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(message),
+ action: action,
+ duration: const Duration(seconds: 4),
+ ),
+ );
+ ```
+
+8. **Clean up error callback in dispose():**
+ - Before super.dispose(), null out the callback:
+ ```dart
+ context.read().onError = null;
+ ```
+ - Use a try-catch since the provider might already be disposed.
+
+
+Run `flutter analyze` to confirm no errors. Verify overlay_screen.dart has staleness timer that dims at 3s and shows dashes at 10s. Verify speedometer_screen.dart registers error callback and shows snackbar with Settings action for permission errors. Verify no AnimatedOpacity is used (immediate snap-back per user decision).
+
+
+Overlay screen has staleness detection: dims all display elements after 3s of no GPS data (0.5 opacity), shows "--" after 10s, snaps back immediately when fresh data arrives. SpeedometerScreen surfaces overlay errors via snackbar with "Settings" action button for permission issues. All staleness thresholds use constants from OverlayConfig. Error callback mechanism connects OverlayProvider (no BuildContext) to SpeedometerScreen (has BuildContext).
+
+
+
+
+
+
+1. `flutter analyze` shows no new errors
+2. OverlayProvider uses OverlayService for ALL platform calls (no direct FlutterOverlayWindow usage)
+3. OverlayProvider checks permission before showing overlay
+4. OverlayProvider verifies overlay creation via isActive() before setting flag
+5. OverlayProvider has GPS grace period timer (30s)
+6. Overlay screen has staleness timer checking every 1 second
+7. Overlay screen dims at 3 seconds (opacity 0.5), shows "--" at 10 seconds
+8. Overlay screen snap-back is immediate (no AnimatedOpacity)
+9. SpeedometerScreen shows snackbar on overlay creation failure
+10. SpeedometerScreen shows snackbar with "Settings" action on permission error
+11. main.dart registers OverlayService in provider tree
+12. OverlayMessage timestamps flow from main app to overlay screen
+
+
+
+- OVRL-01: Overlay receives continuous debounced data updates via GPS stream forwarding through OverlayService.shareData
+- OVRL-02: Overlay errors surfaced to user via snackbar (permission errors include "Settings" action)
+- OVRL-03: Overlay creation verified via isActive() before setting isOverlayActive flag
+- OVRL-04: Messages include timestamp for staleness detection (millisecondsSinceEpoch)
+- OVRL-05: Overlay displays dim at 3s, "--" at 10s when data is stale (user decision: 3s not 2s)
+- OVRL-06: OverlayService encapsulates all FlutterOverlayWindow platform calls with retry logic
+
+
+