Files
rippr/docs/v3/V3-01-activity-type.md
Dylan 342a8f8382 Break v3 into 16 tickets
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>
2026-08-17 10:41:35 -05:00

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

  1. Activity enum in domain/models.dart; activity field on Trip
  2. Drift column with .withDefault(Constant('motorcycle'))
  3. Migration: schemaVersion 1 → 2, m.addColumn(trips, trips.activity)
  4. ActivityProfile in stats/ holding the constants above; thread it through computeSummary, speedHistogram, Accumulator, isUsableFix
  5. Persist last-used activity in Config
  6. Trip detail: activity row, editable via the same pattern as rename
  7. 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 analyze clean, all existing tests still pass

Tests

  • Migration test — build a v1 database, migrate, assert rides and points survive. Drift's MigrationTestHelper with 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).