Refresh Rippr snapshot and bundle with full project documentation

Re-exported at 46a0726, which adds README.md plus docs/ARCHITECTURE,
DEVELOPMENT, TESTING, v1 history including the original brief, and a v3
backlog. 112 files, and the bundle now carries 19 commits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 08:44:48 -05:00
parent 280fd7f988
commit 247b9cdb3f
11 changed files with 1025 additions and 44 deletions

116
rippr-src/README.md Normal file
View File

@@ -0,0 +1,116 @@
# Rippr
A native Android GPS ride recorder for motorcycles. Records telemetry from an
un-killable foreground service into a local SQLite database, renders the traversed path
on a map afterwards, and exports to GPX/GeoJSON.
Built for one specific use: **hit start, put the phone in a pocket, ride, hit stop.**
Every design decision below follows from that.
```
Package com.rippr minSdk 26 (Android 8.0) targetSdk 35
Kotlin 2.0.21 AGP 8.7.3 Gradle 8.11.1
~5,800 lines Kotlin 84 unit tests 46 instrumented tests
```
## Current state — v2.0.1
| Feature | Status |
|---|---|
| GPS recording via foreground service | Working, validated on real rides |
| Trips with pause/resume/stop/discard | Working |
| Live speed + elapsed clock | Working (fixed in 2.0.1) |
| Path rendered on OpenStreetMap | Working (fixed in 2.0.1) |
| Ride statistics + charts | Working; elevation gain has known drift |
| Rename / delete / merge rides | Working |
| GPX + GeoJSON export | Working, validated against an external parser |
| Upload to a REST endpoint | Implemented, **no UI** — see below |
| Live map during recording | Deliberately absent — see below |
| Group ride view | Deferred to v3 |
## Quick start
Requires **JDK 17–21** (AGP does not support 25) and the Android SDK with platform 35 and
build-tools 35. Full setup in [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md).
```bash
echo "sdk.dir=$HOME/Library/Android/sdk" > local.properties
./gradlew assembleDebug testDebugUnitTest lintDebug
./gradlew connectedDebugAndroidTest # needs a device or emulator
```
## Architecture in one page
```
TrackingService (foreground)
└─ FusedLocation callback ──trySend──► unbounded Channel
│
single writer coroutine (batches of 25, ~2s)
│
┌───────────────────┴────────────────────┐
▼ ▼
Room database Accumulator
Trip → Segment → TrackPoint distance / moving time /
│ elevation, folded per batch
▼
TripRepository (all lifecycle transitions)
│
┌─────────────────┼──────────────────┐
▼ ▼ ▼
RecordScreen TripsScreen TripDetailScreen
└─ RideMap (osmdroid)
```
Three decisions carry most of the weight:
**The location callback never blocks.** Fixes go into an unbounded `Channel`; a single
writer coroutine drains it in batches. Slow disk stalls the writer, never GPS, and no fix
is dropped under back pressure.
**Recording state lives in the database, not memory.** A row in `trips` with
`endedAt IS NULL` *is* the fact of an in-progress ride. It survives process death, which
an in-memory flag cannot — `START_STICKY` restarts the service with a null intent and a
flag would come back `false` mid-ride.
**Segments make pause correct rather than cosmetic.** Without them, pausing at a gas
station and resuming across town draws a straight line through terrain never ridden, and
counts it as distance. Segments propagate all the way through: distance accumulation, map
polylines, and GPX `<trkseg>`.
Full reasoning in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
## Two deliberate absences
**No live map while recording.** The phone is in a pocket; nobody is looking at it. A live
map would burn battery on top of GPS and a wake lock for nothing. The map is a post-ride
artifact, gated behind a toggle, and `TrackingService` holds no reference to any map type.
**No UI for the upload endpoint.** The REST uploader works and is tested, but is only
reachable via `Config.setUploadEndpoint()`. It has no server to talk to yet; the UI arrives
when group-ride streaming does.
## Documentation
| Document | Contents |
|---|---|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Durable design decisions and the reasoning behind them |
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Toolchain setup, build/test commands, emulator harness |
| [docs/TESTING.md](docs/TESTING.md) | What is covered, what is not, and what the emulator cannot verify |
| [docs/v1/](docs/v1/) | Original spec, what shipped, and how it deviated |
| [docs/v2/](docs/v2/) | 18-task plan, per-task docs, and the progress log with every bug found |
| [docs/v3/BACKLOG.md](docs/v3/BACKLOG.md) | Known gaps, deferred work, and ideas for next |
**Start here for v3:** [docs/v3/BACKLOG.md](docs/v3/BACKLOG.md), then
[docs/v2/PROGRESS.md](docs/v2/PROGRESS.md) for the bugs that were found and why.
## Known gaps
1. **Elevation gain drifts** ~30 m per ten stationary minutes against synthetic noise.
Real GPS error is correlated rather than uniform, so the true figure needs a real ride.
Flat ground should read near zero.
2. **No Compose UI tests** for any of the six screens. Verification was manual.
3. **Speed colouring on the map is unvalidated** — the emulator reports zero velocity, so
the path renders uniformly there.
4. **Migrations are now mandatory.** `fallbackToDestructiveMigration()` was removed in v2;
any schema change must ship a `Migration` against
`app/schemas/com.rippr.data.AppDatabase/2.json`.