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:
68
rippr-src/docs/v1/README.md
Normal file
68
rippr-src/docs/v1/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# v1 — the original recorder
|
||||
|
||||
v1 was specified as **MotoTrack**, a "bare-bones, highly resilient" recorder to capture GPS
|
||||
telemetry during motorcycle group rides. Renamed to Rippr partway through.
|
||||
|
||||
The original brief is preserved verbatim in [original-spec.md](original-spec.md).
|
||||
|
||||
**Design priority, quoted from the brief:** *"Unbreakable execution over UI beauty."*
|
||||
That framing drove every architectural choice and still holds.
|
||||
|
||||
## What shipped
|
||||
|
||||
A single-screen app with an un-killable foreground service writing GPS fixes to Room, plus
|
||||
a REST uploader. Validated on a real ride — including max speed, which the emulator cannot
|
||||
produce.
|
||||
|
||||
## Deviations from the spec, and why
|
||||
|
||||
The spec was written as complete, paste-ready code. Most of it was sound; these parts were
|
||||
not, and were changed deliberately.
|
||||
|
||||
| Spec said | What shipped | Why |
|
||||
|---|---|---|
|
||||
| `super.onCreate()` in `MainActivity` | `super.onCreate(savedInstanceState)` | Would not compile |
|
||||
| `kapt` for Room | **KSP** | kapt is 2–3× slower and unreliable on modern Kotlin/JDK |
|
||||
| Hardcoded `material3:1.2.1` beside a Compose BOM | Version catalog + BOM | Version conflict |
|
||||
| `insertPoint()` per fix from the callback | Unbounded `Channel` + batched writer | A coroutine per fix gives no back-pressure guarantee; disk latency could block GPS |
|
||||
| Activity-local `isRecording` | Process-wide state (later, in v2, the database) | Lied after process death |
|
||||
| 1 Hz polling of two suspend DAO queries | Room `Flow` | Push, not poll |
|
||||
| No stop action on the notification | Stop action + tap-to-open | |
|
||||
| Nothing countering Doze / OEM killers | Battery-optimisation exemption prompt | A multi-hour ride must survive |
|
||||
| No `Theme.MotoTrack`, no launcher icon | Generated both | Referenced by the manifest but never defined |
|
||||
| "Streams over HTTP/REST" in the goal, no task for it | Full uploader: `synced` column, batched POST, retry, offline-safe | Stated as a goal, so it was built |
|
||||
|
||||
## Verification
|
||||
|
||||
The emulator harness built here was reused throughout v2:
|
||||
|
||||
- Merged manifest checked inside the APK — `foregroundServiceType=0x8` (location) confirmed
|
||||
- 25 synthetic GPS fixes fed; final stored coordinate matched the fed value **exactly**
|
||||
- Foreground service confirmed via `dumpsys` (`isForeground=true`, ongoing notification)
|
||||
- Stop path verified: service gone, notification removed, final flush landed, 81 contiguous
|
||||
point ids
|
||||
|
||||
**Speed was never verified in v1's automated testing** — the emulator reports zero
|
||||
velocity. It was confirmed only on Dylan's first real ride.
|
||||
|
||||
## What v1 lacked
|
||||
|
||||
Feedback after that ride, which became the v2 brief:
|
||||
|
||||
> "app is simple and clean. I like that but it's missing stuff. There is no reset or pause
|
||||
> button, there is no concept of a trip, it captures speed data but not path data."
|
||||
|
||||
Three of those four were the same missing concept — no `Trip` boundary. The fourth was a
|
||||
misconception worth recording: **path data was already being captured.** Every point stored
|
||||
latitude, longitude, altitude and bearing at 1–2 Hz from day one. What was missing was a
|
||||
map to draw it on.
|
||||
|
||||
## Icon work
|
||||
|
||||
The launcher icon was designed in this phase. Five motorcycle-themed concepts were
|
||||
generated locally as SVG, using Google Material Symbols (Apache-2.0) as reference geometry;
|
||||
Dylan picked **Route** — a switchback trace inside a ring.
|
||||
|
||||
Lesson from that round: the first attempt hand-authored bezier paths without ever rendering
|
||||
them, and they were poor. Building a rasterise-and-look loop (`rsvg-convert`) changed the
|
||||
output quality completely. Generators live in `design/`.
|
||||
91
rippr-src/docs/v1/original-spec.md
Normal file
91
rippr-src/docs/v1/original-spec.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Original v1 brief (verbatim)
|
||||
|
||||
Preserved as written, before any of it was implemented or corrected. See
|
||||
[README.md](README.md) for which parts were changed and why — several would not have
|
||||
compiled or would have dropped GPS fixes under load.
|
||||
|
||||
The project was called **MotoTrack** at this point, with package `com.example.mototrack`.
|
||||
Both were renamed to Rippr / `com.rippr` during development.
|
||||
|
||||
---
|
||||
|
||||
## Project Specification: MotoTrack Single-Session Android Recorder
|
||||
|
||||
**Goal:** Build a bare-bones, highly resilient Native Android app (Kotlin) to record GPS
|
||||
telemetry during motorcycle group rides today.
|
||||
|
||||
**Design Priority:** Unbreakable execution over UI beauty. Must run as an un-killable
|
||||
**Foreground Service** that records telemetry to a local SQLite database and streams
|
||||
updates over HTTP/REST when online.
|
||||
|
||||
### 1. Setup & Installation
|
||||
|
||||
Prerequisites: Android Studio (Koala/Ladybug+), a physical Android phone on 8.0+ (API 26+)
|
||||
with USB debugging on, and a cable.
|
||||
|
||||
Project setup: New Project → Empty Activity (Jetpack Compose); Name `MotoTrack`; package
|
||||
`com.example.mototrack`; Minimum SDK API 26; Kotlin.
|
||||
|
||||
Dependencies specified:
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
id("kotlin-kapt")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.android.gms:play-services-location:21.2.0")
|
||||
val roomVersion = "2.6.1"
|
||||
implementation("androidx.room:room-runtime:$roomVersion")
|
||||
implementation("androidx.room:room-ktx:$roomVersion")
|
||||
kapt("androidx.room:room-compiler:$roomVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
|
||||
implementation("androidx.compose.material3:material3:1.2.1")
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Task breakdown
|
||||
|
||||
**Task 1 — Manifest & permissions.** `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION`,
|
||||
`FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_LOCATION`, `POST_NOTIFICATIONS`, `INTERNET`;
|
||||
`MainActivity` exported with a LAUNCHER intent filter; `TrackingService` declared with
|
||||
`android:foregroundServiceType="location"`.
|
||||
|
||||
**Task 2 — Local storage (Room).** `TrackPoint` entity with `id`, `timestamp`, `latitude`,
|
||||
`longitude`, `speedKmh`, `altitudeM`. `TrackPointDao` with `insertPoint`, `getAllPoints`,
|
||||
`getMaxSpeed`, `getPointCount`. `AppDatabase` at version 1 with a `@Volatile` singleton.
|
||||
|
||||
**Task 3 — Un-killable foreground service.** `TrackingService` holding a
|
||||
`FusedLocationProviderClient`, a `CoroutineScope(Dispatchers.IO + SupervisorJob())`, and a
|
||||
`LocationCallback` that converts `loc.speed * 3.6f` to km/h and launches a coroutine per
|
||||
fix to `insertPoint`. `startForeground` with `FOREGROUND_SERVICE_TYPE_LOCATION` on Q+,
|
||||
`START_STICKY`, a `LocationRequest` at `PRIORITY_HIGH_ACCURACY` / 1000 ms with a 500 ms
|
||||
minimum interval, and an `IMPORTANCE_LOW` notification channel.
|
||||
|
||||
**Task 4 — Simple UI & controller.** `MainActivity` with `mutableStateOf` fields for
|
||||
`isRecording`, `maxSpeed` and `pointCount`; a `RequestMultiplePermissions` launcher; a
|
||||
`LaunchedEffect` polling `getMaxSpeed()` and `getPointCount()` every second; and a single
|
||||
button toggling between START and STOP RECORDING.
|
||||
|
||||
### 3. Verification & deployment
|
||||
|
||||
1. Attach device, click Run 'app' in Android Studio
|
||||
2. Grant location and notification permissions
|
||||
3. Tap START RECORDING; confirm the persistent notification appears
|
||||
4. Lock the screen and take a 1-minute test walk/drive
|
||||
5. Unlock: verify Max Speed and Points Captured are updating
|
||||
|
||||
---
|
||||
|
||||
## Notable in hindsight
|
||||
|
||||
- **"Streams updates over HTTP/REST"** appears in the goal but no task defines it. The
|
||||
uploader was built anyway, since it was clearly intended.
|
||||
- **The 1 Hz polling loop** in Task 4 was replaced by a Room `Flow`.
|
||||
- **A coroutine launched per GPS fix** (Task 3) offers no back-pressure guarantee; this
|
||||
became the unbounded `Channel` and single batched writer that still underpins recording.
|
||||
- **`super.onCreate()`** in Task 4 is missing its argument and would not compile.
|
||||
- The verification steps assume Android Studio. Everything ended up driven from the CLI
|
||||
instead — see [../DEVELOPMENT.md](../DEVELOPMENT.md).
|
||||
Reference in New Issue
Block a user