Skip to content

Latest commit

 

History

History
1103 lines (749 loc) · 37.8 KB

File metadata and controls

1103 lines (749 loc) · 37.8 KB

Strava Dashboard Technical Guide

Purpose

This document describes the current implementation of Strava Dashboard as an engineering system. It covers the product surface, architecture, ETL flow, feature engineering, visualization design, caching, authentication, detailed tab behavior, and the advanced activity-analysis pipeline.

It is intended for maintainers, collaborators, reviewers, and technically oriented users who need to understand how the application works beyond the top-level README.

1. System Overview

Strava Dashboard is a browser-first analytical application built around Strava activity data. It combines a static frontend, a small Vercel-based backend proxy layer, and a significant amount of client-side analytics code.

The system has two major operating modes:

  1. A multi-tab dashboard experience driven by preprocessed activity summaries.
  2. A deep per-activity analysis experience driven by Strava streams and point-level analysis.

The product design philosophy is explicit in the implementation:

  • keep most user data in the browser
  • avoid an application database
  • use serverless endpoints only where secrets or token refresh are required
  • favor explainable heuristics and transparent formulas over opaque models

2. Current Product Surface

Main tab routes

The SPA exposes the following route-to-tab mapping through the History API and Vercel rewrites:

  • / and /run: Run analysis
  • /dashboard: Dashboard
  • /bike: Bike
  • /swim: Swim
  • /athlete: Athlete
  • /planner: Predictor
  • /gear: Gear
  • /activities: Activities
  • /calendar: Calendar
  • /weather: Weather
  • /map: Map
  • /wrapped: Report
  • /ai-coach: AI Coach

Dedicated pages

The repository also includes page-level controllers and HTML shells for dedicated detail views:

  • html/activity-router.html
  • html/activity.html
  • html/run.html
  • html/bike.html
  • html/swim.html
  • html/gear.html
  • html/wrapped.html

These pages support richer, activity-specific or gear-specific exploration outside the main tab shell.

3. Architecture And Module Boundaries

Frontend architecture

The frontend is implemented as native browser ES modules without a bundler. The architecture is intentionally layered:

  • js/app/ — bootstrap (main.js), OAuth and demo gating (auth.js), navigation and shared UI orchestration (ui.js).
  • js/services/ — API clients and browser cache helpers (api.js, barrel index.js).
  • js/shared/ — cross-surface helpers:
    • shared/preprocessing/core.js exports preprocessActivities(activities, userProfile, zones, gears) and the supporting derivation helpers (TSS, CTL, ATL, TSB, efficiency, moving_ratio, weather enrichment hooks).
    • shared/utils/core.js exports the formatters and math utilities consumed everywhere (dates, paces, speeds, sport emoji, rolling means, etc.).
    • shared/utils/weather-analysis.js aggregates per-run weather summaries; shared/utils/speed-insights.js wires Vercel Speed Insights.
  • js/tabs/ — one renderer module per main SPA tab plus a barrel (index.js), tab-local helpers (utils.js), and API surface (api.js).
  • js/pages/ — controllers for dedicated detail pages, organised per sport: activity/, run/, bike/, swim/, gear/. The activity page wires advanced-analysis.js against the analysis pipeline.
  • js/analysis/ — stream-level pipeline. preprocessing.js cleans streams (GPS spikes, Hampel altitude filter, speed-spike cleanup, smoothing). analyzers/ contains sport-specific analyzers (running, trail-run, cycling, gravel-mtb, hiking) extending base-analyzer.js. detection/ holds climb and stop detectors. segmentation/ produces distance/time/terrain splits. engines/ contains fatigue.js, aero.js, physiology.js, and insights-generator.js. export/ emits GPX, CSV, and JSON.
  • js/models/ — analysis-domain typed objects: activity-track.js, track-point.js, segment.js, climb.js, analysis-result.js.
  • js/demo/index.js exposes isDemoMode(), setDemoMode(), loadDemoData(), clearDemoData(), gated through the strava_demo_mode localStorage flag. generator.js synthesises ~250 activities; polylines.js provides demo route geometry.
  • scripts/local-dev-server.mjs — Node http server backing npm run dev. Serves static assets and dynamically imports the corresponding api/<name>.js module for /api/* requests so OAuth and Strava proxies work locally.

Backend architecture

The backend is intentionally thin. It exists mainly to:

  • exchange authorization codes for Strava OAuth tokens
  • refresh access tokens when they are close to expiry
  • proxy Strava endpoints so secrets remain server-side

Key API modules:

  • api/strava-auth.js
  • api/strava-activities.js
  • api/strava-activity.js
  • api/strava-athlete.js
  • api/strava-gear.js
  • api/strava-streams.js
  • api/strava-zones.js
  • api/_shared.js

Routing model

The SPA is path-based rather than hash-based. vercel.json rewrites known tab routes to index.html, while API and static assets remain distinct. This yields clean URLs such as /run and /weather without introducing a full frontend router framework.

4. Authentication, Session Handling, And Security Model

OAuth flow

Authentication uses Strava OAuth 2.0:

  1. The frontend triggers a redirect to Strava's authorization endpoint.
  2. Strava returns an authorization code to the configured redirect URI.
  3. The frontend posts the code to /api/strava-auth.
  4. The backend exchanges the code for an access token, refresh token, and expiration timestamp.
  5. The frontend stores those tokens in localStorage.

Token transport and refresh

Subsequent API requests send a base64-encoded serialized token payload through the Authorization header. The serverless layer decodes it, checks expiry, and refreshes the access token when needed. If refresh occurs, updated tokens are returned in the response payload and written back to localStorage.

Security posture

The application makes a privacy-forward tradeoff:

  • no database is used
  • tokens are not persisted server-side
  • most analytics run locally

This keeps architecture simple and user data localized, but it also means the browser is the effective session store.

5. Caching And Local Persistence

Local cache durations

The client-side cache stores payloads with timestamps and applies differentiated TTLs:

  • athlete profile: 24 hours
  • training zones: 24 hours
  • gear: 24 hours
  • activities: 1 hour

Cached data keys

Notable persisted keys include:

  • strava_tokens
  • strava_athlete_data
  • strava_training_zones
  • strava_gears
  • strava_activities (localStorage fallback only when IndexedDB is unavailable)
  • strava_cache_version — schema version tracked in localStorage metadata
  • strava_demo_mode / strava_demo_activities — demo gating and dataset
  • dashboard_filters
  • dashboard_settings
  • training_goals — user-defined km/hours/activities goal configuration
  • ai_chat_history
  • gemini_api_key
  • gear-specific custom configuration records

IndexedDB details for activity cache:

  • Database: strava-dashboard-cache
  • Store: entries
  • Primary key: key (strava_activities entry)
  • Stored value: raw activities array + timestamp + cache version

Cache schema versioning

js/app/main.js declares const CACHE_VERSION = 'v2-efficiency-moving-ratio' and validates cache entries through js/services/activity-cache.js.

At startup, getCachedActivities({ cacheVersion, maxAgeMs }) checks both schema version and age before serving cached activities. Cache writes go through saveCachedActivities(...), which stores large payloads in IndexedDB and keeps lightweight metadata in localStorage. If IndexedDB is not available, the service automatically degrades to localStorage.

Every time preprocessing semantics change, the CACHE_VERSION literal must be bumped so stale payloads are ignored and regenerated on next load.

Why this matters operationally

The cache strategy reduces repeated API calls while still favoring reasonably fresh activity data. It is a pragmatic middle ground between stateless behavior and persistent server-side storage.

6. ETL Pipeline

The codebase implements a practical ETL pipeline, even though it is not packaged as a traditional data platform.

6.1 Ingestion

The ingestion stage pulls:

  • paginated Strava activity history
  • athlete metadata
  • training zones
  • gear metadata
  • detailed activity streams
  • historical weather records for eligible runs

api/strava-activities.js requests athlete activities with per_page=100 and continues fetching until a page returns no rows. This guarantees complete history retrieval, which is a good analytical default but can stress rate limits for athletes with long histories.

6.2 Cleaning

For activity collections, the preprocessing layer performs broad cleanup and enrichment tasks such as:

  • cache validation and parse-guarding
  • targeted swim distance corrections for a known historical issue
  • null-safe defaults in metrics
  • local bucketing without UTC date drift for many views

For stream-level activity analysis, the cleaning pipeline performs more sophisticated operations:

  • GPS spike detection and interpolation
  • altitude outlier replacement with a Hampel-style filter
  • speed spike cleanup
  • smoothing of altitude, speed, and grade series

6.3 Transformation

Transformations generate normalized analytical fields such as:

  • pace and speed representations by sport
  • TSS and related load metadata
  • weekly and monthly aggregates
  • route point arrays decoded from polylines
  • activity-type and bike-type classification labels
  • pool/open-water labels and pool-length estimates

6.4 Feature extraction

Feature extraction generates the signals used by charts and advanced analytics:

  • CTL, ATL, TSB, rolling load, and injury-risk proxy values
  • environmental difficulty scores
  • distance, elevation, pace, power, HR, and cadence summaries
  • time-in-zone distributions
  • climb, stop, and segment objects
  • fatigue onset and severity markers
  • VAM, GAP, and terrain-derived summaries

6.5 Visualization-ready outputs

The final ETL outputs are view-specific aggregated tables and point sets used for:

  • summary cards
  • line charts
  • scatter plots
  • pie charts
  • histograms
  • calendar heatmaps
  • matrix heatmaps
  • geographic layers
  • sortable tables

7. Data Engineering Details

Missing values and fallback logic

The code is defensive in several places:

  • HR-dependent metrics fall back to simpler estimates if HR is missing.
  • TSS computation can use power, HR zones, HR ratio, suffer score, or time-only approximations.
  • Swim type inference falls back on trainer and GPS availability.
  • Route and stream analyses degrade if optional series such as cadence or watts are absent.

Outlier and noise handling

The detailed analysis pipeline is where most signal hygiene is enforced:

  • GPS points with unrealistic distance jumps are interpolated.
  • Altitude anomalies are replaced with robust local medians.
  • Speed outliers are dampened using median-based local windows.
  • Derived metrics are recomputed after denoising to keep downstream outputs internally consistent.

Aggregation strategies

The dashboard relies heavily on grouping and windowing:

  • day-level grouping for heatmaps and streaks
  • ISO-style week grouping for weekly trends and top-week logic
  • month-level grouping for annual and seasonal summaries
  • rolling mean windows for training trends
  • EMAs for ATL and CTL

Smoothing and normalization

At the activity-detail layer, the code supports smoothing controls that change how stream charts are rendered. This is useful because stream-level HR, cadence, altitude, and power curves can otherwise be too noisy to interpret.

Normalization strategies include:

  • unit conversions by sport and chart context
  • distance normalization for pace-per-100m and elevation-per-km
  • interpolation within HR zones for a refined zone-weighted intensity factor

Sport-aware formatters

All inline pace and speed rendering across js/tabs/*.js and js/pages/**/*.js routes through the helpers in js/shared/utils/core.js:

  • formatPaceRun(secondsPerKm) returns "M:SS /km" for running pace; "—" for null/NaN/non-positive input.
  • formatPaceSwim(secondsPer100m) returns "M:SS /100m" for swim pace; same guard.
  • formatSpeedBike(kmh) returns "X.X km/h"; guards against null/NaN.
  • formatSportPace(value, sport) is the dispatcher: routes to swim/bike/run helpers based on a case-insensitive Swim/Ride|Bike|Cycling/Run match on the sport string, defaults to run pace.

Use these when rendering tables, summary cards, or scatter-plot tooltips. Use formatDate(date) (also in shared/utils/core.js) for any DD/MM/YYYY date display. Do not inline new Math.floor(.../60) + ":" + (... % 60) constructions.

New per-activity fields

preprocessActivities populates three fields beyond the legacy TSS/load/weather/VO2max set:

  • efficiency — sport-aware aerobic-efficiency ratio. Run = pace_min_per_km / avgHR (lower better). Swim = pace_min_per_100m / avgHR (lower better). Bike = avg_speed_kmh / avgHR (higher better). null when avgHR, distance, or moving_time are absent or non-positive.
  • efficiency_method'pace_per_hr' | 'pace100m_per_hr' | 'speed_per_hr' | null. Use this when comparing efficiency values across activities to ensure they share units.
  • moving_ratiomoving_time / elapsed_time as a decimal in [0, 1]. null when elapsed_time <= 0.

Whenever the set of derived fields is extended, the CACHE_VERSION literal in js/app/main.js must be bumped.

8. Analytics, Models, And Heuristics

8.1 Training load and performance management

The dashboard computes a familiar endurance-training trio:

  • ATL: short-term fatigue proxy using a 7-day exponential moving average
  • CTL: long-term fitness proxy using a 42-day exponential moving average
  • TSB: freshness/form proxy computed from CTL minus ATL

An additional injury-risk heuristic is inferred from negative TSB and acute load spikes.

8.2 TSS computation

The code computes a TSS-like value with a hierarchy of methods:

  1. power-based when average watts and FTP exist
  2. HR-zone-weighted when training zones and average HR exist
  3. HR-ratio-squared against max HR when zones are unavailable
  4. suffer-score-based approximation
  5. time-only fallback

Sport-specific multipliers are applied so load estimates vary across running, cycling, swimming, hiking, HIIT, and other activity types.

8.3 Predictor models

The Predictor tab combines multiple interpretable models:

  • Riegel scaling: $T_2 = T_1 \times (D_2 / D_1)^{1.06}$
  • VDOT-inspired performance transfer
  • direct PB matching at nearby race distances
  • personal curve fitting based on the user's historical performances

Users can control the weight of each model and select optimistic, realistic, or conservative scenarios. This is a strong product choice because it turns the prediction experience into a transparent decision-support tool rather than an opaque model verdict.

8.4 Classification heuristics

Run classification uses weighted heuristics over inputs such as pace, elevation, HR behavior, and pace variability to infer categories like recovery, long run, tempo, speed work, race, and trail/hills.

Bike classification uses sport type, elevation-per-kilometer, average speed, speed variability, and moving ratio to distinguish road from MTB-style rides. The tab-level bike summary further labels rides as road, MTB, gravel, indoor, or electric through sport-type and metadata logic.

8.5 Swim analytics

Swim analysis includes:

  • pace per 100 m
  • indoor/pool versus open-water separation
  • pool-length inference among typical metric and yard standards
  • moving-time to elapsed-time comparison

8.6 Activity-detail engines

The advanced activity pipeline includes several higher-resolution engines:

  • climb detection
  • stop detection
  • distance, time, and terrain segmentation
  • sport-specific analyzers for running, trail running, cycling, gravel/MTB, and hiking
  • fatigue engine
  • aero engine
  • physiology engine
  • automatic insight generation

8.7 Evaluation posture

The repository does not currently expose a formal offline evaluation or benchmarking suite. The models are chosen for interpretability and practical usefulness rather than ML-paper-style measurement. This is appropriate for a personal analytics product, but it should be documented clearly when making performance claims.

9. Visualization System

Libraries used

  • Chart.js for general charting
  • chartjs-chart-matrix for matrix heatmaps
  • D3 for supporting visualization workflows
  • Cal-Heatmap for daily consistency grids
  • Leaflet for route maps
  • Leaflet.heat for density heatmaps

Visualization design principles in the current implementation

  • summary first, detail second
  • sport-aware metric representation
  • time aggregation for trend readability
  • dense time-pattern views for temporal behavior analysis
  • geographic context only where route data is available

Interactivity model

Interactivity is implemented through DOM controls rather than a framework state container. Typical interactions include:

  • filter application and reset buttons
  • dynamic year-button generation
  • chart variable selectors
  • sort toggles on tables
  • predictor weight sliders
  • map mode switching
  • AI chat suggestions and conversation history persistence

10. Detailed Feature Breakdown By Tab

Dashboard

What the user sees:

  • nine time-range preset buttons: This Week, Last 7 Days, This Month, Last 30 Days, Last 3 Months, Last 6 Months, This Year, Last 365 Days, All Time
  • custom DD/MM/YYYY From and To text inputs with an Apply button
  • KPI summary cards
  • training-load charts (CTL, ATL, TSB, 7-day rolling load, injury-risk proxy)
  • consistency heatmap
  • acute-load chart with productive-range band (band mode is hardcoded to 'aggressive')
  • goal-progress card configurable as kilometers, hours, or activity count

What data is used:

  • full preprocessed activity collection
  • derived tss, ctl, atl, tsb, and injury-risk-style signals
  • HR zones loaded from localStorage('strava_training_zones')
  • goals from localStorage('training_goals')

What runs behind the scenes:

  • activity filtering by date window via getEffectiveDashboardWindow; when selectedRangeDays === 'custom' the user-entered ISO dates short-circuit the preset table
  • daily TSS aggregation and EMA-based ATL (7d) / CTL (42d) / TSB computation
  • goal-progress accumulation
  • a module-level dashboardMemo Map caches the derived window slice (filteredActivities, runs, recentRuns, previousRuns, recentActivities, previousActivities) keyed by ${startTs}|${endTs}|${dateFilterFrom||''}|${dateFilterTo||''}. The memo is cleared whenever the allActivities reference changes.
  • the custom range is dashboard-local; it does not propagate to the global tab filter

Interactive elements:

  • preset buttons
  • custom from/to controls with Apply
  • goal editing flow

Run

What the user sees:

  • summary cards
  • consistency heatmap
  • activity-type chart
  • monthly distance chart
  • pace-versus-distance scatter
  • distance and elevation histograms
  • accumulated distance chart
  • rolling weekly trend
  • top runs area
  • activity table

What data is used:

  • activities matching running-related types
  • filtered by date and selected gear

What runs behind the scenes:

  • pace formatting in min/km
  • weekday and monthly bucketing
  • weekly distance aggregation and rolling mean
  • gear subset filtering
  • run-type classification heuristics

What insight the user gets:

  • consistency patterns
  • distribution of session lengths and climbing load
  • pace behavior across distances
  • gear-specific usage context

Interactive elements:

  • date range
  • year shortcuts
  • gear selector
  • sortable tables and drilldown links

Bike

What the user sees:

  • bike summary cards
  • bike-type summary
  • consistency heatmap
  • bike-type distribution chart
  • histograms for distance and elevation
  • speed, power, and elevation scatter charts
  • cumulative distance and rolling trend charts
  • top rides and activities table

What data is used:

  • ride-like activity types, including virtual and off-road variants
  • optional gear filter

What runs behind the scenes:

  • bike-type labeling
  • speed conversion to km/h
  • elevation-per-km normalization
  • power-versus-speed pairing

What insight the user gets:

  • how different bike categories contribute to volume
  • whether climbing-heavy rides differ materially from speed-focused rides
  • route and power distribution characteristics

Interactive elements:

  • date range
  • year buttons
  • bike selector
  • sortable tables

Swim

What the user sees:

  • swim summary cards
  • consistency heatmap
  • pool-versus-open-water summary
  • histograms and scatter charts
  • swim activity table

What data is used:

  • swim activity types
  • location availability, trainer flag, and distance/time values

What runs behind the scenes:

  • pace-per-100m derivation
  • pool/open-water inference
  • candidate pool-length evaluation against common metric and yard pools

What insight the user gets:

  • whether swim volume is mostly pool or open water
  • the likely pool configuration behind indoor sessions
  • pace distribution and session-length habits

Interactive elements:

  • date range
  • year buttons
  • sortable tables

Athlete

What the user sees:

  • athlete profile card
  • training zones display
  • all-time totals
  • records
  • duration and start-time histograms
  • yearly comparison
  • weekly and monthly mix views
  • multiple matrix heatmaps
  • interactive matrix controls

What data is used:

  • athlete profile payload
  • training zones payload
  • all activities with sport and date filters

What runs behind the scenes:

  • aggregation by different temporal axes
  • sport filter application
  • data-type switching among time, count, and distance
  • favorite day and favorite hour inference

What insight the user gets:

  • macro profile of training behavior
  • seasonal and circadian preferences
  • long-term changes in training structure

Interactive elements:

  • sport selector
  • data-type selector
  • date range
  • matrix axis selectors

Predictor

What the user sees:

  • PB table
  • top historical efforts per standard race distance
  • model weight sliders
  • scenario selector
  • prediction table
  • prediction evolution chart

What data is used:

  • running history, especially race-like and near-standard distances

What runs behind the scenes:

  • direct PB matching
  • Riegel scaling
  • VDOT-style transfer
  • personal curve fitting
  • weighted blending of models

What insight the user gets:

  • plausible target times for standard distances
  • how sensitive the prediction is to model assumptions
  • whether current history is coherent across distances

Interactive elements:

  • per-model weights
  • optimistic/realistic/conservative mode
  • chart distance selector

Gear

What the user sees:

  • gear list with usage metrics
  • distance-by-gear view
  • usage timeline/Gantt-style display
  • custom durability and price editing
  • health indicators

What data is used:

  • Strava gear metadata
  • gear-linked activities
  • locally persisted custom gear properties

What runs behind the scenes:

  • distance aggregation per gear
  • last-use detection
  • average pace calculation for run gear
  • health percentage based on expected lifetime

What insight the user gets:

  • which shoes or bikes are heavily used
  • which items are nearing replacement thresholds
  • how gear usage maps across time

Interactive elements:

  • sort selector
  • edit/delete custom metadata actions
  • gear detail links

Activities

What the user sees:

  • advanced filter panel
  • full sortable activity table

What data is used:

  • the complete preprocessed activity list

What runs behind the scenes:

  • conjunctive filter logic across sport, name, date, distance, duration, HR, and TSS
  • sport-aware display formatting for pace versus speed

What insight the user gets:

  • precise searchable access to the raw training catalog
  • quick identification of sessions meeting specific criteria

Interactive elements:

  • multi-select sport filter
  • text search
  • date range
  • numeric ranges
  • clickable column sorting

Calendar

What the user sees:

  • year selector
  • annual heatmap
  • streak summary
  • activity groups by date

What data is used:

  • all activities grouped by local calendar date

What runs behind the scenes:

  • day-streak and week-streak calculations
  • per-day grouping and intensity derivation

What insight the user gets:

  • how consistent training is over the year
  • where streaks start, break, and peak

Interactive elements:

  • year selector
  • date-group interaction

Weather

What the user sees:

  • weather summary cards
  • weather-distribution and trend charts
  • temperature-versus-pace scatter
  • selectable weather histogram
  • enriched run table

What data is used:

  • runs with start coordinates and timestamps
  • Open-Meteo hourly archive results

What runs behind the scenes:

  • batched weather requests
  • summary aggregation
  • environmental difficulty scoring
  • pace-weather pair building

What insight the user gets:

  • how training conditions vary by month
  • whether certain weather factors coincide with better or worse pace
  • overall environmental stress across the run history

Interactive elements:

  • histogram variable selector

Map

What the user sees:

  • Leaflet map
  • sport selector
  • date filters
  • heatmap versus route mode
  • tile selector
  • heatmap tuning sliders
  • color-by-sport toggle

What data is used:

  • encoded route polylines from Strava activities

What runs behind the scenes:

  • polyline decoding
  • point-density layer generation
  • route coloring by sport

What insight the user gets:

  • geographic concentration of training
  • route reuse and regional distribution

Interactive elements:

  • map mode toggle
  • sport filter
  • date range
  • tile changes
  • heatmap parameter sliders

Report

What the user sees:

  • year selector
  • annual summary cards
  • sport highlights
  • monthly volume chart
  • sport distribution chart
  • year-over-year comparisons
  • top weeks summary
  • heatmap-style monthly volume view

What data is used:

  • activities grouped by year, month, week, and sport
  • previous-year comparison baseline

What runs behind the scenes:

  • year filtering
  • YoY deltas
  • top-week ranking
  • sport-share aggregation

What insight the user gets:

  • yearly training narrative
  • strongest months and weeks
  • how the current year compares to the previous one

Interactive elements:

  • year selector
  • print/export-oriented workflow

AI Coach

What the user sees:

  • API-key entry or confirmation banner
  • chat transcript area
  • starter suggestion prompts
  • chat input and send control

What data is used:

  • loaded activities summarized into a large context block
  • optional gear summaries and recent activity highlights
  • user-provided Gemini API key stored locally

What runs behind the scenes:

  • context assembly over totals, sport breakdowns, PB-like stats, recent activities, and monthly volume
  • browser-side call to the Gemini Flash preview endpoint
  • local persistence of recent message history

What insight the user gets:

  • natural-language interpretation of their own dataset
  • conversational access to trends and planning guidance

Interactive elements:

  • key management inside the browser
  • suggestion buttons
  • chat history clearing

11. Activity Detail And Advanced Analysis Pipeline

The activity-detail experience is materially more sophisticated than the tab-level summaries and deserves separate treatment.

Data acquisition

For an individual activity, the controller requests:

  • activity metadata from /api/strava-activity
  • the stream set appropriate to the sport from /api/strava-streams

Required streams vary by sport, but the common base includes:

  • time
  • latlng
  • distance
  • altitude
  • velocity_smooth
  • grade_smooth
  • moving

Additional streams such as heart rate, cadence, and watts are requested when appropriate.

Track reconstruction

The advanced analyzer reconstructs structured tracks and points from stream payloads. This enables point-level analytics rather than simple activity-summary analytics.

Preprocessing pipeline

The preprocessing pipeline applies multiple stages:

  1. GPS spike removal
  2. altitude anomaly correction
  3. speed spike cleanup
  4. altitude smoothing
  5. grade smoothing
  6. speed smoothing
  7. terrain classification
  8. derived-metric recomputation

This sequence reduces noise before segmentation and insight generation.

Detection modules

The advanced analysis layer contains dedicated detection modules for:

  • climbs
  • stops

These detectors enable features such as climb summaries, stop timing, terrain-aware segmentation, and fatigue interpretation.

Segmentation

The system segments activities by:

  • distance
  • time
  • terrain

This creates the basis for split-level summaries and variability analysis.

Advanced engines

The activity analyzer then runs higher-level engines for:

  • fatigue
  • aerodynamics and wind effects
  • physiology and zone usage
  • automatic textual insights

Exports

The advanced activity flow supports export to:

  • GPX
  • CSV
  • JSON

This is particularly useful because it turns the application into both an analytics viewer and a derived-data generator.

11b. Chart Styling Conventions

The single stylesheet styles/style.css enforces a uniform chart appearance:

  • :root defines four sport color variables: --sport-default-color: #fc4c02, --sport-run-color: #2e7d32, --sport-bike-color: #1565c0, --sport-swim-color: #00838f. New code should reference these instead of hardcoding hex values.
  • .chart-container ships with border-top: 4px solid var(--sport-default-color) and an h3 color of the same default. The Run Analysis (#run-tab), Bike Analysis (#bike-tab), and Swim Analysis (#swim-tab) ancestors override both to their matching sport color so a tab is visually coherent.
  • The default chart grid uses .charts-grid, two columns on desktop and a single column at mobile breakpoints (800 px, 700 px, 480 px). The class was previously misspelled as .chart-grid in the weather tab; that has been corrected in index.html.
  • Canvases use aspect-ratio: 16 / 9; min-height: …; height: auto !important; at narrow breakpoints. The earlier rules that forced height: <px> !important cropped Chart.js legends on mobile and have been removed.
  • Grouped chart sets stay together by spanning the full row: .eddington-pair, .distance-pair, .readiness-layout, .acute-load-chart-container { grid-column: 1 / -1; }.

11c. Memoization Pattern (Dashboard)

js/tabs/dashboard.js introduces a module-level dashboardMemo = new Map() that caches derived per-window slices. The cache key is the tuple ${startTs}|${endTs}|${dateFilterFrom||''}|${dateFilterTo||''}. The cache is busted in two ways:

  1. The renderer compares the incoming allActivities reference against the previously rendered context and calls dashboardMemo.clear() when it changes.
  2. Adding a new range preset or custom-date input automatically participates because the cache key embeds the resolved start/end timestamps.

When adding new memoizable derived state on another tab, follow the same shape: keep the Map at module scope, derive the cache key from every input that affects the value, and clear the entire map when the upstream activity array reference changes. Avoid leaking memoized references across users or sessions because the cache survives only for the lifetime of the page.

12. Visualizations In Detail

Histograms

Used for distance, elevation, pace, duration, and weather variables. Histograms make it easy to see central tendency, spread, and skew in a way summary averages cannot.

Scatter plots

Used where relationships matter more than totals, such as:

  • pace vs distance
  • distance vs elevation
  • speed vs distance
  • power vs speed
  • temperature vs pace

These plots reveal tradeoffs, clusters, and outliers.

Rolling trend charts

Used in run and bike views to smooth noisy week-to-week volume changes. Rolling means are more useful than raw weekly totals when the goal is to reason about trend direction.

Calendar heatmaps

Used in sport tabs and the Calendar tab to surface consistency, streaks, and training density.

Matrix heatmaps

Used heavily in the Athlete tab to show temporal density patterns across combinations such as weekday/hour or year/month. These views are particularly effective for detecting routine, seasonality, and behavior change.

Map layers

The map system supports both route-line rendering and density heatmaps, which address two different questions:

  • where did I go exactly?
  • where do I most often train?

13. Stack And Runtime Environment

Languages and libraries

  • JavaScript
  • HTML
  • CSS
  • Chart.js
  • chartjs-chart-matrix
  • D3.js
  • Cal-Heatmap
  • Leaflet
  • Leaflet.heat
  • node-fetch
  • @vercel/speed-insights

Hosting and operations

  • frontend static hosting on Vercel
  • serverless API execution on Vercel
  • PWA manifest and service worker for installability and static-asset caching

14. Local Development And Operations

Setup steps

  1. Install dependencies with npm install.
  2. Copy .env.example to .env.local and set STRAVA_CLIENT_ID and STRAVA_CLIENT_SECRET.
  3. Run npm run dev.
  4. Open the local URL and authenticate against your Strava app, or click the demo button to load synthetic data.

Dev server

npm run dev invokes scripts/local-dev-server.mjs (declared in package.json). It is a small Node http server with the following behaviour:

  • Serves any static file under the repository root.
  • For requests starting with /api/, it dynamically import()s the matching file in api/ (e.g. /api/strava-activities -> api/strava-activities.js), invokes the default export as a Vercel-style handler, and forwards the response. Modules are reloaded per-request using a ?v=<version> cache buster so edits take effect without restarting.
  • Listens on PORT (default 3001).
  • Reads .env.local so STRAVA_CLIENT_ID and STRAVA_CLIENT_SECRET are available to the serverless modules.

API endpoints

The handlers under api/ are designed to be deployed as Vercel serverless functions and to run unchanged under the local dev server:

  • api/_shared.js — header parsing, token decoding, refresh helpers.
  • api/config.js — surfaces the public Strava client id to the frontend.
  • api/strava-auth.js — OAuth code exchange and refresh.
  • api/strava-athlete.js — athlete profile.
  • api/strava-activities.js — paginated activity history (per_page=100, pages until empty).
  • api/strava-activity.js — single-activity metadata.
  • api/strava-streams.js — activity streams (time, latlng, distance, altitude, velocity_smooth, grade_smooth, moving, heartrate, cadence, watts).
  • api/strava-gear.js — gear metadata.
  • api/strava-zones.js — HR and power training zones.

PWA

  • manifest.json defines the installable PWA identity, icons, and theme.
  • sw.js is the service worker, responsible for caching static assets and the SPA shell.
  • vercel.json rewrites tab routes (/run, /dashboard, /bike, /swim, /weather, /ai-coach, etc.) to /index.html so the SPA owns deep-link rendering.

Demo mode

Demo mode is gated by a single localStorage flag, strava_demo_mode, read through isDemoMode() in js/demo/index.js. The dedicated demo button in the connect screen invokes loadDemoData() from js/app/auth.js, which generates ~250 synthetic activities via js/demo/generator.js (with route polylines from js/demo/polylines.js), writes them to the same localStorage keys the real API would populate, and installs a fake demo_ token. The various tab renderers and the API layer detect demo mode and bypass network calls when it is active.

Runtime assumptions

  • the browser supports native ES modules
  • the deployment environment supports Vercel serverless functions
  • the Strava developer app is configured with matching redirect URIs

15. Performance Considerations

Current strengths

  • lazy tab rendering avoids generating all charts up front
  • lightweight serverless backend keeps operational complexity low
  • local caching reduces repeated requests for athlete, zones, and gear data

Current bottlenecks

  • all heavy analytics run on the main thread
  • full activity-history fetches can be expensive
  • weather enrichment adds many external requests for large running histories
  • localStorage is not ideal for large-object persistence

Recommended improvements

  • move stream-level computation to Web Workers
  • introduce IndexedDB for larger local caches
  • precompute some reusable tab aggregates once per load
  • consider optional server-side snapshots for power users with very large histories

16. Limitations And Known Issues

  • no server-side persistence or user accounts beyond Strava OAuth
  • dependence on Strava rate limits and upstream availability
  • athlete-specific swim correction currently hardcoded for one known profile case
  • weather data is archive-based and approximate
  • predictor outputs are only as good as the user's historical race coverage
  • GPX export is reconstructed, not original-device data
  • browser storage is convenient but not enterprise-grade session infrastructure

17. Future Engineering Opportunities

  • generalized configuration for athlete-specific corrections and sport preferences
  • formal unit and integration tests around predictors and classification logic
  • broader swim analytics, including structured interval handling
  • richer cycling metrics such as FTP modeling or power-duration curves
  • better offline support through richer service-worker strategies and IndexedDB
  • explicit observability around Strava API rate consumption and request timing

18. Summary

Strava Dashboard is best understood as a client-heavy analytics application rather than a conventional CRUD web app. Its strongest qualities are transparency, breadth of sport coverage, and a clear separation between summary dashboards and high-resolution activity analysis. The system favors local computation, explicit heuristics, and practical visual analytics. That makes it well suited to technically minded athletes and maintainers, while also leaving clear paths for future scaling and refinement.