V3-04 through V3-07, V3-10, V3-16 shipped complete; V3-11/V3-12/V3-14 shipped code-complete pending device/account verification; V3-08/V3-09 deferred behind a new V3-17 (self-hosted OSRM investigation). 316 tests passing, up from 221. The APK is a fresh release build (debug-signed, no release signing config exists yet) with two build fixes applied: core library desugaring enabled for flutter_local_notifications, and sentry_flutter bumped to 9.27.0 (8.14.2's bundled Kotlin plugin was incompatible with this project's Kotlin 2.4.0 toolchain).
81 lines
4.3 KiB
Markdown
81 lines
4.3 KiB
Markdown
# V3-14 — GPX interoperability
|
|
|
|
**Phase** Quality · **Depends on** V3-01 for `<type>` · **Size** S · **Status** Partially done
|
|
|
|
## 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
|
|
1. Add `<type>` to the `<trk>` element, from the trip's activity
|
|
2. Map the internal enum to GPX conventions; document the mapping in the code
|
|
3. Export a real multi-segment ride and import it into all three consumers
|
|
4. 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.
|
|
|
|
## Outcome
|
|
The code half is done; the verification half is explicitly not, and can't be from here.
|
|
|
|
`gpxActivityType(Activity)` maps the internal enum to GPX `<trk><type>` values chosen to
|
|
match Strava's and Garmin Connect's published import vocabularies (`motorcycling`,
|
|
`cycling`, `skateboarding`, `running`, `walking`). `Activity.other` maps to `null`, and
|
|
`gpx()` omits the element entirely rather than writing `<type/>` — an empty element would
|
|
claim "this ride has a type, and it's nothing," which isn't the same fact as "no type was
|
|
recorded." Scooter reuses `motorcycling`: GPX has no dedicated vocabulary entry for it and
|
|
that is the closer of the two categories a consumer actually offers.
|
|
|
|
No existing test needed updating, and there is no byte-identical Kotlin-comparison harness
|
|
for GPX in this repo to speak of — the parity harness described in the original port plan
|
|
compares pure-logic modules (geo, telemetry, ride statistics) against fixtures, not a live
|
|
GPX diff against a Kotlin process. `<type>` is a pure addition; the nine existing GPX tests
|
|
assert specific element counts and positions that a new sibling element doesn't disturb,
|
|
confirmed by running them unchanged. Three new tests added: `<type>` present and correct
|
|
for a mapped activity, absent (not empty) for `other`, and every `Activity` value covered
|
|
without throwing.
|
|
|
|
**Not done, and cannot be done in this environment:** the ticket's actual acceptance
|
|
criteria are entirely device/account verification — export a real ride and import it into
|
|
Strava, Garmin Connect, and Google Earth; confirm the activity type lands correctly;
|
|
document how each consumer treats `<trkseg>` boundaries at a pause. None of that is
|
|
reachable without real accounts on those services and a phone to generate a real multi-
|
|
segment ride. This ticket should be reopened for that verification pass once V3-13's
|
|
real-ride work happens — the two naturally pair, since V3-13 already requires an actual
|
|
ride to exist. `flutter analyze` clean; full suite green (257 tests, up from 254).
|