1. CarPlay widget taps could never launch Maps — FuelBoard is not a
CarPlay app, and Apple only lets a widget launch its own app in
CarPlay when that app is CarPlay-enabled. Marked the widget as a
disfavored CarPlay location (iOS 26+; no-op below): read-only in
the car, no dead tap. Header comment updated to match reality.
2. Widgets now track the user's location: timeline refresh 15m -> 5m,
and the app's location manager reloads all widget timelines after
every significant move (250m filter, throttled to 60s), so the
widget re-orders around the new position while driving instead of
waiting for the next tick.
3. Debug 'Cheapest in range' no longer shows the ALERT prediction
(alertsFuel + alert radius — a different pool by design). It now
mirrors the app list exactly: the TOP-badge station for the
selected fuel within the stationLimit radius (new DebugAppCheapest
computed in ContentView, passed to Settings). Alert-prediction
cheapest kept as fallback when the app view hasn't resolved.
- Relay envelope now carries source (api|csv), stations_count, and
data_updated (freshest price_last_updated the GOV.UK server reports,
independent of relay sync time).
- App decodes the new envelope metadata (RelayMeta), persists it after
each full fetch, and shows it in Settings → About → Data source:
Connection (API/CSV), Stations count, Data updated (local-formatted).
- Older relays without the fields decode cleanly (nil meta → em-dash).
- 2 new tests for meta decode + absent-meta tolerance; 37/37 pass.
- Debug section now shows the last device fix (lat/lng to 5dp) and its age.
- In-range indicator uses the SAME criteria as live alerts: stations selling
the monitored fuel within the alert radius of the fix. Green dot + count
when in range, red when none, grey while waiting for a fix.
- Shows the cheapest in-range station (name, distance, price).
- ProximityMonitor recomputes the snapshot on every location/stations
change and on Debug-section appear; ContentView passes the live status.
- SettingsView extracted to its own property to keep TabView body within
the compiler type-check budget. 35 tests pass.
- New Sort by value 'Favourites' in Edit Widget (per-widget config).
- ParameterSummary hides the Distance picker when Favourites is selected
(favourites are not radius-bound).
- Favourites mode reads keychain favourites (shared access group works on
SideStore free even without App Groups), filters to the widget's fuel,
refreshes prices from a focused relay fetch when in range, and ranks
cheapest-first with distance tiebreak. Not radius-filtered.
- App now re-saves refreshed favourites to keychain after every fetch so
the widget shows current prices instead of star-time snapshots.
- Empty state + headings adapt to favourites mode. 35 tests pass.
- Widget tap-to-directions now uses the native maps:// scheme instead of
http://maps.apple.com. The universal-link http URL failed to open from a
widget and fell back to launching the containing app; maps:// opens
Apple Maps directly for both small (whole widget) and medium (per row).
- Onboarding: removed the Skip button (mandatory flow), and system prompts
(location, notifications, local network) now fire only when the user taps
Continue/Allow on each page — not when the page appears. Pages
auto-advance once a permission is granted.
- After 'Start Using FuelBoard' the first data fetch is forced, so a
reinstall that leaves a recent lastRefresh in app-group defaults can no
longer skip the fetch and leave the station list empty. 35 tests pass.
Location and network fetches no longer fire at launch before onboarding
reaches the relevant step: foreground tracking and the first refresh are
gated behind onboarding completion (scenePhase no longer starts tracking
while the cover is up, and onAppear defers its fetch until after
finish). A new Data page sits just before 'You're all set': it probes
the FuelBoard Relay, which surfaces the iOS Local Network permission
prompt in context (NSLocalNetworkUsageDescription added), verifies
connectivity, and offers Open Settings/Check again on denial. Onboarding
is now 5 pages: Welcome, Location, Notifications, Data, Done. 35 tests
pass.
- Plain test notification now picks the nearest station selling the
monitored fuel and carries its real name, price, distance and
coordinates (was a static no-station notification)
- addAlertRequest reports whether the map snapshot attached or why it
failed, appended to the Settings result line instead of silently
delivering a notification without the embedded map
- Settings routes both test buttons through the live monitor
- Alerts (live + real-data test) carry the station name + coordinates in
userInfo and attach a map snapshot with a red pin, so the expanded
notification shows where the station is
- Tapping a notification opens Apple Maps driving directions to the
station from the user's current location (maps:// daddr)
- If Apple Maps hand-off fails (LiveContainer), an in-app map sheet
(StationMapView) pins the station + user location with a Directions
button
The first Settings test button no longer sends a hardcoded placeholder —
it runs the same criteria as a live alert (monitored fuel, alert radius,
current location) against the loaded stations and sends the REAL station
name, brand and price. Result line shown under the button; graceful
message when no stations or no sellers within radius.
Fires a local notification shaped exactly like a real price alert (uses the
currently configured alerts fuel + radius), with permission-gated handling:
prompts on first use, guides to Settings when denied. Tapping it opens
FuelBoard, same as a real alert.
The proximity monitor was tied to the Stations-tab fuel selection, so
switching the list to another fuel silently re-targeted geofences. Now the
Alerts tab has its own 'Fuel to monitor' picker (persisted as
fuelboard.alertsFuel):
- Monitor reads loadAlertsFuel() on init/background relaunch
- Changing the alerts fuel re-registers geofences around stations selling
that fuel only
- Favourites priority slots stay scoped to the monitored fuel
- Tests: alerts fuel round-trip + default (33/33 passing)
Fixing the bug where favouriting an Unleaded entry also created a Diesel
favourite. Favourites are now [FavouriteEntry] = (station, fuel) pairs:
- Starring a row pins it only for the fuel being viewed
- Favourites tabs show only fuels that actually have favourites
- Geofence monitor gives priority slots only to favourites matching the
monitored fuel
- Tests updated + new fuel-scoping test (31/31 passing)
- FuelType labels: 'Unleaded (E10)'/'Premium (E5)' -> 'Unleaded'/'Premium'
- New DistanceUnit (miles/km) preference; all app + widget distances,
radii and alert text convert/display in the chosen unit
- Settings tab (4th tab): segmented miles/km picker, 'Show introduction'
replay button (onboarding moved out of Alerts tab), StoreKit tip £4.99
- ProximityMonitor + widget notifications use the chosen unit
- Tests: label expectations updated, DistanceUnit conversion + format tests