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

View File

@@ -0,0 +1,188 @@
# Development
Everything needed to build, test, and drive Rippr without Android Studio. This was all
done from the command line; the GUI is not required at any point.
---
## Toolchain
### JDK — must be 17–21
**Not 25.** AGP 8.7 does not support it, and the failure is confusing. This machine runs
JDK 25 by default via `mise`, so builds need an explicit `JAVA_HOME`:
```bash
export JAVA_HOME=~/.local/share/mise/installs/java/temurin-21.0.12+8.0.LTS
export PATH="$JAVA_HOME/bin:$PATH"
```
Install with `mise install java@temurin-21` if it is missing.
### Android SDK
```bash
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
```
Needs platform 35 and build-tools 35. `sdkmanager` and `avdmanager` come from
`brew install android-commandlinetools`, but note the brew binaries resolve their own SDK
root and **will not see** `~/Library/Android/sdk` — `avdmanager create` fails with
"Package path is not valid" even when the image is installed. Hand-editing
`~/.android/avd/*.ini` is more reliable than fighting it.
### Disk space — the surprise blocker
The emulator enforces a **fixed ~7.4 GB free-space minimum** before it will boot. This is
not tunable: shrinking the AVD's `disk.dataPartition.size` and passing `-partition-size`
both leave the requirement unchanged.
A full build needs roughly 3–5 GB on top of that. Reclaimable without touching source:
```bash
brew cleanup -s
rm -rf ~/Library/Caches/Homebrew/* ~/Library/Caches/ms-playwright
pip cache purge; go clean -cache
```
---
## Build and test
```bash
./gradlew assembleDebug # debug APK
./gradlew assembleRelease # release APK (unsigned — will not install)
./gradlew testDebugUnitTest # 84 tests, no device needed
./gradlew lintDebug
./gradlew connectedDebugAndroidTest # 46 tests, needs a device
```
Only the **debug** APK installs directly; release is unsigned.
### Reading results without the HTML report
```bash
python3 -c "
import re,glob
t=f=0
for p in glob.glob('app/build/test-results/testDebugUnitTest/*.xml'):
m=re.search(r'tests=\"(\d+)\".*?failures=\"(\d+)\".*?errors=\"(\d+)\"',open(p).read())
t+=int(m.group(1)); f+=int(m.group(2))+int(m.group(3))
print(f'{t} run, {f} failed')
"
```
### Room schema export
Schemas land in `app/schemas/com.rippr.data.AppDatabase/`. **The directory name follows the
`@Database` class package** — when the class moved from `com.rippr` to `com.rippr.data`,
the export path moved with it, which initially looked like the schema had not generated at
all.
---
## Emulator
```bash
emulator -avd Medium_Phone_API_35 -no-window -no-audio -no-boot-anim \
-gpu swiftshader_indirect
adb emu kill # shut down
```
Grant permissions up front so the runtime dialog does not eat your taps:
```bash
for p in ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION POST_NOTIFICATIONS; do
adb shell pm grant com.rippr android.permission.$p
done
```
### Feeding a synthetic ride
```bash
adb emu geo fix <lon> <lat> <altitude>
```
**Coordinate order is longitude first.** See [TESTING.md](TESTING.md) for what this
cannot simulate — it matters more than it sounds.
### Driving the UI
The service is `exported=false`, so `adb shell am start-service` is **correctly refused**.
Drive through the UI, or from an instrumented test running in the app's own process.
Locating a control by its label:
```bash
tapText() {
adb shell uiautomator dump /sdcard/u.xml >/dev/null 2>&1
adb shell cat /sdcard/u.xml | python3 -c "
import sys,re
d=sys.stdin.read()
m=re.search(r'text=\"$1\"[^>]*bounds=\"\[(\d+),(\d+)\]\[(\d+),(\d+)\]\"', d)
print(f'{(int(m.group(1))+int(m.group(3)))//2} {(int(m.group(2))+int(m.group(4)))//2}' if m else 'NONE')
"
}
read x y <<< "$(tapText 'START RECORDING')"; adb shell input tap $x $y
```
### Inspecting the database
`sqlite3` is **not present** on the emulator image. Pull the files instead — and take the
`-wal` too, or recent writes are missing:
```bash
for f in rippr_db rippr_db-wal rippr_db-shm; do
adb exec-out run-as com.rippr cat databases/$f > /tmp/db/$f
done
python3 -c "
import sqlite3; c=sqlite3.connect('/tmp/db/rippr_db')
print(c.execute('SELECT id,state,ROUND(distanceM),pointCount FROM trips').fetchall())
"
```
---
## Hard-won harness lessons
These cost real time. All were false alarms that looked like app bugs.
**Never `sleep` and assume a tap landed.** A cold start took 8.7 s once; a 4-second sleep
produced a silently-missed tap and a false "the service didn't start" conclusion. Poll
`uiautomator dump` for the expected text, then tap.
**Even a confirmed-present control can swallow a tap** right after a fresh install.
`uiautomator` reported the button present, the tap returned success, and nothing happened —
no `databases/`, no service. Repeating it moments later worked.
**Verify database state between UI steps.** Every emulator false alarm in this project came
from trusting a tap instead of checking what actually happened. Checking for the *absence
of the data directory* is what finally made one of them obvious.
**Suspiciously identical results mean a broken harness.** A constant-sweep loop returned
four results identical to six decimal places. The cause was a shell quoting bug that
corrupted the source file while the compile error hid behind `/dev/null`. Never redirect a
build to `/dev/null` inside a measurement loop.
**The notification shade stays open between runs** and will cover the app, making
`uiautomator` report quick-settings tiles instead of your UI. `adb shell cmd statusbar
collapse` first.
---
## Design assets
Logos and icons are generated, not hand-drawn:
```bash
python3 design/gen_logos.py # SVG concepts + PNG previews (needs rsvg-convert)
python3 design/to_vector_drawable.py # → app/src/main/res/drawable/
python3 design/build_preview.py # review page
```
`design/material/` vendors Google Material Symbols (Apache-2.0) as reference geometry.
The launcher icon lives on Android's 108-unit adaptive canvas. **Content must stay inside
the centre 66-unit safe circle** — the first version drew its ring at r=43 and the launcher
mask cropped it clean off, taking the accent segment with it.