One file per feature, in the v2 shape that worked: goal, context, design, implementation, acceptance criteria, tests, risks, out of scope -- written before implementing so the risks are on paper rather than walked into. Three carry more weight than their size suggests. V3-01 ships the port's first real migration, and since the destructive fallback is gone, getting addColumn and a v1-database test right matters more than the feature. V3-08 forces a routing-engine decision with ongoing cost, so it sits behind a RoutingService interface mirroring what LocationSource did for GPS. V3-13 is not code at all -- it answers the three questions open since v2, and V3-15 may close unbuilt as a result, which is a legitimate outcome. Several tickets record constraints that are easy to lose: no activity picker in front of Start, because the founding premise is press-and-go with gloves; the Route-to-Trip foreign key must not cascade, or deleting an old plan deletes the ride; V3-14 will deliberately break the parity harness by adding GPX <type>, and that expectation should be updated rather than the check dropped; and V3-16 must not regress the explicit text colours that exist because of the black-on-black bug. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.3 KiB
V3-01 — Activity type per ride
Phase Foundations · Depends on nothing · Size M · Status Not started
Goal
Every ride records what it was done on — motorcycle, bicycle, scooter, skateboard, running, walking, other — and that choice drives per-activity defaults rather than just labelling the row.
Context
The recording pipeline never cared what you were sitting on: it records positions, speeds and altitudes. Only the UI says "motorcycle". Dylan rides both bikes and motorcycles.
This is also a positioning decision (see ../LAUNCH.md): it determines the store category, the screenshots, and who ever finds the app. Cyclists are a much larger audience and already pay for ride apps. Cheaper to settle before a store listing exists.
This ticket ships the port's first real migration. That matters more than the feature.
Design
Text enum column on Trip, exactly like TripState:
motorcycle · bicycle · scooter · skateboard · running · walking · other
Type drives defaults, not labels. Introduce an ActivityProfile rather than scattering
if (activity == …):
| Setting | Today | Why it must vary |
|---|---|---|
| Speed noise floor | 1.5 km/h | Right for a motorcycle; walking lives near it |
| Histogram bucket | 10 km/h | Useless for running — one bucket. Wants ~1 km/h |
| Accuracy gate | 50 m | A bike at speed tolerates looser fixes than a walker |
| Elevation smoothing window | 15 samples | Tuned for 2 Hz at road speed |
| Map fit zoom | — | A 2 km walk and a 200 km ride differ |
No picker in front of Start. The founding premise is press-and-go with gloves on. Default to the last activity used; make it editable on trip detail next to rename.
Implementation
Activityenum indomain/models.dart;activityfield onTrip- Drift column with
.withDefault(Constant('motorcycle')) - Migration:
schemaVersion1 → 2,m.addColumn(trips, trips.activity) ActivityProfileinstats/holding the constants above; thread it throughcomputeSummary,speedHistogram,Accumulator,isUsableFix- Persist last-used activity in
Config - Trip detail: activity row, editable via the same pattern as rename
- Trips list: show the activity icon on each tile
Acceptance criteria
- A new ride records an activity; existing rides read
motorcycle - A v1 database opens, migrates, and keeps every ride and point
- Changing a trip's activity recomputes its aggregates under the new profile
- Start still takes exactly one tap
flutter analyzeclean, all existing tests still pass
Tests
- Migration test — build a v1 database, migrate, assert rides and points survive.
Drift's
MigrationTestHelperwith a generated v1 schema. - Profile selection: each activity yields its own noise floor and bucket size
- A running-activity ride produces a histogram with more than one bucket
- Round-trip the enum through the database
Risks
- The migration is the risk. Getting it wrong destroys real rides, and the destructive fallback is deliberately gone. Write the migration test first.
- Recomputing aggregates on activity change is easy to forget — a ride switched from motorcycle to walking keeps a wrong moving time otherwise.
Out of scope
Per-activity totals or a stats screen. GPX <type> export (see V3-14).