Built with Next.js 16 (App Router) and React Server Components. Runs in Docker containers for environment consistency.
- Docker Desktop
make dev- App: http://localhost:3000
- Health: http://localhost:3000/api/health
# Database
make db-push # Push schema changes
make db-seed # Seed exercise library + demo user
make db-seed-fake # Populate demo user with realistic test data
make db-reset-user # Wipe all user data (keeps exercises + user record)
make db-studio # Open Drizzle Studio
make db-migrate # Run migrations
# Quality
make lint # ESLint
make build # Production build check
make verify # typecheck + lint + tests (run before pushing)
# Tests (run locally — no Docker needed)
make test # Run all tests once
make test-watch # Watch modeRun make (or make help) to list every target.
db:seed-fake populates the demo user (id=1) with:
- 2 programs ("Push Pull Legs A" and "Upper Body") with planned exercises and sets
- An active 12-week training cycle with Mon/Wed/Fri slots
- ~12 completed workout sessions spread over the past 4 weeks
# Populate
docker-compose exec app pnpm db:seed-fake
# Overwrite existing data
docker-compose exec app pnpm db:seed-fake --force
# Wipe everything and start fresh
docker-compose exec app pnpm db:reset-usermake dev # Start dev environment
make dev TEST=1 # Run tests before starting
make dev PROD=1 # Build and run production image
make dev SKIP_BUILD=1 # Skip Docker image rebuild
make dev CLEAN=1 # Force clean build (no cache)
make logs # Attach to logs only| Concern | Tool |
|---|---|
| Framework | Next.js 16 — App Router, React Server Components |
| Language | TypeScript (Strict Mode) |
| Database | PostgreSQL 16 (Dockerized) |
| ORM | Drizzle ORM |
| Authentication | Better Auth |
| Validation | Zod — required on all Server Actions |
| Styling | Tailwind CSS 4 |
| UI | shadcn/ui + Lucide React |
| PWA | Serwist (Service Workers) |
| Testing | Vitest |
- Server Components for all data fetching — never fetch in Client Components unless required for interactivity
- Server Actions for all mutations — no REST routes for CRUD
- Zod validation on every Server Action before touching the database
- Local state (
useState) only for UI interaction — no Redux/Zustand
- Every screen must feel like a native iOS app (fixed headers, bottom nav)
- Touch targets minimum 44×44px
- Use
:activefor tap feedback, not:hover - Primary actions go in pinned bottom bars
- Destructive actions at the bottom of the screen
- Reuse before adding — always check for existing components, hooks, and utilities before writing new ones. The feature-component split and
src/lib/utils/exist for this reason. Adding a duplicateformatTimeor a second picker modal is worse than importing the shared one. - No dead code — if a button has no
onClick, remove it or implement it. Don't leave placeholder UI. - No premature abstractions — don't extract a helper for something used once. Wait until it's needed in two or more places.
src/
├── app/ # Next.js pages and layouts only
├── components/
│ ├── features/ # Feature-specific Client Components
│ └── ui/ # shadcn/ui base components
├── contexts/ # React Contexts (e.g. WorkoutSessionContext)
├── db/
│ ├── index.ts # Drizzle client
│ └── schema/ # Schema definitions — single source of truth for types
├── lib/
│ ├── actions/ # Server Actions (one file per domain)
│ ├── utils/ # Pure utility functions (format, set-mapping, etc.)
│ └── validators/ # Zod schemas (colocated with actions)
├── __tests__/ # Vitest unit tests
└── types/ # Shared TypeScript types (inferred from Drizzle)
| Table | Purpose |
|---|---|
exercises |
Exercise library (system + custom) |
programs |
Workout templates |
program_exercises |
Exercises within a program |
workout_sessions |
Workout logs (date, duration) |
workout_sets |
Sets performed (reps, weight, RPE) |
Types are inferred from Drizzle schema — never define them manually:
type ProgramSet = typeof programSets.$inferSelect;The app exposes a Model Context Protocol endpoint so MCP clients (Claude Desktop/Code, etc.) can read and write your programs, training cycles, and profile/weight on your behalf.
- Endpoint:
http://localhost:3000/api/mcp(Streamable HTTP) - Auth: OAuth via Better Auth's
mcpplugin. The client runs the OAuth flow against your normal login; the access token is scoped to your user. Discovery metadata lives at/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource. - Scope: read + write for programs (incl. exercises/sets), training cycles (incl. slots), and profile/weight.
userIdis always taken from the token — never a tool argument — so a client can only ever touch its own data.
Connect a client — point it at the endpoint URL and complete the browser OAuth prompt. With the Claude Code CLI:
claude mcp add --transport http logeverylift http://localhost:3000/api/mcpTools (~13): list_programs, get_program, create_program, update_program, delete_program, edit_program_exercise; list_training_cycles, get_training_cycle, manage_training_cycle, edit_cycle_slot; get_profile, update_profile, manage_weight. Tool code lives in src/lib/mcp/tools/; the route is src/app/api/[transport]/route.ts.
Adding npm deps for the dev container:
node_modulesis an anonymous Docker volume baked from the image, so a host-onlypnpm addwon't reach the running container. After changing dependencies, rebuild:docker-compose build app && docker-compose up -d --force-recreate --renew-anon-volumes app(ormake dev CLEAN=1).
docker build -t logeverylift-pwa:latest .
docker run -p 3000:3000 -e DATABASE_URL=... logeverylift-pwa:latestEnvironment variables:
DATABASE_URL—postgresql://user:pass@host:5432/dbNODE_ENV—developmentorproductionNEXT_TELEMETRY_DISABLED—1
logeverylift/
├── src/ # Application code (see Directory Structure above)
├── drizzle/ # Committed SQL migrations + meta snapshots
├── scripts/ # Dev, DB, and migration scripts (dev.sh, migrate.ts, seed.ts)
├── e2e/ # Playwright end-to-end specs
├── public/ # Static assets and PWA icons
├── docs/ # Project documentation
└── .github/
└── workflows/ # CI and security pipelines
| Workflow | Trigger | Purpose |
|---|---|---|
| CI | push, PR | typecheck, tests, build & push Docker image |
| Dependency Review | PR | block PRs that introduce known-vulnerable dependencies |
| Scorecard | push, weekly | OpenSSF supply-chain grade → Security tab + badge |
| Container Scan | push, weekly | Trivy image scan → Security tab |
- Port 3000 in use:
lsof -i :3000 - DB connection failed:
docker-compose ps/docker-compose logs postgres - Type errors after schema change:
docker-compose exec app pnpm db:generate





