v3: promote activity type from an idea to a specified item

It turned out to be a positioning decision rather than a feature -- it decides the
store category, the screenshots and who finds the app, and cyclists are a much
larger audience than motorcyclists. Far cheaper to settle before a store listing
exists.

Specified the schema (a text enum on Trip), and flagged that this is the first
real migration the port will ship: the destructive fallback is gone, so it needs
addColumn with a motorcycle default, schemaVersion 2, and a test that opens a v1
database and asserts the rides survive. Getting that path right matters more than
the feature.

The value is in driving per-activity defaults rather than labels: the 1.5 km/h
noise floor is wrong for walking, 10 km/h histogram buckets are useless for
running, and the elevation smoothing window was tuned for 2 Hz road speed.

Also recorded a UI constraint: no picker in front of Start. The founding premise
is press-and-go with gloves, so default to the last activity used and make it
editable on trip detail afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 10:28:59 -05:00
parent a0f678a2a3
commit af05c5e061

View File

@@ -104,7 +104,68 @@ What still holds regardless:
---
## 4. Smaller items
## 4. 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).
Everything user-facing says motorcycle, but the recording pipeline never did: it records
positions, speeds and altitudes, and nothing in it cares what you are sitting on. Dylan
already rides both bikes and motorcycles.
**Why it matters beyond the feature:** it decides the store category, the screenshots and
who ever finds the app. Cyclists are a far larger audience than motorcyclists and are
already used to paying for ride apps. That question is much cheaper to settle before a
store listing exists than after.
### Schema
An `activity` column on `Trip`, stored as a text enum like `TripState`:
```
motorcycle · bicycle · scooter · skateboard · running · walking · other
```
This is the **first real migration** the port will ship. The destructive fallback is gone
for good, so it needs `m.addColumn(trips, trips.activity)` with a default of `motorcycle`
for existing rows, `schemaVersion` bumped to 2, and a migration test that opens a v1
database and asserts the rides survive. Getting that path right once matters more than the
feature does — every later schema change depends on it.
### Type should drive defaults, not just labels
This is where the real value is, and it is easy to miss:
| Setting | Why it differs |
|---|---|
| Speed noise floor (1.5 km/h) | Fine for a motorcycle; wrong for walking, where real movement lives near it |
| Speed histogram bucket (10 km/h) | Useless for running — everything lands in one bucket. Wants ~1 km/h. |
| Accuracy gate (50 m) | A motorcycle at speed can tolerate looser fixes than a walker |
| Elevation smoothing window | Tuned at 15 samples for 2 Hz road speed; a slower activity covers less ground per sample |
| Map fit zoom | A 2 km walk and a 200 km ride want different defaults |
Treat these as a per-activity profile rather than scattering `if (activity == …)` through
the code.
### Do not put a picker in front of Start
The founding premise is press-and-go with gloves on. A modal asking "what are you doing?"
before recording begins would undo that.
Better: **default to the last activity used**, and make it editable on the trip detail
screen afterwards, next to rename. Most people do the same thing most days, and the one
time they don't, they can fix it after.
### Follow-ons, once the column exists
- **Filter and group the trips list** by activity
- **GPX `<type>` on `<trk>`** — Strava and Garmin read it, so an exported ride imports as
the right activity instead of defaulting to something wrong
- Per-activity totals, if a stats screen ever appears
---
## 5. Smaller items
| Item | Notes |
|---|---|
@@ -119,7 +180,7 @@ What still holds regardless:
---
## 5. Ideas
## 6. Ideas
Terse on purpose. Unshaped, to be consolidated later.
@@ -130,11 +191,6 @@ Terse on purpose. Unshaped, to be consolidated later.
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
@@ -151,7 +207,9 @@ Terse on purpose. Unshaped, to be consolidated later.
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.
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
@@ -160,7 +218,7 @@ standalone.
---
## 6. Things that must not regress
## 7. Things that must not regress
Hard-won and easy to undo by accident. Each has a comment in the code explaining why.
@@ -181,7 +239,7 @@ Hard-won and easy to undo by accident. Each has a comment in the code explaining
---
## 7. Reading order for picking this up cold
## 8. 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