v0.7.0
Breaking
- FIT parsing is now remapped to the official FIT profile. Since 0.6.0, session/lap stats and the sport value were parsed incorrectly
- Migration: re-parse any FIT summaries stored with ≤ 0.6.x.
- FIT encoding fixed accordingly: lap distance and sport values are now written correctly. FIT files exported with ≤ 0.6.x carry these defects; re-export them from source.
- Multi-track GPX is now preserved instead of merged: the first
<trk>becomes the primaryRawActivity; the rest are stored inRawActivity.additionalTracks. Single-track encoders (TCX/FIT/CSV/GeoJSON) merge them automatically via the newRawActivity.flattened(), so conversions keep every point. Butactivity.pointsafter loading a multi-track GPX now contains only the first track. - Default normalization now repairs sentinel values:
RawEditor.trimInvalid()(run byconvert/exportwhennormalize: true, the default) removes Null Island points (lat/lon ≈ 0) and clears elevations ≤ −499 m while keeping the point. Repairs are reported viaRawEditor.repairDiagnosticsand in conversion/export diagnostics (repaired.sentinel_coords_removed,repaired.sentinel_elevation_cleared). Passnormalize: falseto opt out. ValidationResult.isValid/LapValidationResult.isValidnow derive from the structureddiagnosticslist as well as the legacyerrorslist.
Added
Parsers and encoders now round-trip everything a format can hold; anything the target format can't represent is reported via a lossy.* diagnostic (lossy.sets_dropped, lossy.events_dropped, lossy.lengths_dropped, lossy.sessions_dropped, lossy.summary_dropped, lossy.laps_dropped, lossy.multi_track_flattened) instead of being dropped silently. Per format:
- FIT import: multi-session files (e.g. triathlons), timer events, per-length swim data, and unknown record/session/lap fields (
extraFitFields/extraFitArrays) are all preserved; developer fields decode using their declared name, type, scale, and offset. - FIT export: lossless FIT→FIT round-trips: session/lap stats, sets, events, swim lengths, device metadata, and custom channels (including unmapped ones as named developer fields) all write back.
- GPX: waypoints and routes are preserved as structured data (
gpxWaypoints,gpxRoutes) instead of being folded into the track; multi-segment tracks and unknown extensions round-trip. - TCX: power (
ns3:TPXWatts/Speed/RunCadence) now parses as channels instead of being dropped; notes, author, and lap intensity/trigger fields round-trip. - GeoJSON: elevation, per-point timestamps, and feature properties all round-trip; polygons parse as tracks.
- CSV: unknown numeric columns round-trip as custom channels.
New data model and API:
- Swim metrics:
ActivitySummary.poolLengthMeters/.numActiveLengths/.swimStroke/.avgStrokeCount,Lap.numActiveLengths/.swimStroke, theSwimStrokeenum, plusActivitySummary.subSportand.totalCycles. - Strength-training sets: FIT set messages parse into
RawActivity.setsasWorkoutSet(type, repetitions, weight, exercise category; raw id kept for round-trips). - Point-level editing on
RawEditor:insertPoint,deletePointAt,updatePoint,deleteRange,insertPause,removePause, all with lap/set boundary clipping;shiftTimenow also shifts sets. - Batch import:
ActivityFiles.loadBatch(sources)with per-file error capture,onProgress, andstopOnError. - Structured validation diagnostics:
ValidationDiagnostic(stablecode,suggestedFix,priority) on every validation result;ParseDiagnosticgainssuggestedFix/priority; newvalidateDeviceMetadata()andvalidateChannels()validators;DiagnosticCategorydocuments the code prefixes. - Smaller additions:
ActivityFiles.channelSamplesFrom()(channels → export-ready stream samples),Lap.copyWithoutSport(),ValidationResult.fromDiagnostics()/LapValidationResult.fromDiagnostics(), and theParseFidelityModeenum (declared for 0.8.0; this release always usespragmaticNormalize).
Changed
- GPX diagnostics
gpx.wpt.invalid_timestamp/gpx.rtept.invalid_timestampnow say the point is kept with an epoch fallback time (the previous "ignored" wording contradicted the behaviour; codes unchanged).
Fixed
- FIT device metadata was read from the wrong fields: device_info was shifted by one (the serial number as the manufacturer, the hardware revision as the software version), and metadata from paired sensors overwrote the recording device's (a connected speed sensor could replace the head unit's name).
product_nameis now captured asActivityDeviceMetadata.model. - Modern Garmin files no longer collapse to a handful of points: message definitions with more than 96 fields (routine on current devices) were rejected, and multi-byte array fields misaligned every following field in the message. Together, these could reduce a multi-hour ride to a few points and zero laps.
- Real-device laps were silently dropped: the fallback record heuristic (0.6.0) misclassified normal lap messages as GPS records.
- Valid legacy FIT files were flagged as corrupt: the trailer CRC must be computed over header + data, not data alone, and a header CRC of 0x0000 means "not computed" and is no longer compared. 12-byte-header files (including the official FIT SDK sample) and Edge 810-era files now validate cleanly, while genuinely corrupt files are still detected.
- GPX temperatures are now written as decimals:
atemp/wtempwere rounded to whole numbers although the schema types them as decimal (18.5 °C came back as 19).