A fully responsive, AI-powered specialized training planner and analytics suite built exclusively for Hyrox athletes.
Features β’ Tech Stack β’ Architecture β’ Project Structure β’ Getting Started β’ Scripts β’ Testing β’ CI/CD β’ License
Plan structured training programs, log complex workouts with voice or free-text using Gemini LLMs to parse sets & reps, automatically sync activities from Strava and Garmin Connect, and get real-time prescriptive AI coaching to adjust your volume based on your completed results.
- Interactive Timeline β Drag-and-drop view spanning past, present, and future workouts with status indicators (completed, planned, missed).
- Timeline Annotations β Mark date ranges as injury / illness / travel / rest so dips in volume are visible in context on the Timeline and as shaded bands on Analytics charts.
- Training Plans β Import CSV, DOCX, or PDF training blocks, use the built-in 8-week Hyrox program, or generate a fully custom plan via AI.
- Custom Exercises β Log non-standard movements (sled pushes, sandbag lunges) alongside standard lifts.
- Guided Onboarding β Configure profile, units, and weekly goals on first launch.
- Workout Parsing β Say or type "3 sets bench 225lbs x 8, then 3 miles in 24 min" and Gemini parses it into structured data with Zod-validated schemas.
- Photo-to-Workout β Snap a photo of a workout plan (whiteboard, gym printout, coach's notes) and Gemini extracts the exercises, sets, and prescribed loads. Images are compressed client-side before upload.
- Auto-Coach β Reads your plan and recent activity to evaluate fatigue, volume, and pacing, then suggests schedule adjustments with rationale stored alongside each plan day.
- Streaming Chat β Ask contextual questions over Server-Sent Events (SSE), e.g. "What pace for my next 1km run?"
- Document Uploads β Upload coaching materials (CSV, DOCX, PDF) to enrich the AI coach's knowledge base.
- Vector Search β Documents are chunked and embedded via pgvector for semantic retrieval-augmented generation.
- Strava β OAuth link with HMAC-signed state; activities auto-appear on the timeline with encrypted token storage and per-user dedupe.
- Garmin Connect β Email/password link against the reverse-engineered Garmin SSO, protected by a global 429 circuit breaker, a per-user in-flight mutex, and a 5-minute minimum sync interval. Credentials and OAuth tokens are encrypted at rest. (Note: Garmin 2-step verification must be disabled for the current SSO library to authenticate.)
- Personal Records β 1RM estimation, lifetime PRs, and progression charts.
- Week-over-Week Deltas β The Analytics overview shows percentage-change indicators against the equal-length prior period for total workouts, avg/week, total duration, and avg duration.
- Filtering β Drill down by exercise category, date range, or micro-cycle.
- Data Export β Download workout timeline and exercise sets as CSV or JSON.
- Email Notifications β Opt-in weekly training summaries and missed-day reminders via pg-boss + Resend, with a master toggle plus independent per-type switches (
emailWeeklySummary,emailMissedReminder).
- GDPR Account Deletion β
DELETE /api/v1/accountremoves your Clerk identity, best-effort deauthorizes Strava, and cascade-deletes every row owned by the user acrossworkout_logs,exercise_sets,training_plans,plan_days,chat_messages,coaching_materials,document_chunks,strava_connections,garmin_connections,custom_exercises,push_subscriptions,ai_usage_logs,idempotency_keys, andtimeline_annotations. - AI Consent Gate β The AI coach is opt-in (
aiCoachEnabled, defaultsfalse); no workout data is sent to Google Gemini until the user explicitly enables it. - Privacy Policy β First-party Privacy page (
client/src/pages/Privacy.tsx) listing every third-party processor (Clerk, Gemini, Strava, Garmin, Resend, Sentry) and the data they receive.
- Installable β Progressive Web App with Workbox service worker for offline caching and native-like mobile experience.
This repository is a fully functional monorepo containing both the React frontend and the Express REST API backend, written entirely in strictly typed TypeScript.
- Framework: React 18, Vite 5, TypeScript 5.9
- Styling: Tailwind CSS 4 layered over shadcn/ui (accessible Radix primitives)
- State Management: TanStack Query (React Query) for optimized server state caching
- Client Routing: wouter for ultra-lightweight navigation
- Drag & Drop: dnd-kit for sortable, accessible drag-and-drop interactions
- Visualization: Recharts for interactive performance charts
- PWA: vite-plugin-pwa + Workbox for offline support and installability
- Error Tracking: Sentry for real-time error monitoring
- API Runtime: Node.js + Express 4 with thin controller wrappers and thick service abstractions
- Database: PostgreSQL (hosted on Railway) bridged by the type-safe Drizzle ORM
- Authentication: Clerk JWT middleware protecting all private endpoints
- AI: Google Gemini API (
@google/genai) for workout parsing and coaching - Vector DB: pgvector on Neon for RAG document embeddings
- Job Queue: pg-boss for background tasks (email scheduling, maintenance)
- Email: Resend for transactional email delivery
- Logging: Pino + pino-http for structured, high-performance logging
- API Documentation: Swagger UI auto-generated from Zod schemas via zod-to-openapi
- Validation: Zod for runtime schema validation with drizzle-zod integration
- Security:
- Helmet for HTTP security headers
- express-rate-limit for granular API rate limiting
- csrf-csrf for CSRF protection (double-submit cookie pattern bound to Clerk userId); in production
CSRF_SECRETis required and must differ fromENCRYPTION_KEY(key separation is enforced at startup) - Server-side idempotency enforcement via
X-Idempotency-Keyheader with database-backed cache; pg-boss job retries are scoped to idempotent handlers only (sendJobNoRetryfor side-effectful handlers like email send) - Compression middleware skips
text/event-streamresponses to unblock Gemini streaming chat - AES-256-GCM encryption for Strava and Garmin credentials / tokens at rest
- Strava OAuth CSRF state verification
- Garmin 7-layer safety stack: per-route rate limiter, per-user mutex, 5-minute min-sync interval, fail-fast on prior
lastError, global 30-minute 429 circuit breaker, no silent re-login on stale tokens, audit logging on every Garmin call - HTML sanitization of AI-generated content
- Shared Zod schemas and TypeScript types between client and server
- OpenAPI spec generation from Zod schemas
flowchart TB
subgraph Client["Client (React SPA)"]
UI[Vite + React 18]
TQ[TanStack Query]
SW[Service Worker / PWA]
end
subgraph Server["Express API"]
API[Route Handlers]
Services[Service Layer]
Gemini[Gemini AI Engine]
Queue[pg-boss Job Queue]
end
subgraph Data["Data Layer"]
PG[(PostgreSQL β Railway)]
PGV[(pgvector β Neon)]
end
subgraph External["External Services"]
Clerk[Clerk Auth]
Strava[Strava OAuth]
Garmin[Garmin Connect SSO]
Resend[Resend Email]
GeminiAPI[Google Gemini API]
end
UI --> TQ --> API
API --> Services
Services --> PG
Services --> PGV
Services --> Gemini --> GeminiAPI
API --> Clerk
Services --> Strava
Services --> Garmin
Queue --> Resend
Queue --> PG
SW -.->|offline cache| UI
flowchart LR
subgraph Input
Voice[Voice Input]
Text[Free-Text Input]
end
subgraph Parsing
GP[Gemini Parser]
ZV[Zod Validation]
end
subgraph Storage
DB[(PostgreSQL)]
end
Voice --> GP
Text --> GP
GP --> ZV --> DB
subgraph RAG["RAG Coaching"]
Doc[Document Upload]
Chunk[Chunking]
Embed[(pgvector Embeddings)]
Retrieve[Semantic Retrieval]
Coach[Gemini Coach]
end
Doc --> Chunk --> Embed
Embed --> Retrieve --> Coach
DB -.->|training context| Coach
Hyrox-Companion/
βββ client/ # React frontend (Vite SPA)
β βββ src/
β βββ components/ # UI components
β β βββ ui/ # shadcn/ui primitives
β β βββ icons/ # Custom SVG icon components
β β βββ analytics/ # Analytics dashboard
β β βββ coach/ # AI coaching interface
β β βββ onboarding/ # Onboarding wizard
β β βββ plans/ # Training plan management
β β βββ settings/ # User preferences
β β βββ timeline/ # Drag-and-drop timeline
β β βββ workout/ # Workout logging
β β βββ workout-detail/ # Workout detail dialog (v2) + coach prescription UI
β β βββ exercise-input/ # Multi-set / single-set entry widgets
β β βββ exercise-row/ # Shared exercise row renderer
β βββ hooks/ # Custom React hooks
β βββ lib/ # Utilities & API client
β βββ pages/ # Route pages (Landing, Timeline, LogWorkout, Analytics, Settings)
βββ server/ # Express backend
β βββ gemini/ # Gemini AI parsing & prompt logic
β βββ middleware/ # Express middleware (CSP nonce, CSRF, idempotency)
β βββ routes/ # API route handlers
β βββ services/ # Business logic layer (includes workoutUseCases.ts)
β βββ storage/ # Database access layer (Drizzle)
β βββ utils/ # Server utilities
βββ shared/ # Shared code (client + server)
β βββ schema/ # Drizzle table definitions, Zod types, enums
βββ migrations/ # Drizzle SQL migrations
βββ cypress/ # E2E test suites
βββ .github/workflows/ # CI/CD pipelines (7 workflows)
βββ scripts/ # Build & maintenance scripts
βββ .claude/commands/review/ # Code-review skill profiles (security, privacy, ux, performance, business, qa, devops, all)
βββ docs/ # Documentation (11 living guides + dated snapshots)
Detailed documentation for each subsystem is available in the docs/ directory:
| Document | Description |
|---|---|
| Architecture Overview | End-to-end flows, service dependencies, RAG decision tree, schema pipeline |
| Environment Variables | Curated reference: what each env var unlocks, defaults, safety invariants |
| Client (Frontend) | React SPA: pages, components, routing, styling, PWA, error tracking |
| Server (Backend) | Express API: bootstrap, middleware stack, security, logging, graceful shutdown |
| Database | PostgreSQL schema, Drizzle ORM, pgvector, migrations, storage layer |
| AI and RAG | Gemini integration, workout parsing, auto-coach, RAG pipeline |
| State Management | TanStack Query, custom hooks, offline queue, utility functions |
| API Reference | All API endpoints with request/response shapes and rate limits |
| Authentication | Clerk setup, user sync, dev auth bypass, route protection |
| Integrations | Strava OAuth, Garmin Connect, email system, pg-boss queue, cron scheduling |
| Testing | Vitest, Cypress E2E, jest-axe accessibility tests, code-review skill profiles, CI workflows |
| Native Mobile | Capacitor vs. React Native comparison, packaging phases, cost trade-offs |
Interactive API documentation is available via Swagger UI at /api/docs when the server is running. The spec is auto-generated from Zod schemas using @asteasolutions/zod-to-openapi, ensuring documentation always stays in sync with the codebase.
A committed OpenAPI 3.0 snapshot is also kept at docs/openapi.json. Regenerate it with pnpm docs:openapi; the Build CI job fails if the committed file drifts from the Zod schemas, making API changes diff-visible in every pull request.
Follow these instructions to run the full application ecosystem locally.
- Node.js (v20 or higher)
- pnpm (v9.x β run
corepack enableto auto-install) - PostgreSQL with the pgvector extension β production uses Railway for the main DB and Neon for vector embeddings
- A Clerk.dev account for auth (optional β use
ALLOW_DEV_AUTH_BYPASS=truefor local dev) - A Google AI Studio key for AI features (optional)
Copy the example environment file and fill in your values:
cp .env.example .envAt minimum, set the two required variables:
DATABASE_URLβ PostgreSQL connection stringENCRYPTION_KEYβ 32+ char hex key (generate withnode -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
See .env.example for the copy-template and docs/env-reference.md for the curated reference with per-variable context and safety invariants (Clerk auth, Gemini AI, Strava sync, Resend email, Web Push, Sentry, and more).
# Install dependencies
pnpm install
# Generate and run Drizzle ORM migrations
pnpm run db:generate
pnpm run db:migratepnpm devThis fires up the Vite frontend with HMR and the Express backend on port 5000. Visit http://localhost:5000 in your browser.
| Script | Description |
|---|---|
pnpm dev |
Start development server (Vite HMR + Express) |
pnpm build |
Production build (client + server) |
pnpm start |
Run production build |
pnpm check |
TypeScript type checking |
pnpm test |
Run Vitest unit & integration tests |
pnpm test:watch |
Run tests in watch mode |
pnpm test:smoke |
Fast smoke-test suite (via vitest.smoke.config.ts) for pre-push checks |
pnpm lint |
Run ESLint |
pnpm lint:fix |
Auto-fix lint issues |
pnpm format |
Format code with Prettier |
pnpm format:check |
Check formatting without writing |
pnpm db:generate |
Generate Drizzle migrations from schema changes |
pnpm db:migrate |
Run pending database migrations |
pnpm db:check |
Validate migration/schema consistency |
pnpm db:decode-entities |
One-off maintenance script that decodes HTML entities in stored text |
pnpm coach:influence |
Runs the AI coach influence/metrics harness against scripted scenarios |
pnpm docs:openapi |
Regenerate docs/openapi.json from the Zod registry (CI fails if stale) |
postinstall also runs script/patch-cypress-deps.js automatically to patch a vulnerable transitive Cypress dep.
| Layer | Tool | Coverage | Command |
|---|---|---|---|
| Unit & Integration | Vitest | 880+ tests across 89 files (80% threshold) | pnpm test |
| End-to-End | Cypress | 60+ tests across 12 spec suites | pnpm exec cypress open |
| Accessibility | jest-axe (via Vitest + jsdom) | Automated a11y assertions on interactive components (*.a11y.test.tsx) |
pnpm test |
| Type Safety | TypeScript 5.9 (strict) | Full codebase | pnpm check |
| Lint & Format | ESLint + Prettier | Full codebase | pnpm lint / pnpm format:check |
Opinionated code-review skill profiles live under .claude/commands/review/ and can be invoked as /review:<profile> (security, privacy, ux, performance, business, qa, devops, or all) for structured, role-based audits of the codebase.
Every push and pull request triggers automated pipelines via GitHub Actions:
| Workflow | Trigger | Purpose |
|---|---|---|
| Build | Push / PR | Lint, type check, production build, SonarCloud analysis |
| Test | Push / PR | Unit & integration test suite |
| Cypress | Push / PR | E2E browser tests |
| Migrations | Push / PR | Database schema consistency validation |
| Post-Migration | After migration | Post-migration health checks |
| Trivy | Push / PR / Weekly | Security vulnerability scanning |
| Dependency Review | PR | Audit new/updated dependencies for known vulnerabilities |
We target WCAG 2.1 Level AA conformance. The app is built on Radix UI primitives for robust focus management, supports keyboard-only navigation across every page, respects prefers-reduced-motion, and ships with automated jest-axe checks on interactive components.
Supported assistive tech (CI-validated via axe; manual spot-checks per release):
| Screen reader | Browser |
|---|---|
| VoiceOver | Safari (macOS / iOS) |
| NVDA | Firefox (Windows) |
| JAWS | Chrome (Windows) |
| TalkBack | Chrome (Android) |
Report an issue: open a GitHub issue with the accessibility label including the page, assistive tech + browser version, and the expected vs. actual behaviour.
Contributions make the open-source community an amazing place to learn, inspire, and create. Any contributions to HyroxTracker are greatly appreciated.
- Fork the Project.
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Verify your tests strictly (
pnpm check&pnpm test). - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request.
This project is licensed under the MIT License.