OpenAthlete
AGPLv3 endurance training platform: EU-oriented hosting, transparent CTL/ATL/TSB logic in code, self-hosting, and full export. Built in Grenoble.
Live stage-by-stage IPSC competitor comparison
Translation pending
Sports & fitnessTypeScript · PythonGitHub @mandakanOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
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
Analyze and compare your IPSC scores
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Live stage-by-stage comparison tool for IPSC competitions on shootnscoreit.com.
The official site only lets you view one competitor at a time. This app lets you select
any group of competitors and compare their results across every stage side-by-side --
including during an active match before official results are published.
Live instance: scoreboard.urdr.dev -- not affiliated with or endorsed by ShootNScoreIt.
• Node.js 20+
• pnpm 10+ (corepack enable && corepack prepare [email protected] --activate)
• A ShootNScoreIt API key (account settings on shootnscoreit.com)Read the full README ↗Live stage-by-stage comparison tool for IPSC competitions on shootnscoreit.com.
The official site only lets you view one competitor at a time. This app lets you select any group of competitors and compare their results across every stage side-by-side -- including during an active match before official results are published.
Live instance: scoreboard.urdr.dev -- not affiliated with or endorsed by ShootNScoreIt.
corepack enable && corepack prepare [email protected] --activate)pnpm install
cp .env.local.example .env.local # then fill in SSI_API_KEY
pnpm dev # http://localhost:3000
The app supports two production targets, selected at build time via DEPLOY_TARGET. Both
require a Redis instance for hot caching, and a relational store (SQLite or D1) for
persistent shooter / match metadata that survives Redis flushes.
cp .env.local.example .env.local # fill in SSI_API_KEY, CACHE_PURGE_SECRET
pnpm docker:build # builds image
pnpm docker:up # starts redis + app on port 3000
REDIS_URL is set automatically via the compose service name. Two named volumes persist
state across container restarts:
redis_data -- Redis hot cacheshooter_data -> /app/data -- SQLite store (shooter profiles, match indices, achievements, historical match cache)Without Docker Compose (bare server, Kubernetes, Fly.io): run a Redis instance and
set REDIS_URL to its connection string (rediss:// for TLS). Mount a writable volume
at ./data/ (or set SHOOTER_DB_PATH) so the SQLite database persists. The app uses
lazyConnect: true, so a missing Redis at startup is non-fatal -- requests fall back to
direct GraphQL fetches.
The Cloudflare build swaps in HTTP-based adapters at build time:
@upstash/redis instead of ioredis (Workers cannot open TCP)better-sqlite3 (Workers have no persistent disk)You will need an Upstash Redis database and a D1 database.
pnpm cf:build # DEPLOY_TARGET=cloudflare + @opennextjs/cloudflare build
pnpm cf:deploy # build + wrangler deploy
Full step-by-step walkthrough -- wrangler login, secrets, custom subdomain, verification, troubleshooting -- in docs/deploy-cloudflare.md.
Note on serverless platforms without persistent disk (Vercel, Netlify default): the app's SQLite AppDatabase needs writable disk for shooter/match metadata. These platforms only work if you switch the persistence layer to D1 or a managed SQL store -- they are not currently supported targets.
The most common ones an operator needs:
| Variable | Target | Description |
|---|---|---|
SSI_API_KEY | Both | ShootNScoreIt API key -- server-side only, never exposed to browser |
CACHE_PURGE_SECRET | Both | Secret for the admin cache-purge endpoint -- any strong random string |
NEXT_PUBLIC_BUILD_ID | Both | Git SHA baked into the bundle for version detection (auto-injected by pnpm docker:build) |
REDIS_URL | Docker | Redis connection string (redis://localhost:6379, or rediss://... for TLS) |
SHOOTER_DB_PATH | Docker | Optional. Path to SQLite database file. Defaults to ./data/shooter-index.db |
UPSTASH_REDIS_REST_URL | Cloudflare | REST URL from the Upstash console |
UPSTASH_REDIS_REST_TOKEN | Cloudflare | REST token from the Upstash console |
MCP_SECRET | Both | Optional. If set, POST /api/mcp requires Authorization: Bearer <MCP_SECRET> |
NEXT_PUBLIC_APP_URL | Both | Base URL for MCP tool internal API calls. Required on Cloudflare Pages |
AI_PROVIDER / AI_MODEL / AI_API_KEY / AI_API_URL | Both | AI coaching tips. Set to "openai" or "cloudflare"; omit AI_PROVIDER to disable |
Full reference -- including TTL tunables, telemetry sampling, probe kill switches, and all internal flags -- in docs/env-vars.md.
https://shootnscoreit.com/event/22/26547/) to jump straight
to a match. The collapsible Filters panel narrows results by discipline, country,
and level.Comparison & rankings
Charts
Coaching analysis
Cross-competition (shooter dashboard)
Search & navigation
Platform
next/og)pnpm dev # start dev server on port 3000
pnpm build # production build
pnpm lint # ESLint -- zero warnings required
pnpm typecheck # TypeScript type check -- zero errors required
pnpm test # Vitest unit + component tests
pnpm test:watch # Vitest in watch mode
pnpm test:e2e # Playwright E2E tests (mocked API, no live key needed)
pnpm test:e2e:ui # Playwright with interactive UI
Quality bar: pnpm lint, pnpm typecheck, and pnpm test must all pass with zero
errors and zero warnings before merging. CI enforces this.
Held back from latest -- don't upgrade without a migration plan:
| Package | Pinned at | Why |
|---|---|---|
zod | 3.x | Zod 4 has breaking API changes. Needs a dedicated migration across lib/ and app/api/. |
eslint | ^9 | ESLint 10 is brand-new; wait until eslint-config-next officially supports it as a peer dep. |
SSI Scoreboard exposes an MCP server so Claude and other
AI assistants can query competition data directly. Seven tools: search_events, get_match,
compare_competitors, get_stage_times, get_popular_matches, get_shooter_dashboard,
find_shooter.
HTTP transport (public, no local setup needed):
POST https://scoreboard.urdr.dev/api/mcp
stdio transport (local subprocess) -- .mcp.json at repo root pre-registers two stdio
servers:
ssi-scoreboard -- calls the live production instance (no local server needed)ssi-scoreboard-local -- calls localhost:3000 (requires pnpm dev running)Claude Code picks up .mcp.json automatically when you open the repo.
For client-specific setup (Claude Desktop, generic HTTP clients), example prompts, and troubleshooting, see docs/mcp.md.
Browser -> Next.js Route Handlers -> shootnscoreit.com/graphql/
^
POST /api/mcp (MCP clients / Claude)
The SSI_API_KEY lives server-side only and is never sent to the browser. Route Handlers
are the only place that touches the upstream GraphQL API.
More diagrams (the live scorecard refresh cycle and the tiered match data store) are in
docs/diagrams/, rendered inline there; the .html files in
that directory are interactive versions of the same diagrams.
Two persistence layers:
Key entry-points:
app/api/match/[ct]/[id]/ -- match metadata (stages, competitors, scoring progress)app/api/compare/ -- fans out scorecard queries, merges ranking dataapp/api/events/ -- event search with date-range, firearms, country, level filtersapp/api/shooter/[shooterId]/ -- cross-competition shooter dashboardapp/api/og/match/[ct]/[id]/ -- dynamic Open Graph imagesapp/api/mcp/ -- MCP HTTP endpoint (JSON-RPC, single-shot transport)app/api/coaching/ -- AI coaching tipsapp/api/admin/cache/ -- authenticated cache purge / health / force-refreshFor the full module layout and contributor conventions, see CLAUDE.md.
This project uses Tailwind v4 with CSS custom property design tokens (OKLCH color space)
defined in app/globals.css. Always use semantic token classes (bg-background,
text-muted-foreground, etc.) rather than raw palette classes (bg-gray-100).
Dark mode is supported via next-themes and the .dark class -- all tokens have light
and dark values. A theme toggle in the toolbar lets users switch at runtime.
shadcn/ui is the primary component library. Never modify files in components/ui/
directly; use pnpm dlx shadcn@latest add to update them.
This project targets WCAG 2.1 AA compliance:
globals.css)role="alert" for screen reader announcements<button>, <table>, <th scope>, etc.)If this tool saves you time at the range, consider buying me a coffee: