OpenTrailPaper
Open-source bike computer firmware for the LilyGO T5S3 4.7" E-Paper PRO, with offline maps, GPX routes, FIT recording and Bluetooth sensors.
SF2G leaderboard — riders and rides ranked by commute counts, speed, and routes.
Translation pending
Sports & fitnessTypeScript · PLpgSQLGitHub @pmaier983OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Tracking your commutes from SF to G/A/PA/MTV/FB/ETC
Taken from the repository README
Loading the full README.
Read the full README ↗A competitive cycling commute tracker for the SF2G community. Connect your Strava account, sync your rides, and see how you stack up across seven Bay Area corridors.
Live at sf2ging.com
| Layer | Technology |
|---|---|
| Framework | TanStack Start (full-stack React) |
| UI | React 19, TanStack Table, TanStack Virtual |
| Data | TanStack Query |
| Backend | Supabase (PostgreSQL + REST) |
| Auth | Strava OAuth 2.0 |
| Charts | Recharts |
| Maps | Leaflet |
| Analytics | PostHog |
| Styling | Vanilla CSS (custom properties, glassmorphism) |
| Testing | Vitest |
| Bundler | Vite |
| Deploy | Cloudflare Workers (Free plan) |
| Package Manager | pnpm |
packageManager)brew install colima docker && colima start)brew install supabase/tap/supabase)localhost in its Authorization Callback Domain (only needed to log in locally)git clone https://github.com/pmaier983/sf2g.git
cd sf2g
pnpm install
# Local database (never point local dev at production)
pnpm db:start # starts Postgres + PostgREST in Docker
pnpm db:reset # applies supabase/migrations/* and supabase/seed/*
cp .env.example .env.local # local Supabase defaults are pre-filled
pnpm dev # http://localhost:5173
| Command | Description |
|---|---|
pnpm dev | Start Vite dev server (localhost:5173) |
pnpm build | Production build for Cloudflare |
pnpm preview | Preview production build locally |
pnpm deploy | Build and deploy to Cloudflare Workers |
pnpm typecheck | Run TypeScript type checking |
pnpm test | Run tests once |
pnpm test:watch | Run tests in watch mode |
pnpm test:db | DB tests against the local fixture DB (resets it when migrations or fixtures differ) |
pnpm e2e | Playwright smoke tests (reuses a running pnpm dev) |
pnpm db:start / db:stop | Start / stop the local Supabase stack |
pnpm db:reset | Re-apply all migrations and seed data locally |
pnpm db:types | Regenerate app/lib/database.types.ts from the local DB |
app/
├── routes/ # File-based routing
│ ├── __root.tsx # Root layout (navbar, theme, toasts)
│ ├── index.tsx # Landing page
│ ├── leaderboard.tsx # Full leaderboard with filters
│ ├── routes.tsx # Route corridor documentation
│ ├── auth/ # OAuth login & callback
│ └── profile/ # Rider profiles ($userId)
├── components/ # Shared components
│ ├── LeaderboardTable.tsx # Virtualized leaderboard
│ ├── LeaderboardColumns.tsx # Column definitions & sub-tables
│ ├── LeaderboardFilters.tsx # Route/time filter chips
│ ├── InteractiveMap.tsx # Leaflet route map
│ ├── GatewayMap.tsx # Gateway checkpoint map
│ ├── ProfileRideStats.tsx # Rider stat cards
│ ├── ProfileRidesTable.tsx # Per-rider ride history
│ ├── CommunityPieCharts.tsx # Route distribution charts
│ ├── GrowthChart.tsx # Cumulative ride growth
│ ├── RideFrequencyChart.tsx # Ride frequency over time
│ ├── RouteSpeedTable.tsx # Speed stats by route
│ ├── WindIndicator.tsx # Tailwind/headwind display
│ ├── FilterChips.tsx # Chip-based filter UI
│ ├── UnitToggle.tsx # Metric/imperial toggle
│ ├── ThemeToggle.tsx # Dark/light mode toggle
│ ├── NavBar.tsx # Navigation bar
│ ├── Toast.tsx # Sonner toast wrapper
│ └── DevToolsPanel.tsx # Admin/dev tools panel
├── lib/ # Utilities, types, classifiers
│ ├── route-classifier.ts # GPS gateway route classification
│ ├── destination-classifier.ts # Office/destination detection
│ ├── office-locations.ts # Known office coordinates
│ ├── constants.ts # Gateways, colors, labels
│ ├── database.types.ts # Auto-generated Supabase types
│ ├── session.ts # Cookie session management
│ ├── strava-oauth.ts # Strava OAuth flow
│ ├── strava.ts # Strava API helpers
│ ├── wind.ts # Wind calculation utilities
│ ├── rate-limiter.ts # API rate limiting
│ ├── analytics.ts # PostHog analytics
│ ├── supabase.ts # Supabase client factory
│ └── useUnit.ts # Unit preference hook
├── queries/ # TanStack Query option factories
│ ├── leaderboard.ts
│ ├── rides.ts
│ └── user.ts
├── server/ # Server-side logic (createServerFn)
│ ├── auth.ts # OAuth login/logout/callback
│ ├── sync.ts # Strava ride sync
│ ├── leaderboard.ts # Leaderboard queries
│ ├── rides.ts # Ride CRUD operations
│ ├── reclassify.ts # Bulk route reclassification
│ ├── wind-enrichment.ts # Wind data enrichment
│ ├── weather.ts # Weather API integration
│ ├── cron.ts # Maintenance + admin "run one tick"
│ ├── sync-drain.ts # Sync job queue drain (Cron Trigger)
│ └── users.ts # User management
└── styles/ # CSS files
├── global.css # Theme variables, base styles
├── components.css # Component-specific styles
├── leaderboard.css # Leaderboard-specific styles
├── maps.css # Map component styles
├── routes.css # Routes page styles
└── wind.css # Wind indicator styles
Routes are classified using GPS gateway checkpoints — specific lat/lng coordinates that a ride's decoded polyline must pass through. This avoids dependency on Strava segment IDs and works reliably across different GPS devices.
| Route | Description | Gateway North | Gateway South |
|---|---|---|---|
| Bayway | Bay shore / Foster City | (37.684, -122.390) | (37.579, -122.311) |
| Skyline | Mountain ridge / Skyline Blvd | (37.668, -122.485) | (37.489, -122.320) |
| HMBW | Coastal / Devil's Slide | (37.578, -122.512) | (37.449, -122.429) |
| Royale | El Camino Real corridor | (37.611, -122.403) | (37.521, -122.277) |
| Fleaway | Flat El Camino / Bayshore | (37.624, -122.408) | (37.492, -122.266) |
| MEBW | Middle East Bay | (37.683, -122.178) | (37.509, -122.114) |
| FEBW | Far East Bay | (37.814, -122.144) | (37.669, -122.001) |
Start point: Peet's Coffee, PPR (37.773, -122.439) Intercept: JD/Skyline (37.698, -122.495) Gateway radius: 500 meters
Browser ──► Cloudflare Worker (TanStack Start, Free plan: 10 ms CPU, 50 subrequests)
│ every page = one SQL function call (get_leaderboard, get_rides, …)
▼
Supabase Postgres ◄── sync_jobs queue ◄── Strava webhook (records only)
▲ │
└── Cron Trigger */5 ────┘ drains the queue in budget-sized steps
app/lib/table-query.ts) and every table renders through
app/components/table/DataTable.tsx.wrangler.jsonc)
drains sync_jobs every 5 minutes; a 12-hour sweep catches anything a
webhook missed. See docs/ops/sync.md.app/lib/dates.ts).Copy .env.example to .env.local (local values) — production values are
Cloudflare secrets/vars.
| Variable | Description |
|---|---|
SUPABASE_URL, SUPABASE_ANON_KEY | Supabase project (local: pnpm db:start) |
SUPABASE_SERVICE_ROLE_KEY | Server only — sync, queue, admin |
STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET | Strava API app |
STRAVA_WEBHOOK_VERIFY_TOKEN | Echoed back when Strava verifies the webhook |
STRAVA_WEBHOOK_SUBSCRIPTION_ID | Optional — ignore events for other subscriptions |
SESSION_SECRET | ≥ 32 chars, signs session cookies |
ADMIN_USER_IDS | Comma-separated user ids allowed to use admin actions |
CRON_SECRET | Bearer token for POST /api/cron |
APP_URL | Public app URL |
VITE_MAP_TILE_URL, VITE_MAP_TILE_URL_DARK, VITE_MAP_TILE_ATTRIBUTION | Optional map tile provider (default: OpenStreetMap) |
Private — not yet licensed for public use.