T22 -- TelemetryUploader on package:http with MockClient standing in for MockWebServer, tested against a real in-memory Drift database rather than a fake DAO. Upload runs on its own timer, injected as a callback so the engine has no opinion about HTTP and tests need no network. Config on shared_preferences with a hand-rolled UUID v4. Still no UI for the endpoint, exactly as in the native app. 7 tests. T24 -- integration_test/app_test.dart, 4 tests passing on the iOS simulator. These cover what widget tests cannot: Drift opening against real platform storage, plugin registration, go_router driving a real Navigator, cold start. T26 -- RELEASE-IOS.md. Usage strings are specific rather than generic, which is the leading Guideline 5.1.1 rejection cause; privacy-label answers decided; a pre-submission list covering the bundle-id switch back to com.rippr, the still default app icon, a release build, and a demo video for review notes. T25 written up as REAL-RIDE-CHECKLIST.md but outstanding by nature. Two items decide real things: force-stopping mid-ride on Android confirms the 111 km crash-gap bug is fixed, and parking 15 minutes mid-recording on iOS decides whether geolocator is sufficient or the paid engine is needed. Two prefer_initializing_formals lints suppressed with a reason: Dart forbids a named parameter beginning with an underscore, so the suggested fix will not compile. 178 tests passing, analyze clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.7 KiB
T26 — iOS release readiness
What App Review will look at, and what is already in place. Background-location apps get
more scrutiny than most, and the research in docs/PORT_RESEARCH.md names unclear
privacy disclosure as the leading rejection cause.
Status: configured, not submitted. Nothing here has been through review.
Guideline 5.1.1 — data privacy and transparency
Rejection cause: generic usage strings like "This app needs location".
Already in ios/Runner/Info.plist, written specifically:
| Key | Says |
|---|---|
NSLocationWhenInUseUsageDescription |
Records the GPS track of your ride so you can see route, speed and distance afterwards. Nothing is recorded until you press Start. |
NSLocationAlwaysAndWhenInUseUsageDescription |
Keeps recording while the phone is in your pocket or the screen is off, so a ride you started is captured beginning to end. Recording stops the moment you press Stop. |
Both name what is collected, why, and when it stops. That last clause matters — it is the difference between "an app that wants your location" and "a recorder you control".
App Privacy nutrition labels
Declare, and make sure it stays true:
- Location → Precise Location, linked to the user? No. Used for App Functionality only.
- No data collected for tracking, no advertising identifiers, no analytics SDKs.
- Data is not transmitted off-device by default. The uploader exists but has no UI and no endpoint configured; it is inert unless someone sets one deliberately.
If a server ever ships (the v3 group-ride idea), these labels must change before it does. Undisclosed background transmission is a straightforward rejection.
Guideline 2.1 — completeness and background stability
The exposure is crashing on background resume. Mitigations in place:
- Recording state lives in the database, so resuming after suspension reads real state rather than guessing.
restoreAfterProcessDeathruns at startup and is covered by four tests, including the crash-gap guard.- Every upload failure path is swallowed; the network cannot stop a recording.
- Database write failures are caught per batch — losing points beats losing the app.
Still unproven: what iOS actually does when the app is suspended while stationary. That is item I3 in REAL-RIDE-CHECKLIST.md and it must be answered before submission.
Guideline 4.2 — minimum functionality
Not a realistic risk: native Flutter UI, real hardware integration, offline-first storage, no web view anywhere.
Before submitting
- Switch the bundle id from
com.rippr.porttocom.rippr(T27). The.portsuffix exists only so the native app can be installed alongside during the port. CFBundleNameis still the generated lowercaserippr;CFBundleDisplayNameis alreadyRippr. Make them consistent.- App icon and launch screen — still Flutter defaults. The native app's adaptive icon
artwork lives in
~/dojo/rippr/design/and needs re-exporting at iOS sizes. - Run REAL-RIDE-CHECKLIST.md on a real iPhone, especially I3.
- Record a demo video of a real ride for the review notes. Background-location apps are frequently asked to justify the entitlement; a video pre-empts a rejection round.
flutter build ipa --releaseand confirm the release build works — everything so far has been debug.
Known parity gap to disclose internally
The notification cannot carry actions, so the native app's Pause/Resume buttons in the shade are absent on both platforms. Not an App Review issue; it is a feature difference the T27 audit must record.