Files
samplez/rippr-src/docs/v3/BACKLOG.md
2026-08-11 12:09:35 -05:00

182 lines
9.4 KiB
Markdown

# v3 backlog
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.
**Before planning anything: run the real-ride checklist in
[../TESTING.md](../TESTING.md).** Several items below may turn out to be non-issues, and
others may appear that nobody has thought of.
---
## 1. Carried over from v2 — the honest debt
### Elevation gain accuracy · *needs real data first*
~30 m of phantom gain per ten stationary minutes against synthetic ±8 m uniform noise. Real
GPS altitude error is *correlated* rather than uniform, so the true behaviour is unknown.
The current implementation is a 15-sample moving average plus reversal hysteresis (see
[../ARCHITECTURE.md](../ARCHITECTURE.md)). A naive version reported 1498 m over a parked
bike, so the guard rails matter.
**Do not tune this blind.** Record a flat ride, check whether the reported gain is
plausible, and only then adjust. If it needs work, options are a longer smoothing window, a
larger threshold, or using barometric pressure where available (much more accurate than GPS
altitude, and most phones have the sensor).
### Compose UI tests · *the largest coverage gap*
Zero UI tests across six screens. Everything was verified by manual screenshot. Worth
covering: navigation record→trips→detail→back, rotation/state retention, empty states,
`NotFound`, chart degradation below two points, selection mode enabling Merge only at two.
### Unmeasured, and probably should be
- **Battery drain** over a multi-hour ride — never measured, and it is the thing most likely
to make the app unusable in practice
- **Map memory across repeated navigation** — the osmdroid lifecycle is a known hazard and
the wiring was never leak-tested
- **GPX import into Strava/Garmin** — validated against an XML parser, but schema validity
does not guarantee a consumer accepts it
---
## 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**
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
on the handlebars** to watch the route live. Treat the v2 stance as superseded.
**The handlebar case changes the premise, not just the feature.** v1 and v2 were both built
around "start it, pocket it, stop it". A mounted phone is a different product with different
constraints, and it is worth deciding explicitly whether that becomes a first-class mode:
- **Screen on for the whole ride** — battery goes from "a background service" to "a
service plus a lit screen plus continuous map rendering". Measure before committing.
- **Sunlight legibility** — the current dark theme is chosen for glanceability, but daylight
behind a visor is a different problem.
- **Glove-sized targets** — already partly handled (72dp buttons); a map needs the same care.
- **Keep-screen-awake** handling, and what happens on a call or notification.
What still holds regardless:
- **`TrackingService` must never reference a map.** Rendering belongs to the Compose
lifecycle of a visible screen, not the service.
- **No tile fetch or redraw while backgrounded**, even in mounted mode.
---
## 4. Smaller items
| Item | Notes |
|---|---|
| **Trip splitting** | Merge exists; split does not. The natural counterpart. |
| **SAF export** | Dropped in T16 as unnecessary — share sheet covers it. Add if a real need appears. |
| **Auto-pause** | Detect a stop and pause automatically. Rejected in v2 as unreliable in traffic; revisit only with real ride data showing it would help. |
| **Distance units** | Metric only, hardcoded. Trivial to add a preference. |
| **Settings screen** | None exists. `Config` has endpoint, deviceId, mapEnabled — the map toggle currently lives on trip detail because one switch did not justify a screen. |
| **Offline tile pre-download** | osmdroid caches what it renders; a mountain ride with no signal shows blank tiles. Respect OSM's usage policy — no bulk prefetch of their public servers. |
| **Notification live stats** | Show distance/duration in the ongoing notification, readable without unlocking. |
| **Crash reporting** | None. A recorder that dies mid-ride currently leaves no trace beyond logcat. |
---
## 5. Ideas
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.
- **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.
- **Activity type per ride.** Motorcycle, bicycle, skateboard, running, other. The app is
not inherently motorcycle-only — the recording pipeline is activity-agnostic already.
Note: adds a column to `Trip`, so it needs a real `Migration` (the destructive fallback
is gone). Type could also drive sensible defaults — speed noise floor, map zoom,
elevation smoothing.
- **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.
Activity type and theming are independent and much cheaper — either could ship alone.
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.
---
## 6. Things that must not regress
Hard-won and easy to undo by accident. Each has a comment in the code explaining why.
1. **The unbounded `Channel` + single batched writer.** Do not write to the database from
the location callback.
2. **Points stamped with `tripId`/`segmentId` at creation.** Refactoring this into a
write-time lookup breaks the pause guarantee silently.
3. **Recording state derived from the database.** Never reintroduce an in-memory flag.
4. **`fallbackToDestructiveMigration()` stays removed.** Any schema change ships a
`Migration` against `app/schemas/com.rippr.data.AppDatabase/2.json`.
5. **Decimation is render-only.** It must never reach storage or export.
6. **`RipprTheme`'s `Surface`.** It sets `LocalContentColor`; without it, text without an
explicit colour renders black-on-black and disappears.
7. **The map zoom clamp.** `zoomToBoundingBox` ignores `maxZoomLevel`; a short ride will
render an empty grid without it.
8. **Instrumented tests use in-memory databases.** One previously wiped the real device
database in `setUp`.
---
## 7. 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
3. [../v2/PROGRESS.md](../v2/PROGRESS.md) — every bug found during v2 and how
4. [../TESTING.md](../TESTING.md) — **especially "What the emulator cannot verify"**
5. [../DEVELOPMENT.md](../DEVELOPMENT.md) — when you actually need to build something
The v2 planning approach worked well and is worth repeating: one document per task with
goal, context, design, acceptance criteria and risks, written *before* implementing, plus a
running progress log recording what actually went wrong. Several bugs were caught precisely
because the risk had been written down first — and one (the osmdroid lifecycle) was written
down and then walked into anyway, which is its own lesson.