Commit Graph
68 Commits
Author SHA1 Message Date
FuelBoard Contributor 16bf9ba9dd Backlog: price-history direction — P0 relay archive (start now) → P1 Favourites Trends graph → P2 avg/min-max overlay → P3 prediction arrow + widget sparkline 2026-08-15 05:39:51 +01:00
FuelBoard Contributor bb6ee33254 Stations: move price-rating Key (legend) into a modal sheet opened by an info button in the nav header; list stays focused on stations. Sheet = medium detent, inline nav title 'Key', Done button; legend rows unchanged. Adds -showKeySheet launch arg (QA hook for sim). 2026-08-14 23:53:02 +01:00
FuelBoard Contributor 75506b48b7 Telemetry: tag relay fetches with X-Client (app/widget) — app via RelayFuelProvider, widget focused/fuel-only fetches + diag beacon; Siri untouched (cached-only). Purely observational for relay admin /stats. 2026-08-14 23:23:06 +01:00
FuelBoard Contributor d91652aedc Onboarding: space the welcome feature rows out vertically (per-row padding 5pt → 10pt) 2026-08-14 22:47:48 +01:00
FuelBoard Contributor 9571e0c876 Onboarding: enlarge welcome-page feature icons 16pt → 20pt (semibold, wider alignment frame) for readability 2026-08-14 22:43:10 +01:00
FuelBoard Contributor bfb59d498a Live Activity: price now respects the Price display style — style carried in ContentState (captured at push time, extension renders deterministically instead of re-reading storage), setting change pushes an in-place content update via explicit override (no save-ordering race) 2026-08-14 21:06:04 +01:00
FuelBoard Contributor 8a589a35c1 Settings: drop parenthesized example figures from Price display segmented buttons — 'Station sign' / 'Pounds & pence' (cleaner, roomier labels) 2026-08-14 20:56:01 +01:00
FuelBoard Contributor e317a1e451 Settings: rename 'Show introduction' row to 'Replay onboarding' with round-arrow icon (arrow.clockwise.circle); key stays the address, value changed in Localizable.strings 2026-08-14 19:34:11 +01:00
FuelBoard Contributor 80113456ae Price display: station sign drops the £ (134.9) — matches roadside signs; pounds & pence uses forecourt superscript style £1.29⁹/L (small superscript third digit, small /L); attributed Text on list/widgets/Live Activity, Unicode superscript in Siri cards 2026-08-14 18:14:10 +01:00
FuelBoard Contributor fd7fa5f9c9 Settings: price display toggle — station sign (£129.9) vs pounds & pence (£1.299); display-only, all surfaces (list, widgets, Live Activity, Siri cards); Siri dialog stays speech-safe pounds 2026-08-14 15:33:26 +01:00
FuelBoard Contributor 819d2e6152 BACKLOG: favourite-directions phrase drop + Directions-to-Cheapest allocation 2026-08-14 14:50:53 +01:00
FuelBoard Contributor 8a4c472ab3 Siri: allocation swap B — Directions-to-Cheapest voice phrases restored (per-fuel), favourite-directions phrases dropped (card button covers it) 2026-08-14 14:50:31 +01:00
FuelBoard Contributor b46c09109e Siri: full-width Directions row on price card — fixes squashed 2-line button in narrow snippet 2026-08-14 14:45:51 +01:00
FuelBoard Contributor 6d7254ebba Siri: interactive price-card snippet (iOS 26 SnippetIntent) with Directions button → Apple Maps; zero AppShortcut slots 2026-08-14 14:38:14 +01:00
FuelBoard Contributor b458d92972 Siri: favourite price + favourite directions (top favourite per fuel); trim to 10 AppShortcuts (Apple cap) 2026-08-14 14:24:20 +01:00
FuelBoard Contributor aed99e3f32 Siri directions: speak the station before Maps hand-off (.result(opensIntent:dialog:)) 2026-08-14 14:05:42 +01:00
FuelBoard Contributor caadf184f2 Siri: Directions to Cheapest [fuel] AppShortcuts — OpenURLIntent hands off to Apple Maps (universal link) 2026-08-14 13:55:03 +01:00
FuelBoard Contributor a3b6f37843 Siri: shortcut names match app (Unleaded/Premium/Diesel); phrases cover UK speech variants 2026-08-14 13:23:52 +01:00
FuelBoard Contributor e55078c5e2 Siri: per-fuel App Shortcuts with literal fuel words (unleaded/petrol/premium/diesel) + UK case labels 2026-08-14 13:06:26 +01:00
FuelBoard Contributor 037c2418af Siri: scope cheapest-near-me to the saved search radius (5/10/15 mi) 2026-08-14 12:49:30 +01:00
FuelBoard Contributor be781d80f5 Backlog: Siri on-screen awareness (View Annotations + StationEntity) P2 2026-08-14 12:42:27 +01:00
FuelBoard Contributor 29d4a0472b Siri: cheapest [fuel] near me intent + App Shortcuts (cached-first, freshness label) 2026-08-14 12:32:31 +01:00
FuelBoard Contributor b5fe21c44b Backlog: Siri cheapest entry notes Tailscale interim + IN PROGRESS status 2026-08-14 12:29:09 +01:00
FuelBoard Contributor f85c74a7a8 Backlog: Localizable.strings key-normalization idea (P3, no action) 2026-08-14 12:02:26 +01:00
FuelBoard Contributor b66a9161a3 Copy sync: Localizable.strings matches current UI (114 keys; favourites reorder note, geofence debug section, alerts footer; dead keys pruned) 2026-08-14 11:07:15 +01:00
FuelBoard Contributor fbf8c79feb Favourites: drag-and-drop reorder per fuel; stored order drives single-widget favourite 2026-08-14 10:48:53 +01:00
FuelBoard Contributor aa95d214e8 Alerts: move geofence monitoring status to Settings debug; plain-language footer 2026-08-14 10:34:45 +01:00
FuelBoard Contributor edf376d378 Settings: move Data source section behind debug flag (debug-only) 2026-08-14 10:01:36 +01:00
FuelBoard Contributor 3dd567a5cc Widgets: merge small+medium into one kind (both sizes, one gallery entry) 2026-08-14 09:52:50 +01:00
FuelBoard Contributor e62177ae2e Backlog: add lock screen widgets (P2, Option B) 2026-08-14 05:02:43 +01:00
FuelBoard Contributor e044a1552a Backlog: add Siri/Shortcuts features (P1-P3); mark widget config done 2026-08-14 04:59:26 +01:00
FuelBoard Contributor f5a5d3325f Swap app icon to water-drop pound design (drip export) 2026-08-14 04:53:31 +01:00
FuelBoard Contributor b94f769750 Small widget: support .systemMedium so the icon menu offers both sizes
The iOS 18+ long-press app-icon menu (turn icon into a widget) shows
the supported SIZES of the FIRST-registered widget kind only — it
does not list widget kinds and is not keyed by intent type (two
distinct intents in 6375af4 changed nothing on-device). The bundle's
first kind (FuelPriceSmallWidget, small-first since 38b47ee) therefore
decided the menu offered only the small tile.

The early build worked because FuelPriceWidget was registered first
back then. Reordering to medium-first would fix the menu but flip the
gallery order (38b47ee). Instead: this first kind now supports BOTH
.systemSmall and .systemMedium, so the icon menu offers both size
tiles (ChatGPT/Safari behave the same way — their first kind supports
several families). The gallery still lists both widget kinds in bundle
order; the Small entry just gains a medium variant. The shared view
already branches on family, so medium renders the station list.
2026-08-14 02:53:52 +01:00
FuelBoard Contributor 6375af4c05 Small widget: restore its own intent TYPE (minimal params) for the icon menu
iOS keys the long-press app-icon contextual menu by configuration-intent
TYPE, so sharing one intent across kinds collapsed the menu to the
first-registered kind (small) — the medium 'FuelBoard Prices' vanished
from the icon menu while the + gallery (kind-keyed) still listed both.
dafc96f's skeleton fix was never the problem; the extra favourite
AppEntity params were.

FIX: FuelBoardSmallWidgetConfigurationIntent — a distinct intent TYPE
with the IDENTICAL minimal parameter set (fuel/sort/distance only, no
AppEntity params). Fresh-widget default-config resolution stays intact;
the appex metadata now registers two WidgetConfiguration actions.

Verified: build green, 45/45 tests, extract.actionsdata lists both
intents, fresh IPA packaged + signed per component.
2026-08-14 02:28:43 +01:00
FuelBoard Contributor 050c9f13a3 Onboarding: show the app icon with iOS rounded corners
The welcome page's green-gradient circle + SF symbol is replaced by the
app icon itself (AppIconArtwork imageset mirroring AppIcon.png), clipped
to the standard iOS rounded-rect corner radius (21pt at 96pt — ~22%),
so onboarding matches the Home Screen icon.
2026-08-13 23:41:16 +01:00
FuelBoard Contributor fec139cf5a Icon: replace with Fuelboard-copy composer render (Vecteezy gas-station art)
New Icon Composer project artwork (gas-station vector, layered + background)
rendered via ictool at 1024pt: Default / Dark / TintedLight renditions
replacing all three asset-catalog slots.
2026-08-13 23:36:57 +01:00
FuelBoard Contributor 018e6eec2f Icon: use the Icon Composer render instead of the pre-baked appiconset PNG
The export zip contained both a pre-rendered appiconset AND the
Fuelboard.icon composer project; the pre-baked PNGs were a different
(flatter) render. Re-rendered the .icon bundle with ictool (Xcode 26
Icon Composer executable) — Default/Dark/TintedLight renditions at
1024pt — and replaced the three asset-catalog slots.
2026-08-13 22:00:14 +01:00
FuelBoard Contributor 3545239ad6 New app icon: green pump showing £1.00, with dark + tinted variants
Replaces the single 1024px icon with the three-variant set (universal,
dark-luminosity, tinted) generated for FuelBoard; Contents.json updated to
the iOS-only slots.
2026-08-13 21:37:36 +01:00
FuelBoard Contributor cd88e23601 Widget pump icon flexes to the chosen fuel's colour
The pump glyph in the widget header (both the small single-station face and
the medium list face) was hardcoded green; it now uses the app's fuel
colour wheel — green #30D158 unleaded, yellow #FFD60A premium, cyan
#64D2FF diesel — mirroring the per-fuel pumps in the app's segmented
picker. Palette duplicated in the widget target (Shared is Foundation-only
by design); cross-referenced to StationsView.swift's FuelType.tintColor.
2026-08-13 20:53:20 +01:00
FuelBoard Contributor dafc96f845 Fix small widget skeleton: use the proven medium intent; delete failing small intent
ROOT CAUSE (proven by A/B test on-device): the small widget's custom
intent (FuelBoardSmallWidgetConfigurationIntent with favouriteFuel +
favourite AppEntity params) fails fresh-widget default-config resolution
at the system level — the whole default config fails, no timeline is
ever requested, makeEntry never runs, widget stays on the skeleton.
A minimal small widget sharing the MEDIUM intent (fuel/sort/distance
only) populated immediately in the same extension on the same device.
Neither removing defaultResult() (4d35b08) nor the
@IntentParameterDependency (8a9e775) helped — the extra entity params
themselves break resolution.

FIX: FuelPriceSmallWidget now uses FuelBoardWidgetConfigurationIntent
exactly like the working test widget. FuelBoardSmallWidgetConfiguration
Intent, FavouriteFuel(+Query), WidgetFavourite(+Query) deleted; the
pinned-favourite picker is gone (approved tradeoff). Favourites sort
still works on both faces via the list branch — the small face renders
the cheapest favourite of the chosen fuel. Beacon tag distinguishes the
small widget in diagnostics; test widget removed, gallery order
restored (small, medium, live activity).
2026-08-13 20:43:15 +01:00
FuelBoard Contributor 553730681f A/B test: from-scratch minimal small widget (proven medium intent)
Temporary FuelBoardTestSmallWidget kind: a small-face widget driven by
the PROVEN medium intent (fuel/sort/distance only — no favourite params,
no dependency, no custom entity query), registered first in the gallery.
Isolates small SIZE vs small INTENT: if it populates while
FuelPriceSmallWidget skeletons, the small intent's extra machinery is the
culprit; if it also skeletons, the failure is small-family/system-level.
Provider gains a beaconTag so the test widget's beacons are
distinguishable in the relay log and keychain slots. Removed once the
root cause is confirmed.
2026-08-13 20:27:03 +01:00
FuelBoard Contributor 8a9e77546f Small widget: remove @IntentParameterDependency from WidgetFavouriteQuery
Final structural difference from the working medium widget. The
dependent-picker machinery resolves the favourite parameter through the
hidden favouriteFuel parameter during fresh-widget default-config
resolution (default Sort = Cheapest hides favouriteFuel), and that
resolution fails on-device — the whole default config fails and the
small widget strands on the skeleton. Favourite picker now lists ALL
favourites, each row labelled with its fuel (the fuel-scoped filter was
the dependency's only purpose). Configured Favourites-mode widgets are
unaffected. Per-intent beacons remain in place to verify the outcome.
2026-08-13 20:23:12 +01:00
FuelBoard Contributor 3cf1d5c9af Widget diagnostics: per-intent beacons (keychain slots + relay query)
The single-slot beacon couldn't distinguish small vs medium (last writer
wins). Beacon keychain service now embeds the intent type name so small
and medium never overwrite each other; the relay GET now carries the
intent type + entry state as query params so the access log shows exactly
which widget kind ran makeEntry, when, and with what. Diagnostics screen
shows SMALL and MEDIUM beacons side by side — a missing SMALL beacon
after adding the small widget proves its timeline never reaches the
provider (system-level config resolution failure).
2026-08-13 20:13:24 +01:00
FuelBoard Contributor 335a9751dc Widget diagnostics: beacon from makeEntry (keychain + relay) + app diagnostics section
The small-widget skeleton survived four resolution fixes; instrument the
widget instead of guessing. Every makeEntry now writes its entry state
(intent type, location source, station count, fuel, sort, favourites flag,
first station) to a keychain beacon (app-readable) and fire-and-forgets a
GET to the relay's /api/v1/widget-diag so its access log records whether a
widget timeline actually runs in the extension and what it produced.
Settings → Debug gains a Widget Diagnostics section: the keychain beacon +
the installed-widget inventory from WidgetCenter. Relay gains the
/api/v1/widget-diag route (204, log-only).
2026-08-13 19:52:12 +01:00
FuelBoard Contributor 4d35b08533 Widget: keychain-free fresh defaults — drop defaultResult(), auto-populate at entry time
The small-widget skeleton (fresh widget stranded on the placeholder while
the medium populated in the gallery) traced to the small intent's default
configuration resolution: FavouriteFuelQuery/WidgetFavouriteQuery
defaultResult() read keychain in the gallery-preview/fresh-add path,
failing the whole default config and leaving no timeline. Configured
widgets bypassed defaultResult (stored values → lenient entities) — the
exact favourites-works/cheapest-closest-doesn't split.

- Remove defaultResult() from both queries: fresh configs now resolve
  from the static @Parameter(default:) values through lenient
  entities(for:) with ZERO storage reads.
- WidgetFavouriteQuery.entities(for:) skips the keychain name lookup for
  id-only (empty station) values.
- Auto-populate moves to makeEntry: empty pinned favourite → first
  favourite of the fuel (same rendered result as the old sheet default).
- Sheet keeps the dependent picker via suggestedEntities() + dependency;
  fresh widgets show static defaults until picked (approved tradeoff).
2026-08-13 19:35:46 +01:00
FuelBoard Contributor 8646521176 Small widget: fuel-only relay fetch when no location, so default/Cheapest/Closest faces populate with real stations instead of sample data. Root cause of the persistent skeleton: favourites mode reads keychain (works on free accounts), but the non-favourites path needs stations — the app-group cache can be absent on free accounts and keychain can't hold the station dump, so with no location fix the widget fell through to SampleFuelProvider and never populated. Now when location is nil the widget fetches /api/v1/stations?fuel=X&limit=500 (fuel-only, cheapest-first, verified against the relay). 2026-08-13 18:51:41 +01:00
FuelBoard Contributor 38b47eea38 Widget gallery order + default-widget skeleton fix. (1) Register FuelPriceSmallWidget BEFORE FuelPriceWidget so the gallery lists small, then medium (bundle order = gallery order). (2) Revert entity resolution to LENIENT: the timeline request resolves the full configuration through the entity queries, and the b1765b5 strict entities(for:) (nil for the empty default favourite 'fuel|') failed resolution for default-config small widgets, stranding them on the iOS skeleton placeholder. entities(for:) now always returns a value; defaultResult() still auto-populates fresh widgets to the first fuel with a favourite / first favourite of that fuel. 2026-08-13 18:39:30 +01:00
FuelBoard Contributor b1765b5611 Widget favourites defaults: auto-populate Fuel and Favourite rows when Sort = Favourites. FavouriteFuelQuery gains defaultResult() (first fuel with a favourite, Unleaded→Diesel order) and strict entities(for:) — a stored fuel that no longer has favourites fails resolution and falls back to the default. WidgetFavouriteQuery gains defaultResult() (first favourite of the Fuel row's fuel, else first favourite overall) and strict entities(for:) — the empty static default ('fuel|') and removed favourites now resolve to nil so the sheet pre-selects a real favourite instead of showing a placeholder. Provider fallback (cheapest favourite of the fuel) unchanged. 2026-08-13 18:28:23 +01:00
FuelBoard Contributor 906f8a8429 Small widget: declare config params in display order for every summary branch. If iOS falls back to declaration order when re-rendering the sheet after a parameter change, the favourites branch previously re-ordered to Sort by first (sort was declared before favouriteFuel). Now favouriteFuel, fuel, sort, favourite, distance — Favourites mode reads Fuel · Sort by · Favourite under either ordering mechanism. No encoding change (config keys are identifiers, order-independent). 2026-08-13 18:21:03 +01:00
FuelBoard Contributor 926317236a Widget config sheets: unify row layout across both widgets — Fuel type first, then Sort by, then Favourite (Favourites sort only). Small widget's Favourites branch reordered from Sort/Fuel/Favourite to Fuel/Sort/Favourite, and the favourites-scoped fuel param retitled 'Fuel' so the row reads identically in every mode. Medium widget was already Fuel-first. No parameter type changes, so no stored-config reset beyond the normal reinstall. 2026-08-13 18:02:53 +01:00
FuelBoard Contributor 3e2d3775f2 Widget config sheet: revert WidgetSort to String-backed AppEnum. parameterSummary When(\.$sort, .equalTo, …) silently falls back to the otherwise branch for AppEntity parameters (iOS 17+ bug FB13263902), which hid the Favourite/Favourite Fuel rows on the small widget — the metadata compared against a literal 'WidgetSort.favourites' string instead of the enum case. String AppEnums match reliably (the d8506a0 form). Tradeoff: the Sort picker always offers Favourites; picking it with no favourites shows the empty state. FavouriteFuel entity + IntentParameterDependency filtering unchanged. 2026-08-13 17:48:33 +01:00
FuelBoard Contributor d0bd4e7a8e Small widget favourites config: fuel dictates the favourite list (@IntentParameterDependency filters picker options by the chosen fuel), fuel picker shows only fuels with favourites (FavouriteFuel entity), Favourites sort hidden when no favourites exist (WidgetSort → dynamic AppEntity), fuel tags removed from favourite rows 2026-08-13 17:14:14 +01:00
FuelBoard Contributor d8506a035f Small widget favourite picker: embed station name + multi-fuel flag in the WidgetFavourite entity so picker rows render with no storage read (config-sheet resolution can run without keychain/app-group access, which made every row show the 'Favourite' placeholder) 2026-08-13 17:00:17 +01:00
FuelBoard Contributor 7357eb98c3 Small widget favourites: fuel row returns to the config sheet (picks the default favourite); pinned favourite wins, fuel-tagged picker rows when favourites span >1 fuel; fallback = selected fuel's cheapest favourite 2026-08-13 16:51:21 +01:00
FuelBoard Contributor 1bdef59d78 Small widget: Favourites gains a picker to pin which favourite to show (separate FuelPriceWidgetSmall kind + AppEntity picker; medium keeps the list, no picker). Backlog: dual-unit widget picker labels 2026-08-13 15:49:18 +01:00
FuelBoard Contributor 26e724f48a Widget config: Distance picker as AppEntity so labels resolve dynamically from the app's units (AppEnum labels are static) 2026-08-13 15:33:24 +01:00
FuelBoard Contributor 64330f96a8 Widget config: Distance picker mirrors the app's units setting (5 mi = 8 km) via dynamic AppEnum displayRepresentation 2026-08-13 15:25:20 +01:00
FuelBoard Contributor 2409b71f2f Labels: pluralize miles (1 mile) and show true distances in the chosen unit (5 mi = 8 km) across Alerts/Stations pickers + captions 2026-08-13 15:10:23 +01:00
FuelBoard Contributor 490c343fd5 Alerts: mile-friendly radius 1/2/3/5/8 + Follow search (capped 8 mi); Live Activity Follow search; legacy radii clamped 2026-08-13 15:00:53 +01:00
FuelBoard Contributor bb48995fac Onboarding copy: tighten values (daily refresh, widget line, alert line) 2026-08-13 14:22:12 +01:00
FuelBoard Contributor 61bb61d802 Backlog: add GitHub production-data mirror (P3); mark API switch, unit tests, backoff done 2026-08-13 14:04:58 +01:00
FuelBoard Contributor 2d070fa3ba Fuel colours: align with wheel palette — Unleaded #30D158, Premium #FFD60A, Diesel #64D2FF 2026-08-13 13:09:22 +01:00
FuelBoard Contributor 3fe2a7f03c Onboarding copy: drop premium fuel mention, 'updated daily' 2026-08-13 13:02:50 +01:00
FuelBoard Contributor 0d91c82bb1 Localize all app copy via Localizable.strings 2026-08-13 10:57:36 +01:00
FuelBoard Contributor bbe43a6149 Onboarding: Local Network confirmation matches other pages
Granted label now reads just 'Connected' with the green tick and uses
the same .subheadline confirmation styling as the Location and
Notifications pages.
2026-08-13 10:35:18 +01:00
FuelBoard Contributor a1fa0f9998 Onboarding: silent Local Network pre-flight on replay
Replaying onboarding showed a visible probe on the 4th page because
iOS exposes no status API for the Local Network permission — the
connection attempt IS the only detector. Location and Notifications
query their status at init, so their ticks are already known when the
page appears; Local Network could only be known by probing.

Now, when onboarding is re-opened (already completed), a pre-flight
probe runs silently at open: granted connects in ms, denied reports
.waiting/.localNetworkDenied immediately, and a plain .waiting
(permission undetermined after a reinstall) cancels so page 4's
Continue still fires the prompt in context. By the time the data page
appears its tick (or denied text) is already on screen, matching the
other permission pages. First run is unchanged.
2026-08-13 10:29:20 +01:00
FuelBoard Contributor ed243fae9e Onboarding: show green tick on Local Network page before advancing
The probe result landed while the Local Network prompt was still
dismissing, so the granted tick was skipped and the page jumped
straight to the final slide. Hold 'Connected — prices will load'
on screen for a beat before auto-advancing, matching the visible
tick flow of the Location and Notifications pages.
2026-08-13 10:11:57 +01:00
FuelBoard Contributor cb174f66fa Settings About: drop redundant 'FuelBoard x.y(z)' footer
Version already shown in the Version row; the footer just duplicated it.
2026-08-13 10:06:51 +01:00