Skip to content

Repository files navigation

AfterRun Race Timing

No chip, no barcode, no worries.

AfterRun is a lightweight, private, Strava-assisted delayed-results tool for informal running events and community time trials.

Timing is optional. Timed runners opt in for a specific event, record the route using their own watch or phone, and let the activity sync to Strava as normal. AfterRun checks later against the event rules and prepares private results for organiser review.

Untimed runners can still take part socially without connecting Strava or appearing in results.

AfterRun is not live timing and is not a formal race platform.

Why I Built It

AfterRun grew from a community run I organised, where the event worked well socially but some runners also wanted an optional way to compare times.

The challenge was to offer that without introducing chips, barcodes, scanners, finish funnels, manual stopwatch timing, or extra race-day administration.

The core idea is simple:

Let runners use the devices and Strava accounts they already have, then assemble results later.

That keeps the event social and low-pressure while still giving timed runners a simple result if they want one.

Portfolio Context

This repository is a sanitised public portfolio version of AfterRun.

The public app uses synthetic data. Real runner data, private test records, live credentials, production identifiers, operational tooling, and commercially sensitive implementation details are intentionally excluded.

The repository still demonstrates the engineering approach through:

  • the public synthetic product flow;
  • matching and review logic;
  • automated tests;
  • PostgreSQL / Supabase schema and RLS examples;
  • privacy-by-design decisions;
  • recruiter-safe Strava OAuth and webhook code samples;
  • architecture and development documentation.

The full private implementation contains deeper operational functionality that is deliberately not published here.

Real-World Validation

The private implementation progressed beyond the synthetic demo and was validated with a small, consented group in a real community time trial.

That validation exercised the core workflow end to end:

  • timed-runner opt-in;
  • Strava authorisation;
  • event-specific consent;
  • delayed activity checks;
  • token refresh where required;
  • result matching;
  • review states for uncertain cases;
  • private organiser result display.

Real participant data and private operational infrastructure are intentionally excluded from this repository.

Public Demo Flow

The portfolio demo is static and synthetic:

No live Supabase or Strava credentials are required for the current portfolio demo.

Engineering Challenges

AfterRun explores several real product and systems problems:

  • modelling delayed result states such as matched, needs_review, missing, pending, and removed_from_results;
  • matching activity summaries against event rules without silently guessing;
  • using elapsed time rather than moving time as the result basis;
  • flagging significant elapsed/moving-time differences for organiser review;
  • handling multiple plausible activities conservatively;
  • separating OAuth authorisation from event-specific runner consent;
  • keeping token storage separate from participant and result display records;
  • designing private-first results with minimal data exposure;
  • modelling organiser ownership and row-level security boundaries;
  • keeping the recruiter-facing demo independent from private operational infrastructure.

Matching Logic

AfterRun matching is deliberately conservative.

A clean automatic match requires:

  • the correct activity type;
  • a start time inside the accepted event window;
  • a distance within the configured tolerance;
  • availability by the relevant delayed check;
  • an elapsed time value that can be used as the result basis.

Moving time is review context only. If elapsed time and moving time differ significantly, the activity is held for review rather than silently ranked.

Multiple plausible activities also require review rather than guessing.

See STRAVA_MATCHING.md for the detailed matching design.

Result Model

Result status and result source are kept separate.

Examples:

  • matched + strava
  • needs_review + strava
  • missing + none
  • matched + manual
  • removed_from_results

Only accepted matched results receive positions.

Positions are calculated from eligible results rather than stored as fixed values, which avoids stale rankings when a result changes after review.

Privacy by Design

AfterRun is designed around data minimisation.

The intended result display is deliberately small:

  • runner name;
  • time;
  • position;
  • status;
  • source;
  • optional category or club.

The product is not designed to publish:

  • GPS traces;
  • maps;
  • heart rate;
  • cadence;
  • power;
  • detailed activity streams;
  • full Strava payloads;
  • unnecessary analytics.

OAuth connection and event-specific consent are separate concepts. A runner connecting Strava does not automatically opt them into every future event.

See PRIVACY_DESIGN.md for the privacy case study.

Tech Stack

  • Next.js App Router
  • React
  • TypeScript
  • PostgreSQL / Supabase architecture examples
  • Strava API integration architecture examples
  • Vitest
  • Vercel in the private implementation

This public repository demonstrates the architecture and synthetic portfolio flows; the underlying workflow was implemented and validated separately in the private project.

Database and Security Design

The public repository includes a curated, synthetic SQL set:

These files demonstrate:

  • organiser approval;
  • organiser-owned private events;
  • timed-runner consent;
  • result state modelling;
  • separated Strava token storage;
  • OAuth state handling;
  • row-level security concepts.

They are portfolio examples, not the private project's full migration history.

See DATABASE_DESIGN.md.

Testing

The public repo includes Vitest coverage for matching and Strava error-boundary behaviour.

Coverage includes:

  • manual activities;
  • activity type mismatches;
  • distance tolerance;
  • start windows;
  • elapsed/moving-time review;
  • multiple possible activities;
  • missing activities;
  • safe HTTP error mapping.

Relevant files:

Recruiter-Safe Strava Examples

The real private Strava integration is not published as active runtime code here.

Instead, the repository contains recruiter-safe examples showing the architecture without live infrastructure:

These examples demonstrate OAuth state handling, token security boundaries, webhook verification, and deauthorisation handling without exposing credentials, production identifiers, or live database operations.

Screenshots

The screenshots below show the progression from the synthetic public demo to selected sanitised views from the deeper private implementation.

Public Portfolio Homepage

AfterRun public portfolio homepage showing the product proposition and delayed-results workflow.

This sanitised public portfolio homepage demonstrates the core product proposition, optional timing, the four-step delayed-results journey, and the synthetic demo entry point.

Synthetic Timed-Runner Demo

Synthetic timed-runner demo showing event details, consent wording, and a disabled Strava connection button.

The public demo uses sample data. The button shown does not start real OAuth; it demonstrates event details, runner consent, privacy wording, and the intended Strava-assisted journey.

Synthetic Private Results Review

Synthetic private results review showing ranked matched results plus needs-review and missing states.

The synthetic results view demonstrates ranked matched results, needs-review and missing states, minimal result fields, and no detailed Strava activity data.

Event-Specific Consent Architecture

Sanitised private implementation screenshot showing final event-specific timed-runner consent.

Sanitised screenshot from the private implementation. It demonstrates the separation between Strava OAuth authorisation and final event-specific timed-runner consent. The operational code and real test data are intentionally excluded from this public repository.

Private Organiser Review

Sanitised private implementation screenshot showing a private organiser result review dashboard.

Sanitised screenshot from the private implementation. It demonstrates private organiser result review, event-level result states, minimal runner/result information, and a read-only organiser workflow with no maps, GPS traces, or detailed activity data. The operational dashboard code and real test data are intentionally excluded from this public repository.

Architecture Documents

Running Locally

npm install
npm run dev
npm test
npm run build

The current portfolio demo uses synthetic data and does not require live Supabase or Strava credentials.

Scope Boundary

AfterRun deliberately is not:

  • live timing;
  • chip timing;
  • a public Strava scraper;
  • a formal race-registration platform;
  • a public leaderboard;
  • a detailed athlete analytics platform.

The product stays intentionally narrow: optional, delayed, private results for community time trials without turning the event into a formal race.

About

Private, Strava-assisted delayed results for community time trials. No chip, no barcode, no worries.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages