trails/openspec/changes/fit-parsing-hardening/proposal.md
Ullrich Schäfer 5dd4968626 docs+openspec: prior-art research (Organic Maps, Endurain, wanderer) and 15 proposals
Add docs/inspirations.md as the durable record of the 2026-07-05/06
prior-art research — per-project learnings with source paths, canonical
credit lines, and the changes each spawned — and extend the
acknowledgment lists in philosophy.md/architecture.md (Organic Maps,
Endurain, wanderer).

New OpenSpec changes (proposal/design/specs/tasks each):
- Organic Maps: elevation-profile-hardening, gpx-parser-robustness,
  hiking-time-estimate, poi-index, hiking-foot-profile
- Endurain: account-export, activity-duplicate-review,
  fit-parsing-hardening, activity-locations, self-hosting-guide,
  activity-privacy-controls
- wanderer: federation-hardening, link-share-tokens
- credits-page (user-visible acknowledgments)

Updated in-flight changes with wanderer prior-art sections:
route-federation, route-discovery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 07:23:00 +02:00

2.5 KiB

Why

The shared FIT→GPX converter (apps/journal/app/lib/connected-services/fit.ts) is 40 lines: it takes every record with coordinates and emits one flat track segment. It ignores pause/resume events (moving time and speed bridge lunch breaks), ignores sessions (a multisport file becomes one merged track), passes device altitude through without preferring the corrected enhanced_altitude, and does no coordinate sanity checks. Wahoo already feeds it; the Garmin importer (in flight) and COROS (planned) will too, multiplying every gap. Endurain's 1,100-line FIT handler (reviewed 2026-07-06) is a catalog of exactly which quirks matter in the wild — we adopt the subset relevant to trails' GPX-centric model.

What Changes

  • Pause-aware segmentation: FIT event stop/start pairs (and, as fallback, record gaps > 5 minutes) split the output into multiple <trkseg>s, so downstream moving-time and instant-speed math never bridges a pause.
  • Session-aware output: multi-session files (multisport) produce one <trkseg> per session with records sliced to each session's time window; single-session behavior unchanged.
  • Field preference and validation: prefer enhanced_altitude/enhanced_speed over base fields; drop records with non-finite or out-of-range coordinates or non-finite altitude (altitude → undefined, record kept); require a timestamp per record.
  • Sport extraction: expose the file's session sport/sub-sport mapped to trails' SportType so importers stop guessing (Wahoo importer currently maps workout-type codes itself; it can consume this when a FIT file is present).
  • Not in scope: HR/cadence/power/temperature streams (trails doesn't model them), lap extraction, developer fields, FIT writing (unchanged in @trails-cool/fit).

Capabilities

New Capabilities

Modified Capabilities

  • wahoo-import: the "FIT to GPX conversion" requirement is extended with pause/session segmentation, enhanced-field preference, and record validation guarantees.

Impact

  • apps/journal/app/lib/connected-services/fit.ts — the converter grows the four behaviors above; returns optional sport metadata alongside the GPX.
  • Wahoo importer/webhook + Garmin importer consume the richer return type (backward-compatible: GPX string still primary).
  • Tests: fixture FIT files (pause, multisport, garbage-coordinate) added next to the module; Endurain's backend/tests/.../test_utils_fit.py catalog is the reference for cases.
  • No schema or API changes.