# 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/`) ```bash npm run dev # Vite dev server on port 5173 npm run build # Production build -> dist/ npm run preview # Preview production build ``` ### Backend (`backend/`) ```bash npm start # node src/index.js npm run dev # nodemon with auto-reload ``` ### Docker ```bash docker compose up -d --build # Build and run all services ``` ### Database ```bash 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.