Files
gravl/CLAUDE.md
T

4.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Gravl is a fitness/workout tracking app (PPL - Push/Pull/Legs) with progression tracking. The UI is in Swedish. It uses a React frontend, Express backend, and PostgreSQL database, deployed via Docker with nginx and Traefik.

Commands

Frontend (frontend/)

npm run dev       # Vite dev server on port 5173
npm run build     # Production build -> dist/
npm run preview   # Preview production build

Backend (backend/)

npm start         # node src/index.js
npm run dev       # nodemon with auto-reload

Docker

docker compose up -d --build   # Build and run all services

Database

psql -h localhost -U postgres -d gravl -f db/init.sql   # Initialize schema + seed data

There are no test or lint configurations.

Architecture

Frontend (React 18 + Vite, no TypeScript)

  • Entry: main.jsx sets up React Router v6 with AuthProvider context
  • Top-level routing (main.jsx): /login, /register, /onboarding use route guards (AuthRoute, ProtectedRoute)
  • In-app navigation (App.jsx): Uses useState view switching (not URL routes) between 'dashboard', 'profile', 'progress', 'select-workout', 'workout'
  • State: AuthContext is the only shared state (token in localStorage, user profile). No Redux or other state libraries. Component-level state via useState
  • API calls: Direct fetch() in components with API_URL = '/api' constant. No shared API service layer
  • Styling: Plain CSS with custom properties for theming. Two files: index.css (globals) and App.css (~1900 lines, organized by component sections). Dark theme with orange accent (#ff6b35). Mobile-first, max-width 600px
  • Icons: Custom SVG icon library in components/Icons.jsx (no emoji usage per design decision)
  • Pages directory: src/pages/ holds full-page components (Dashboard.jsx, WorkoutPage.jsx, LoginPage.jsx, RegisterPage.jsx, OnboardingWizard.jsx, ProfilePage.jsx, ProgressPage.jsx, WorkoutSelectPage.jsx)
  • Input components: components/StepperInput.jsx (pure controlled — no internal useState), WeightInput.jsx (2.5kg steps, kg suffix), RepsInput.jsx (1-rep steps). Used in workout set rows.

Backend (Express, single-file)

  • All routes in src/index.js — no separation into route files or controllers
  • Auth: JWT with 30-day expiry, bcryptjs for passwords, authMiddleware for protected routes
  • Database: pg with parameterized queries ($1, $2 placeholders)
  • Currently hardcodes program ID=1 in many queries
  • Env vars (all have defaults): JWT_SECRET, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME

Database (PostgreSQL)

  • Schema in db/init.sql: users, programs, program_days, exercises, program_exercises, workout_logs
  • Seeded with one PPL program (Push A/B, Pull A/B, Legs A/B) and 18 exercises

Deployment

  • Frontend: multi-stage Docker build (node -> nginx), nginx proxies /api to gravl-backend:3001
  • Backend: node:20-alpine container on port 3001
  • External PostgreSQL on homelab Docker network
  • Traefik reverse proxy at gravl.homelab.local

Conventions

  • Swedish language for all UI text, some variable names and comments
  • Functional components only, hooks throughout
  • Workout-type CSS color variables: --workout-push, --workout-pull, --workout-legs
  • Progression logic: increase weight by 2.5kg when all sets hit max reps
  • StepperInput is a pure controlled component — no internal useState, all state in parent
  • 44px minimum touch targets on all interactive elements (stepper buttons, inputs)
  • Input font-size ≥ 16px everywhere (prevents iOS auto-zoom on focus)

agents/ Directory

Contains AI agent persona definitions (SOUL.md files) for different roles (architect, backend-dev, frontend-dev, coach, nutritionist, reviewer). The coach/ directory also has exercise data, program definitions (beginner/hypertrophy/strength), and foods data as JSON.