# V3-14 — GPX interoperability **Phase** Quality · **Depends on** V3-01 for `` · **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: `` per segment is the correct GPX representation, but consumers vary in whether they honour it. **`` on ``** — 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 `` to the `` 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 `` changes the output** ## Tests - `` 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 `` 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 `` — 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. `` 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: `` 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 `` 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).