Files
rippr/docs/v3/V3-03-units.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

1.9 KiB

V3-03 — Distance and speed units

Phase Foundations · Depends on V3-02 · Size S · Status Not started

Goal

Imperial as well as metric, chosen once and applied everywhere.

Context

Everything is hardcoded metric: formatDistance switches m/km at 1000, formatSpeed prints km/h, elevation prints metres. Fine in Canada, useless to anyone in the US or UK.

Cheap, and the kind of thing that makes an app feel unfinished when missing.

Design

A UnitSystem enum (metric, imperial) in Config, defaulting from the device locale on first launch.

Conversion belongs in formatting only. Storage stays SI — metres, km/h, metres of altitude — forever. Converting at the storage layer would corrupt every existing ride and break the parity harness.

Value Metric Imperial
Distance m / km ft / mi
Speed km/h mph
Elevation m ft

Implementation

  1. UnitSystem in Config; default from Platform.localeName
  2. Extend ui/format.dart — every formatter takes the unit system
  3. Thread it through: record screen, trips list, trip detail, charts, map legend
  4. Exports stay SI regardless. GPX is metres by specification; changing that breaks consumers.

Acceptance criteria

  • Switching units updates every screen immediately
  • Stored values are unchanged — verified by exporting before and after
  • GPX/GeoJSON output is byte-identical across the two settings
  • First launch picks a sensible default from the locale

Tests

  • Formatter tests for both systems, including the m→km and ft→mi boundaries
  • A test asserting export output does not change with the unit setting
  • Widget test toggling units and checking a rendered label

Risks

The obvious trap is converting too deep in the stack. Guard it with the export test.

Out of scope

Temperature, pace (min/km) — pace is arguably right for running, revisit after V3-01.