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>
2.3 KiB
V3-14 — GPX interoperability
Phase Quality · Depends on V3-01 for <type> · Size S · Status Not started
Goal
Confirm an exported ride actually imports into Strava, Garmin Connect and Google Earth — and add the activity type so it lands as the right kind of activity.
Context
Export is well tested: 15 tests, parsed with a real XML parser, and byte-identical to the Kotlin original under the parity harness. But every one of those tests proves structural validity, and structural validity does not mean a consumer accepts the file. That gap has been open since v2.
Design
Two parts.
Verification — export a real ride and import it into each of Strava, Garmin Connect and
Google Earth. Record what each does with pauses, elevation and timestamps. Pauses are the
interesting case: <trkseg> per segment is the correct GPX representation, but consumers
vary in whether they honour it.
<type> on <trk> — Strava and Garmin read it to decide the activity. Without it a
bicycle ride may import as a run. Needs V3-01's activity, mapped to each consumer's
vocabulary (Strava uses ride, run, and so on).
Implementation
- Add
<type>to the<trk>element, from the trip's activity - Map the internal enum to GPX conventions; document the mapping in the code
- Export a real multi-segment ride and import it into all three consumers
- Write the findings into this file, including anything that surprises
Acceptance criteria
- A real ride imports into Strava with the right activity type
- It imports into Garmin Connect
- It opens in Google Earth with the path in the right place
- Pause behaviour in each consumer is documented, whatever it turns out to be
- Existing export tests still pass, including the byte-identical parity check —
this one will need updating, since
<type>changes the output
Tests
<type>present and correct per activity- Absent, not empty, when the activity is
other - The parity harness will now differ from Kotlin here. That is expected and correct — update its expectation and note why, rather than dropping the check.
Risks
Silently breaking the parity harness by changing export output. Update it deliberately.
Out of scope
GPX import into Rippr. FIT and TCX formats.