diff --git a/docs/V3-BACKLOG.md b/docs/BACKLOG.md similarity index 61% rename from docs/V3-BACKLOG.md rename to docs/BACKLOG.md index 8b8c6eb..494273d 100644 --- a/docs/V3-BACKLOG.md +++ b/docs/BACKLOG.md @@ -13,10 +13,14 @@ --- -# v3 backlog +# Backlog — v3 and v4 -Everything known-outstanding as of v2.0.1, with enough context to pick up cold. -Nothing here is committed to — it is a menu, roughly ordered by value. +Everything known-outstanding, with enough context to pick up cold. Nothing here is +committed to — it is a menu, roughly ordered by value. + +**v3 is everything that can be built with no server.** **v4 is everything that cannot.** +That split is the most useful thing in this document: it means v3 can proceed indefinitely +without anyone deciding to run infrastructure or hold other people's location data. **Before planning anything: run the real-ride checklist in [the real-ride checklist](port/REAL-RIDE-CHECKLIST.md).** Several items below may turn out to be non-issues, and @@ -57,29 +61,7 @@ covering: navigation record→trips→detail→back, rotation/state retention, e --- -## 2. The original v3 candidate — live group ride view - -Deferred from v2 as "needs real server work". This was in the **v1** brief's goal -statement, so it has been the intended destination all along. - -Already in place: -- `TelemetryUploader` — batched POST, retry, offline-safe, cannot stall recording -- `synced` column and backlog semantics -- `trip_id` / `segment_id` per point, so a server can reconstruct rides and pauses -- `Config.deviceId` — stable per-install id to distinguish riders - -Missing: -- **A server.** Nothing exists. This is the actual work. -- **UI for the endpoint** — currently only reachable via `Config.setUploadEndpoint()` -- Other riders' positions on a map, and a live map at all (see below) -- Auth, rider identity, group membership - -**Worth deciding early:** this is the point where Rippr stops being a local-only app. That -brings hosting, privacy, and location-sharing consent into scope. - ---- - -## 3. Live map on the recording screen — **decision reversed** +## 2. Live map on the recording screen — **decision reversed** v2 deliberately shipped no live map, on the reasoning that the phone rides in a pocket. Dylan has since asked for one — for visual appeal, and because **people may mount the phone @@ -104,7 +86,7 @@ What still holds regardless: --- -## 4. Activity type per ride +## 3. Activity type per ride Promoted out of the ideas list, because it turned out to be a **positioning decision** rather than a feature. See [LAUNCH.md](LAUNCH.md). @@ -165,6 +147,59 @@ time they don't, they can fix it after. --- +## 4. Route drawing and planning + +Drop pins on a map, have the **shortest path between them resolved along real roads**, and +get distance and estimated ride time back before setting off. Save it, ride it later. + +This is *pre*-ride planning: a genuinely new mode alongside recording, not an extension of +it. It needs no ride in progress, no server of its own, and could be built entirely +standalone — which makes it the largest thing in v3 that carries no dependency on anything +else. + +### It needs a routing engine, and that is the whole decision + +Straight lines between pins are easy and reuse `haversineMeters`. **Road-snapped shortest +path is not** — it needs a routing service over OpenStreetMap data: + +| Option | Trade-off | +|---|---| +| **Public OSRM demo server** | Free, zero setup, **not for production use** and rate-limited. Fine for prototyping only. | +| **Self-hosted OSRM** | Fast, well-understood. Needs a machine and a regional OSM extract (a province is a few GB). | +| **GraphHopper** | Self-hostable, good cycling and motorcycle profiles, friendlier ETAs | +| **Valhalla** | Best multi-modal profiles, heavier to run | +| **Commercial (Mapbox, Google)** | No ops, per-request billing, an API key in the app | + +**Profiles matter here more than usual.** A motorcycle route and a bicycle route between +the same two pins are genuinely different, and cycling engines avoid motorways while +motorcycle riders often want the twisty road rather than the fast one. This is where +[section 3](#3-activity-type-per-ride) pays off — the activity picks the routing profile. + +### Estimated time is a promise, and an easy one to get wrong + +Routing engines return a duration based on posted speed limits. That is not how long *you* +take. Once there is real ride history, a far better estimate comes from the rider's own +average moving speed for that activity — which the app already stores on every `Trip`. + +Show the engine's estimate first, and replace it with a personal one when there is enough +history to justify it. + +### Data model + +`Route` + `Waypoint`, **separate from `Trip`** — a plan is not a recording, and conflating +them would put unridden kilometres into ride totals. The resolved geometry (the polyline +the engine returns) should be cached on the `Route` so a saved plan opens offline and does +not re-bill a routing request every time it is viewed. + +### Follow-ons + +- Follow a planned route on the live map while riding (needs section 2) +- Compare a recorded ride against the plan afterwards — where you deviated, how the real + time compared to the estimate +- Export a plan as GPX so it loads into a dedicated sat-nav + +--- + ## 5. Smaller items | Item | Notes | @@ -185,40 +220,89 @@ time they don't, they can fix it after. Terse on purpose. Unshaped, to be consolidated later. - **Live map while recording.** More visually appealing than a numbers screen. Reverses the - v2 decision — see section 3 for the constraints that survive it. + v2 decision — see section 2 for the constraints that survive it. - **Pick a real theme.** The current look is functional dark + safety orange, chosen to match the icon. Decide on an actual visual identity and push the UI toward something polished rather than merely clean. -- **User sign-up and accounts.** Register people, give their data somewhere to live. - Prerequisite for anything cloud-side, and pairs with the group-ride server in section 2. -- **Paid cloud backup.** Ongoing storage of rides over time. Needs accounts first, plus a - decision on hosting, pricing, and what happens to data when someone stops paying. -- **Waypoint route planning.** Drop a series of pins on the map to "draw" a route, get - distance and estimates back, and save it to ride later. This is *pre*-ride planning — - a genuinely new mode alongside recording, not an extension of it. Needs its own entity - (`Route` + `Waypoint`), separate from `Trip`, since a plan is not a recording. - Straight-line pin-to-pin distance is easy and reuses `Geo.haversineMeters`; snapping to - actual roads needs a routing service (OSRM, GraphHopper, Valhalla — self-hostable) and is - a much larger step. Natural follow-ons: follow a planned route on the live map, and - compare a recorded ride against the plan afterwards. - ### Threads running through these Sign-up, cloud backup and group ride are one programme, not three: they all need a server, -identity, and a privacy stance. Worth scoping together rather than separately. +identity, and a privacy stance. They have therefore been moved out to **v4** below, and +should be scoped together or not at all. Activity type (now section 4) and theming are independent and much cheaper — either could ship alone, and activity type is the natural first v3 task because it forces the migration path to be proven while the stakes are still low. -Live map, handlebar mounting and waypoint following also cluster: all three assume a -visible screen during the ride, and all three want the same map component. Route planning -is the odd one out — it needs no ride in progress at all and could be built entirely -standalone. +Live map, handlebar mounting and route *following* cluster: all three assume a visible +screen during the ride, and all three want the same map component. Route *planning* +(section 4) is the odd one out — it needs no ride in progress at all and could be built +entirely standalone. --- -## 7. Things that must not regress +--- + +# v4 — everything that needs a server + +Split out from v3 deliberately. These three are **one programme, not three items**: each +needs a server, an identity system, and a privacy stance, and none of them is worth +building alone. Nothing in v3 depends on any of them. + +## Live group rides + +Invite someone to a ride and see both of you on the map, live. + +This was in the **v1 brief's goal statement**, so it has been the intended destination all +along — it was deferred from v2 as "needs real server work", and that is still the honest +summary. + +### Already in place, from v1 + +- `TelemetryUploader` — batched POST, retry, offline-safe, and structurally unable to stall + recording +- The `synced` column and backlog semantics +- `trip_id` / `segment_id` carried **per point**, so a server can reconstruct rides *and* + their pauses +- `Config.deviceId` — a stable per-install id, enough to distinguish riders + +### Missing + +- **A server. Nothing exists.** This is the actual work. +- UI for the endpoint — still only reachable via `Config.setUploadEndpoint()` +- Auth, rider identity, group membership, invitations +- Other riders drawn on the map, which also needs the live map from v3 section 2 +- **Live** delivery. The current uploader is a batched backlog drain on a 30 s timer, which + is right for archiving and useless for watching someone move. Live positions want a + websocket or similar, running *alongside* the existing uploader rather than replacing it — + the batch path is what guarantees no fix is ever lost. + +### The decisions that are not technical + +- **Location sharing is consent, not a feature.** Who can see you, for how long, and how + does it stop? Sharing that outlives the ride is a privacy incident waiting to happen. +- Ride invitations mean handling someone declining, leaving mid-ride, or losing signal for + twenty minutes — the map has to say "last seen 8 minutes ago", not silently freeze them + in place. +- This is the point where Rippr stops being a local-only app and starts holding other + people's location data. + +## Accounts and sign-up + +Prerequisite for everything else here. Note that Apple **requires in-app account deletion** +for any app offering account creation, and that a location app with accounts inherits real +obligations — see [LAUNCH.md](LAUNCH.md). + +## Paid cloud backup + +Ongoing storage of rides over time, and the only monetization model that justifies +recurring money — because it is the only one with recurring costs. Needs accounts first, +plus decisions on hosting, pricing, and what happens to someone's history when they stop +paying. + +--- + +# Things that must not regress · all versions Hard-won and easy to undo by accident. Each has a comment in the code explaining why. @@ -239,7 +323,7 @@ Hard-won and easy to undo by accident. Each has a comment in the code explaining --- -## 8. Reading order for picking this up cold +# Reading order for picking this up cold 1. [../README.md](../README.md) — what the app is and its current state 2. [ARCHITECTURE.md](ARCHITECTURE.md) — why it is built this way diff --git a/docs/LAUNCH.md b/docs/LAUNCH.md index 28ef2b2..1d3831b 100644 --- a/docs/LAUNCH.md +++ b/docs/LAUNCH.md @@ -121,7 +121,7 @@ Everything in Stage 1, plus the parts that are genuinely work. Everything user-facing currently says motorcycle, but the recording pipeline is entirely activity-agnostic — it records positions and speeds, and nothing in it cares what you are -sitting on. `V3-BACKLOG.md` already carries **activity type per ride** as an idea. +sitting on. `BACKLOG.md` already carries **activity type per ride** as an idea. That matters here rather than only in the backlog, because it decides the store category, the screenshots and who finds the app. Cyclists are a far larger audience than diff --git a/docs/port/PROGRESS.md b/docs/port/PROGRESS.md index dff600a..88b09f4 100644 --- a/docs/port/PROGRESS.md +++ b/docs/port/PROGRESS.md @@ -756,7 +756,7 @@ regex including unverified items, and that lesson is written into the audit's pr - `docs/ARCHITECTURE.md` — the durable reasoning, with a table of what changed and why, the Float→double divergence, and the two iOS settings that are easy to get wrong - `README.md` — status, the parity harness, and the two native bugs this port found -- `docs/V3-BACKLOG.md` — carried over with a header noting the two items whose status +- `docs/BACKLOG.md` — carried over with a header noting the two items whose status changed (UI tests are no longer a gap; elevation can now be checked against Kotlin) - The native repo's `README.md` now opens with a pointer here, and records both bugs