Files
fuelboard/APPSTORE.md
T

228 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FuelBoard App Store Submission Plan
Updated: 2026-09-26 · Target: safe App Store submission.
Companion to SECURITY.md (code security) — this file is the *review-gate*
checklist: what must happen, who owns it, and the timeline.
## Status: SUBMITTABLE after the checklist below
The two 2026-08-11 blockers are RESOLVED by P0 (live chain):
- ~~LAN relay data source~~ → **app fetches from public HTTPS
(raw.githubusercontent.com mirror) with a bundled real 8,022-station dump
as the no-network fallback.** No private IP in the consumer path. Reviewers
on Apple's network see real data.
- ~~Always-location risk~~ → **Always is requested only when the user enables
Alerts** (ProximityMonitor escalates WhenInUse → Always on alert opt-in).
Onboarding requests WhenInUse only.
## StoreKit sandbox: verified working configuration and caveats
### Verified configuration
The FuelBoard consumable tip products are:
- `com.apt.fuelboard.tip099`
- `com.apt.fuelboard.tip299`
- `com.apt.fuelboard.tip499`
The app requests these exact **Product IDs** with StoreKit 2
`Product.products(for:)`; the UI names are display labels and are not used for
lookup. The app bundle ID, App Store Connect app, explicit App ID, development
team, and Sandbox Apple Account must belong to the same developer account.
Two test environments must remain distinct:
- **Local StoreKit testing:** select `/Users/apt/workspace/fuelboard/FuelBoardTips.storekit`
in Xcode, under Product > Scheme > Edit Scheme > Run > Options. This bypasses
App Store Connect and confirms the app's StoreKit loading and purchase code.
- **Apple sandbox testing:** select **None** for StoreKit Configuration, then
use Product > Run on a physical device with the development-signed build and
a Sandbox Apple Account signed in under Settings > Developer. This exercises
the real App Store Connect catalog.
The local configuration returning all three products does not prove that the
Apple sandbox catalog is ready. Conversely, a successful local run showing `$`
does not mean the App Store Connect price is USD: `Product.displayPrice` is
formatted for the active StoreKit/device storefront. Local StoreKit prices are
placeholders and do not upload to App Store Connect.
### Availability versus pricing
App Store Connect has separate controls:
- **Availability** controls the storefronts where the IAP is offered. The
verified FuelBoard configuration uses **United Kingdom only**.
- **Price schedule** may show generated prices for **175 countries and
regions**. This is expected and does not make the IAP available in all 175
storefronts; it is Apple's price conversion schedule.
The Sandbox Apple Account's App Store Country or Region must match an enabled
IAP storefront. After changing the sandbox tester's storefront, sign out and
back in under Settings > Developer so the storefront change activates.
### Important propagation/cache caveat
During sandbox diagnosis, `Product.products(for:)` completed successfully but
returned zero products with the IAP availability set to UK. Temporarily
changing one product's availability to all regions caused it to appear in the
sandbox catalog; changing it back to UK left it working. This indicates an
Apple-side catalog refresh or propagation/cache issue, not a change required in
the StoreKit code. Apple documents that product metadata changes can take up to
approximately one hour to appear in sandbox.
If an otherwise valid product returns zero products:
1. Confirm the Sandbox Apple Account belongs to the same developer team.
2. Confirm its storefront is an enabled availability region.
3. Sign out and back in to the sandbox account on the device.
4. Delete/reinstall the development build and retry with StoreKit Configuration
set to **None**.
5. If necessary, temporarily broaden one product's availability to all
regions, allow propagation, verify the product, then restore the intended
availability and allow propagation again.
Record whether the diagnostic result is **request failed**, **completed with
zero products**, or **a partial product set**. These are different failure
classes. Do not add fallback prices or change product lookup to display names.
Sandbox testing does not require prior App Review approval of the IAP. It does
require active developer membership, an active Paid Applications Agreement,
complete banking and tax information, valid product metadata and pricing, the
correct app/team association, and a matching Sandbox Apple Account.
## Hard blockers (must happen before ANY submission)
0. **[USER] Paid Applications Agreement in effect** — REQUIRED for paid IAP
configuration and sandbox testing. App Store Connect →
Business > Paid Applications Agreement → View and Agree. **This is the most
common cause of a 2.1(b) "unable to purchase item / error message" rejection
— without it every sandbox purchase fails.** (REJECTED once for this:
review date 2026-09-25, version 1.0 (21), iPad Air 11″ iPadOS 27.)
**Status 2026-09-25: agreement clicked Agree but shows "Pending user info" —
this is NOT in effect yet.** Complete the Banking (account + sort code/IBAN)
and Tax (W-8/W-9/UK tax ref, e.g. sole-trader UTR) sections under Business >
Payments & Tax until the agreement shows "Active" and every tab is green.
Only then do sandbox IAPs start working.
0. **[USER] Tip IAPs configured and available to the sandbox** — the three
consumables may exist but must ALSO have complete metadata, a current price,
and an enabled storefront availability. Prior App Review approval or
`Cleared for Sale` status is **not required for sandbox product discovery**.
They must be backed by the **In-App Purchase capability on the App ID**
`com.apt.fuelboard` (developer portal → Identifiers → App ID capability).
3-tier tip code is already correct (retry lookup `7cb23e0`); do NOT churn
SettingsView.swift. Test product discovery and purchase with a Sandbox
Apple Account first: Settings → Support FuelBoard.
1. **[USER] Apple Developer Program membership** — the app has never been
signed (all builds are `CODE_SIGNING_ALLOWED=NO` + ad-hoc re-sign for
SideStore). Requires a paid membership ($99/yr, up to 48 h to approve) and
a developer account.
2. **[USER] Register App ID `com.apt.fuelboard`** in the developer portal,
with the **App Group `group.com.apt.fuelboard`** capability AND the
**In-App Purchase** capability enabled (the widget + app-group cache
depend on the group; the tip jar needs IAP).
3. ~~[ME] `PrivacyInfo.xcprivacy`~~ — **DONE 2026-08-16** (app + widget
manifests added; UserDefaults `1C8F.1`, Location + Diagnostics declared).
4. **[ME] Real Distribution signing** — Distribution certificate + App Store
provisioning profile (or Xcode automatic signing once the account is in
Xcode); then `xcodebuild archive` (no more `CODE_SIGNING_ALLOWED=NO`).
5. **[USER] Privacy policy URL** in App Store Connect — mandatory; the app
collects location. Policy text is now in `PRIVACY.md`; publish it via the
FuelBoard marketing site or GitHub Pages and paste that public URL into App
Store Connect.
## Required tasks — owner [ME] (technical, do after account exists)
- [x] **PrivacyInfo.xcprivacy** — app + widget manifests added (2026-08-16):
UserDefaults reason `1C8F.1`; app also declares Location (linked, not
tracking) + Diagnostics (not linked, not tracking — the dev beacon)
- [ ] Distribution archive + TestFlight upload (needs Apple account)
- [x] **Version bump** → `1.0.1` (build 2), app + widget plists
- [x] **OGL attribution** — Settings → Data source footer now reads "Prices
use the UK Fuel Finder dataset (Open Government Licence) and refresh
up to twice a day." (2.3.1 hygiene)
- [x] **Out-of-UK empty state** — "No X stations within N miles" + UK-only
explainer + "Browse all UK stations" button (switches to Closest)
- [ ] ~~Gate dev surface behind #if DEBUG~~ **DECLINED — kept in Release on
purpose**: the user's home-testing installs the Release IPA and toggles
`fuelboard.relayFallback` via Settings → Debug (revealed only by tapping
About → Version ×5 — invisible to reviewers; no credentials involved).
Gating it out of Release would break that workflow for zero review gain.
- [ ] Final pass: `swift test` green (95 tests), archive smoke test on device
- [ ] ~~Capture screenshots from simulator~~ **RAW CAPTURES DONE (2026-08-16)** —
native 6.9″ 1320×2868 PNGs of all four tabs (Stations, Favourites,
Alerts, Settings) at `/tmp/fuelboard-shots/asc/`; assembled into ASC
frames by the user in the yuzu-hub App Store Screenshot Generator
(https://yuzu-hub.github.io/appscreen/). Screenshot harness: launch
args `-skipOnboarding` / `-tab stations|favourites|alerts|settings`
(ContentView, same pattern as `-showKeySheet`).
## Required tasks — owner [USER] (App Store Connect, cannot be done by me)
- [ ] Apple Developer Program membership + App ID (blockers #1–#2)
- [ ] **Tip jar: create the three consumable IAPs** in App Store Connect →
In-App Purchases (all type **Consumable**, exact product IDs, UK price
tier; tip jars are an accepted App Review pattern):
- `com.apt.fuelboard.tip099` — **£0.99** — "Splash & Dash" (Just enough to keep things moving.)
- `com.apt.fuelboard.tip299` — **£2.99** — "Half a Tank" (A generous top-up for development.)
- `com.apt.fuelboard.tip499` — **£4.99** — "Fill 'Er Up" (Keeping the app on the road.)
App-side is ready (`41e4c91`): Settings → Support FuelBoard shows all
three tiers with per-tier prices; sideloaded builds gracefully show
"not available in this build yet" until the products exist.
- [ ] App Store Connect app record: name, bundle ID `com.apt.fuelboard`,
category (Utilities), pricing (Free), availability (UK first?)
- [ ] Description, keywords, support URL, marketing URL, privacy policy URL
- [ ] Privacy nutrition labels questionnaire (location: yes, linked to user
for alerts/nearby; diagnostics: yes — the widget-diag beacon, not
linked, not used for tracking; no ATT prompt needed)
- [ ] Age rating questionnaire (4+; no objectionable content)
- [ ] Export compliance: HTTPS-only + keychain → standard encryption
exemption (5A992a), no documentation needed
- [ ] Approve the TestFlight build and do a real-device pass before submit
- [ ] Final "Submit for Review" click + stand by to answer review questions
## Review notes (copy-paste into App Store Connect)
> FuelBoard shows the cheapest petrol, diesel, and premium fuel near the user
> using the UK government Fuel Finder dataset (Open Government Licence).
> Prices are downloaded from the internet up to twice a day; the app filters
> 5/10/15-mile radii on-device and works offline from a bundled snapshot.
> Location is used to (a) rank stations by distance and (b) power the
> optional Alerts feature: when the user enables alerts, the app geofences
> the cheapest station in their radius and sends a notification when they
> approach it. Always-location is requested only when the user enables
> alerts. The app also provides a home-screen widget showing the current
> cheapest station nearby, and a price-history Trends chart for starred
> stations. All data is UK-wide (Fuel Finder covers England, Scotland, Wales
> and Northern Ireland); users outside the UK see an empty state explaining
> this.
## Review risks (know before submitting)
- **Always-location (5.1.1)** — the #1 question. The feature is visible
(Alerts tab) and opt-in; the review notes above say so explicitly.
- **Reviewer outside the UK** — empty radius; mitigated by the empty-state
task above + review notes.
- **"Cheapest" claims (2.3.1)** — mitigated by the OGL/footnote task.
- **Live Activities** — reviewers may ask why; answer: price alerts at a
glance. It is declared in the plist already.
- **Brand logos (Shell, BP, Tesco…)** — Simple Icons (CC0) + Wikimedia,
nominative use (cf. GasBuddy); low risk, keep sources documented.
- **GitHub raw as data source** — public data, no credentials; bundled dump
covers any regional GitHub unreachability.
## Timeline (best case)
| Day | Owner | Milestone |
|-----|-------|-----------|
| 0–2 | USER | Developer Program enrollment + App ID + app group capability |
| 2–3 | ME | Signing (automatic), PrivacyInfo.xcprivacy, attribution, empty state, version bump |
| 3–4 | ME | Archive → TestFlight; screenshots captured |
| 4–5 | USER | TestFlight install + real-device pass (onboarding, alerts, widget, offline) |
| 5–6 | USER | App Store Connect metadata + labels + age rating + privacy URL |
| 6–7 | USER/ME | Submit for review |
| 7–14 | Apple | Review (typically 1–3 days, can be a week); answer questions same-day |
Rollback note: nothing about submission changes the repo's rollback posture
(`main` history is intact; release builds are tagged per submission).