Activity Explorer is a local, open-source Blazor application for exploring your cycling, running, walking, and rowing history. It works from files you own - Garmin account exports, Strava bulk exports, and individual FIT, GPX, TCX, GZ, or ZIP files - without API credentials, web scraping, or automated access to Garmin or Strava.
Version 0.1.0 is deliberately local-first and has no login system. It binds to localhost by default and stores the database, imported originals, and logs outside the repository.
- Imports official Garmin and Strava account exports and individual activity files.
- Provides official-history ZIP, bounded multi-file, and local-inbox workflows without accepting provider credentials.
- Keeps immutable copies of every distinct imported original.
- Deduplicates exact files and equivalent activities across formats while retaining provenance.
- Shows dashboards, searchable activities, axis-labelled and synchronized gap-aware sensor/respiration charts with pointer and keyboard inspection, rich FIT summaries, laps, records, routes, segments, and a combined world map.
- Shows activity count, distance, moving time, and ascent for the complete filtered result, with shared date shortcuts on Activities and World Map.
- Maps distinct activity frequency and exploration across recorded GPS history, with visited/new cells, first discoveries, and a cumulative timeline. See Maps.
- Organizes activities with profile-owned tags and named saved searches, and reviews exact batches of tag or gear changes before applying them atomically.
- Compares training periods, charts weekly or monthly progress across five summary metrics, and opens daily activity lists from a monthly training calendar.
- Tracks personal distance, moving-time, ascent, activity-count, and active-day goals with recurring editions, pace guides, history, and contributing activities.
- Charts each activity’s best benchmark attempt and the best result so far within a selected period, separately for each profile.
- Deletes one activity, selected activities, or an exact snapshot of the current filtered results with an inline permanent-deletion confirmation.
- Supports separate local owner profiles and an "All profiles" aggregate view.
- Calculates ordered cycling, running, walking, and rowing distance bests, timed distance bests from 1 minute through sport-specific multi-hour targets, 5-second through 2-hour power bests, and directional local segment efforts.
- Awards retrospective metric badges, related calendar editions, points, and progressive levels per profile, with monthly snapshots and activity evidence. See Badges and levels.
- Creates local segments from reviewed GPX, FIT segment/course, TCX, KML, and GeoJSON paths, with optional trimming and direction reversal.
- Organizes nested, shared sub-segments with map highlights and per-pass results inside the selected parent effort, including closed loops and repeated laps.
- Compares two segment efforts with a distance-aligned time gap, paired sensor charts, synchronized map inspection, and corresponding child-pass results. See Comparing two efforts.
- Uses locally vendored MapLibre with a blank basemap by default; OpenFreeMap is a persistent, explicit global opt-in.
- Watches optional local folders without moving or deleting the files in them.
Activity Explorer does not use the Strava API, Garmin Connect API, OAuth credentials, provider passwords, automated Connect access, or a proprietary segment catalog. Generic user-supplied path files create independent local definitions; they do not establish provider identity or synchronization. Automatic phone-to-app synchronization is not available in 0.1.0.
Prerequisites:
- .NET SDK 10.0.303 is preferred;
global.jsonaccepts a later .NET 10 feature band when that exact SDK is unavailable. - An official account export or one or more FIT/GPX/TCX files.
Run:
dotnet restore ActivityExplorer.slnx --locked-mode
dotnet run --project src/ActivityExplorer.WebOpen http://localhost:8342, create a profile, and open Imports. Choose Complete Garmin history, Recent files, or Local inbox. Work continues in a durable background queue, and reports remain available after completion.
On Bash shells, the same dotnet commands work unchanged.
Select one profile on Activities to manage tags, filter by Tags — match any, or save an applied search. All profiles still shows activity tags. Names are unique within a profile, ignoring surrounding whitespace and letter case. Tags can also be edited separately on activity detail.
Saved searches retain filters, sorting, and the chosen date period. Opening a relative period resolves it again; custom dates stay fixed. Use Replace with applied filters to update a search explicitly. A deleted tag blocks that search until you choose replacement tags or explicitly save its removal. Tag bookmarks opened under another profile offer a profile-switch action.
Select activities across pages, then choose Edit selected, or use Edit all matching for the applied filter. Review tag additions/removals and unchanged, set, or cleared gear before applying. The review fixes the exact activity list; later imports are excluded. Any intervening activity edit invalidates the whole review and requires a refresh. Gear changes protect local metadata from import enrichment; tag-only changes do not. Deletion remains a separate action.
Open Goals, then New goal, and choose the owner explicitly. Set a name, one sport or all sports, and a positive target in kilometres, hours, metres, activities, or days. Activities and active days require whole numbers. Choose an inclusive one-off range or weekly, monthly, or yearly recurrence. Dates use the owner's current reporting timezone; weeks start Monday. Past and future starts are supported. A partial first edition runs from the effective date to the period's end and keeps the full target.
Each edition shows its target, actual amount, percentage, remaining amount, and contributing activities. Active days count distinct local activity-start dates, without a minimum-duration rule. All matching stored summaries count, including future-dated activities inside the edition's dates. Progress can exceed 100%; only the bar stops at 100%. Upcoming, Active, or Ended describes the period separately from whether the target is reached.
Active editions show two guides: the target multiplied by completed local days divided by edition days, and the remaining amount divided by remaining days. Today is unfinished and included in remaining days. These are guides, not predictions; upcoming and ended editions omit them.
Name, target, and sport are editable; metric, recurrence, and dates remain fixed. Edit this edition preserves any scheduled future definition. Edit next editions changes the next and following editions. Before the first start, Edit initial definition changes the initial definition. One-off goals remain editable until their range ends; ended definitions stay fixed. The editor shows the effective dates before saving. If another edit or a calendar boundary makes the form stale, reload the goal before retrying.
Archive goal retains history and the complete current edition while stopping later editions. Archiving before the first start prevents all editions from beginning. There is no delete or reactivate action. Imports, corrections, deletions, transfers, and timezone changes can revise progress even for historical editions; saved targets remain unchanged.
The Goals list has Current and upcoming, All, and Archived views. All profiles shows separate owner-labelled goals. Detail links retain the selected edition and history/contributor pages through refresh and Back/Forward. The dashboard's Current goals shows up to six active editions ordered by ending date, including an archived goal until its current edition ends. It follows the profile selector and each goal's own dates and sport, independently of dashboard filters.
Docker is optional:
docker compose up --buildThe compose configuration publishes only 127.0.0.1:8342 and stores application data in the activity-explorer-data volume. Stop it with docker compose down. Do not add -v unless you intentionally want Docker to delete the volume and all imported data.
By default the application uses the operating system's local application-data folder:
- Windows: %LOCALAPPDATA%\Activity Explorer
- Linux: normally ~/.local/share/Activity Explorer
- macOS: the platform local application-data location returned by .NET
Set ACTIVITY_EXPLORER_DATA to use a different absolute location:
$env:ACTIVITY_EXPLORER_DATA = "D:\ActivityExplorerData"
dotnet run --project src/ActivityExplorer.WebThe directory contains:
- activity-explorer.db: SQLite metadata and summaries.
- originals/: immutable, content-addressed activity files grouped by profile.
- staging/: temporary imports; interrupted work is retained for restart recovery.
- logs/: bounded rolling diagnostics.
- quarantine/: profile directories or unreferenced activity originals awaiting durable cleanup after database deletion.
- keys/: persistent ASP.NET Core data-protection keys; include them in a complete backup.
Back up the whole application-data directory while Activity Explorer is stopped. See Data storage and privacy.
| Input | Support |
|---|---|
| Garmin account-export ZIP | Documented uploaded-files layouts, nested archives, and irrelevant-wellness filtering |
| Strava bulk-export ZIP | Original files enriched from activities.csv |
| FIT | Official SDK parsing for timing, laps, track, sensors, respiration, temperature, calories, and training fields when recorded |
| GPX / TCX | Hardened streaming XML readers |
| GZ / ZIP | Safely expanded with traversal, symlink, nesting, count, and size limits |
| Local segment path | One GPX, FIT segment/course, TCX, KML, or GeoJSON path; reviewed sport, trim, direction, and tolerance |
Only cycling, running, walking, and rowing activities are imported. Indoor and virtual variants map to their base sport. Other sport files are reported and skipped. Segment path uploads are parsed into local geometry and then discarded; only the safe file name and normalized format remain as provenance.
See Importing data and Legal and export guides.
Choose a Period on Activities or World Map, then Apply filters. Shortcuts include Today, This week, Last week, This month, Last month, Year to date, Last year, and All time. Custom range includes both endpoint dates and allows either endpoint to be blank. Weeks run Monday–Sunday; This week and This month include the full calendar period, while Year to date ends today.
Dates use the activity owner's Reporting timezone in Profiles, which defaults to Europe/Copenhagen. In All profiles, each owner's local start date and local today determine membership. Activities shows owner-local dates and totals across every matching page, including moving durations longer than 24 hours. World Map applies the period to activity lines, Frequency, and Exploration Range; Cumulative uses its separate inclusive date. Routes and segments retain their owner/sport filters.
Relative date URLs resolve again on refresh; sorting and pagination retain the displayed period. Invalid dates and reversed ranges remain visible for correction. Editing filters clears activity selection and deletion confirmation, and requires applying the filters before selection or deletion becomes available again. An all-filtered deletion confirms exact IDs, excluding activities imported after confirmation. See Maps for date URL parameters.
Dashboard opens at Year to date, compared with the same dates last year. Select a sport and reporting period, then Apply filters. The five totals are activities, distance, moving time, ascent, and active days. Any matching activity makes its owner-local start date active; multiple profiles sharing a date count as one active day. Badge duration requirements do not apply.
Previous period compares the same number of calendar days immediately before the selected range. Prior-year dates shifts each endpoint back one year, clamping February 29 to February 28. Both ranges are shown. Differences are signed, and percentage change is unavailable when the comparison total is zero. All time and open-ended custom ranges remain available but need both endpoints before totals can be compared.
Choose weekly or monthly trends and any of the five metrics. Empty buckets are zero; partial weeks or months include only the selected dates. Sport breakdown, recent activities, and equipment follow the same filters. Record highlights remain labelled Lifetime records; their progress links open the selected period’s benchmark history. Use Refresh dashboard to include later committed imports or edits.
Calendar shows a separately selected month, grouped by sport, with day and week totals. Weeks start Monday, and partial week totals include only days in the displayed month. Open a date to see all matching activities. The responsive view becomes a compact list when space is limited; an explicit Calendar or List choice stays in the URL. Month navigation, filters, dashboard trend controls, and benchmark view/period survive refresh and browser Back/Forward. The selected profile remains session-wide.
MapLibre renders local activity, route, and segment geometry on a blank basemap by default. In this mode every map makes zero requests to external map hosts. You can explicitly enable OpenFreeMap for all maps under Settings; the choice persists locally. Online style and tile requests reveal the viewed area to the configured provider, but do not upload stored activity files or tracks.
OpenStreetMap attribution remains visible whenever the online basemap is active. Route and other map queries are viewport-bounded, including viewports that cross the antimeridian. See Maps.
This is a no-login personal application. Localhost binding is intentional.
Do not expose it to a LAN, reverse proxy, tunnel, or the public internet. Anyone who can reach the application can view location and health data, download imported originals, edit activities, and permanently delete activities or profiles.
Activity deletion uses an inline confirmation that names the affected activity or exact selected/filtered count. Profile deletion requires entering DELETE followed by the profile name. Both are destructive. A profile JSON export describes the local records, but it is not a full backup of original files.
src/
ActivityExplorer.Core/ Domain models, DTOs, and contracts
ActivityExplorer.Infrastructure/ SQLite, imports, calculations, background work
ActivityExplorer.Web/ Blazor Interactive Server host and UI
tests/
ActivityExplorer.Tests/ Unit and SQLite integration tests
docs/ User, architecture, privacy, and legal guides
The design keeps EF entities out of Razor components and exposes testable services such as IActivityImporter, IActivityQueryService, ISegmentMatcher, IRouteService, and IStatisticsService.
dotnet restore ActivityExplorer.slnx --locked-mode
dotnet format ActivityExplorer.slnx --verify-no-changes --no-restore
dotnet build ActivityExplorer.slnx --configuration Release --no-restore -m:1 -p:BuildInParallel=false
dotnet test tests/ActivityExplorer.Tests/ActivityExplorer.Tests.csproj --configuration Release --no-build --no-restoreFor a release candidate, also run the coverage gates, package advisory/deprecation check, isolated Playwright regressions, and loopback-only Docker smoke test documented in Testing.
No default test performs a live Garmin or Strava request. Test data is synthetic and fictional.
- Architecture
- Understanding charts
- Records methodology
- Badges and levels
- Segment methodology
- Testing
- Troubleshooting
- Dependency and license inventory
The Activity Explorer source code is MIT licensed. Dependencies retain their own licenses. In particular, Garmin's official FIT SDK uses Garmin's separate FIT Protocol License and is restored from NuGet rather than redistributed in this repository; review that license before building or distributing the application. See Dependency and license inventory.
Activity Explorer is an unofficial product and is not affiliated with, endorsed by, or sponsored by Garmin, Strava, OpenFreeMap, or OpenStreetMap. Product names are used only to identify compatible user-supplied export formats. No third-party logos, credentials, or tokens belong in this repository.