Fix #120: Stale Data Indicator & Network Connectivity Resilience#127
Fix #120: Stale Data Indicator & Network Connectivity Resilience#127eneshenderson wants to merge 2 commits into
Conversation
michaeljolley
left a comment
There was a problem hiding this comment.
Squad review — mostly good, two issues to address
What this PR does well ✅
- Stale indicator logic (
MarkAsStaleIfNeeded) — clean, correct: only triggers after 15m since last success, guards against double-prefixing, skips error/loading states - Network resilience —
NetworkChange.NetworkAvailabilityChanged+NetworkAddressChangedtriggers immediate retry after connectivity restores. Smart: only fires if last attempt failed. - Timer rethink — 1-minute tick with interval gating is better than a long-interval timer for staleness detection
SyncDockItem()extraction — good refactor, DRY- Test coverage — structural tests verify the fields and method exist
- Dispose cleanup — unsubscribes both network events ✅
Blocking Issues
1. Hardcoded 15-minute stale threshold ignores per-type cache TTLs
if (age.TotalMinutes >= 15)We just merged PR #119 with per-type cache expiration: Current=15m, Hourly=45m, Forecast=180m. The stale indicator should use _settings.UpdateIntervalMinutes (the user's configured refresh interval) rather than a hardcoded 15. If the user sets a 3-hour update interval, data is expected to be 3 hours old — marking it stale at 15m would produce false positives.
Fix: Replace >= 15 with >= _settings.UpdateIntervalMinutes (or _settings.UpdateIntervalMinutes * 1.5 for a grace period).
2. Stale prefix accumulates over time with no reset
MarkAsStaleIfNeeded adds "⚠ " to Subtitle, but after a successful fetch the subtitle is overwritten entirely (good). However, if the success path only sets Title without touching Subtitle (the "location" DockBandSubtitle mode sets Subtitle = _location.DisplayName), then on the next stale tick the prefix gets checked correctly (!Subtitle.StartsWith("⚠ ")) — this is fine.
BUT: there's a NullReferenceException risk — Subtitle can be null in theory (it's set in the constructor, but ListItem.Subtitle is nullable). Add a null check:
if (Subtitle != null && !Subtitle.StartsWith("⚠ ", StringComparison.Ordinal))Wait — you already have Subtitle != null. ✅ Ignore this one.
Revised: only blocking issue #1 (hardcoded threshold).
Non-blocking Notes
-
DockItemicon removal from success path — MovingdockCommandItem.Icon = IconintoSyncDockItem()is correct BUTSyncDockItem()is only called at the end ofUpdateWeatherAsync(success case) and in error paths. In the original code, the icon was set immediately after updating Title. Now it's set after the forecast fetch too. Fine in practice — just noting the shift. -
Timer interval change from configurable to 60s — The 1-minute tick is appropriate for stale detection, but the original code respected
UpdateIntervalMinutes * 60 * 1000. Since the timer now gates ontimeSinceLastUpdate.TotalMinutes >= intervalMinutes, this is functionally equivalent. Just worth a comment explaining why the timer is always 60s. -
⚠️vs⚠— Error states use the emoji version⚠️(with variation selector) while stale uses plain⚠. Consider making them consistent — either both emoji or both plain.
Once the hardcoded 15-minute threshold is replaced with _settings.UpdateIntervalMinutes, this is ready to approve.
michaeljolley
left a comment
There was a problem hiding this comment.
Murdock QA review — requesting changes
Blocking Issue
Hardcoded 15-minute stale threshold (echoing team lead's block)
Confirmed in MarkAsStaleIfNeeded:
if (age.TotalMinutes >= 15)This conflicts with the canonical TTL values from PR #119 (Current=15m, Hourly=45m, DailyForecast=180m) and ignores the user's own UpdateIntervalMinutes setting. A user on a 3-hour update interval would see false stale warnings every 15 minutes. Fix: use _settings.UpdateIntervalMinutes as the threshold (or apply a grace multiplier like * 1.5 to tolerate brief network hiccups).
Test Coverage Gaps (blocking)
The new structural tests confirm the fields and method exist — that's necessary but not sufficient. The staleness logic itself has no behavioral coverage:
- No test for the
>= thresholdboundary — a test with a mocked/injected clock showing data aged exactly at and just underUpdateIntervalMinuteswould prevent future regressions (including the current hardcoded-15 bug, which a behavioral test would have caught). - No test for the network recovery path —
OnNetworkAvailabilityChangedandOnNetworkAddressChangedfireUpdateWeatherAsyncconditionally on_lastUpdateAttempt > _lastSuccessfulFetch. No test covers the guard condition. At minimum, a structural test confirming both event subscriptions are wired and both unsubscriptions exist inDispose()would guard against future regression. SyncDockItem()not covered — new method updatingDockItem.Title/Subtitle/Iconhas no test verifying the propagation.
A structural test for the dispose unsubscriptions is fast to add given the existing reflection pattern — please add those before this merges.
Non-blocking Notes
- The IL-scan test
PinnedWeatherBand_OnTimerElapsed_CallsMarkAsStaleIfNeededis clever, but note it will pass even ifMarkAsStaleIfNeededis called unconditionally — it doesn't verify the conditional path. Fine for structural gating; just be aware of the coverage gap. - The
⚠️(emoji) vs⚠(plain) inconsistency flagged by the team lead is worth fixing for consistency in any follow-up.
PR Title: Fix #120: Stale Data Indicator & Network Connectivity Resilience
Fixes #120
🚀 What this PR does
This PR introduces a comprehensive and highly optimized solution to the silent stale data issue reported in #120, completely overhauling the network resilience of the
PinnedWeatherBand.1. Stale Data Indicator (
⚠️)⚠️warning to the subtitle._updateTimerto tick every 1 minute. The stale check happens locally in memory without triggering unnecessary API calls.2. Instant OS-Level Network Recovery
System.Net.NetworkInformation.NetworkChange.NetworkAvailabilityChangedandNetworkAddressChanged(for guaranteed IP assignment detection).3. Smart 1-Minute Retry
503 Service UnavailableAPI error while internet is connected), the extension no longer waits for the user's full interval (e.g., 30-60 minutes).4. Hover Tooltip Sync Fix
CommandItem.Name) would desync from the widget's actual text during error states.SyncDockItem()method to ensure the CmdPal tooltip and the Widget text are always perfectly identical, especially during⚠️ --error states.5. Clearer Error States
--loading fallback with a prominent⚠️ --when the weather service is unavailable or network drops, providing much better UX.6. Robust Unit Testing
DockBandCardSyncTests.csto guarantee architectural integrity.OnTimerElapsedstate machine successfully callsMarkAsStaleIfNeeded.🧪 How to test
⚠️ --(and the hover tooltip should perfectly match).⚠️to the subtitle.