Rebrand to Proof of Work; multi-user 0.4 package with curated library sync
Repo cleanup - Add top-level .gitignore (was missing; node_modules, .next, *.s9pk, image.tar, seed/data/*.db, log files, etc.) and a root README. - Delete legacy start9/0.3.5/ package (StartOS 0.3.5 wrapper, no longer the deploy target). - Delete start9-example-packaging/ (template from another project). - Delete planning docs (START9_PACKAGING_LOG.md, VERSIONING.md, STARTOS_0.4_UPGRADE_PROMPT.md, ICON_FILES_INDEX.md, etc.) — info now lives in the deploy guide and code comments. - Drop the standalone Dockerfile, docker-compose.yml, ICON_*, and dev log/build artifacts from the app dir. - Drop the v0.1.0:18/19/20 version files (they belonged to the legacy workout-log package and don't apply to the new id). Rename + new package - Rename app dir workout-planner/ -> proof-of-work/. - Rename StartOS package id workout-log -> proof-of-work; the new id makes this a brand new StartOS service (clean cutover from the old one rather than in-place upgrade). - Reset version graph; v1.0.0:1 is the seeded cutover release. The Dockerfile bakes a one-time /data snapshot and docker_entrypoint.sh copies it into the new volume on truly-fresh first boot only (both /data/app.db missing AND /data/.seeded absent). - Move start9/0.4-migration/ -> start9/0.4/; the old start9/0.4/ stub is gone. Curated exercise library (multi-user-aware) - proof-of-work/prisma/exercises.seed.json is the canonical library shipped to every install (164 exercises today, dumped from the live snapshot). - proof-of-work/scripts/sync-library.cjs (npm run sync-library) refreshes the JSON from start9/0.4/seed/data/app.db after refresh_seed.sh. - proof-of-work/prisma/seed.ts now reads from the JSON instead of a hardcoded 52-exercise array; runs at Docker build time to seed the fallback DB and on first boot for fresh installs. - proof-of-work/prisma/ensureExerciseLibrary.cjs runs on every container boot (from docker_entrypoint.sh) and INSERT OR IGNOREs every library entry for every user, keyed on (userId, name). Library updates flow to existing installs on package upgrade; user-custom exercises (isCustom=true) and any colliding names are never overwritten; removed exercises stay on existing installs (additive-only). Deploy guide (start9/0.4/DEPLOY_040.md) - Rewritten end-to-end for the workout-log -> proof-of-work cutover: refresh_seed, sync-library, build, sideload, verify, rotate creds, stop the old service, then post-cutover cleanup release v1.0.0:2.
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
# Workout Planner — File & Folder Reference
|
||||
|
||||
_Last updated: February 18, 2026_
|
||||
|
||||
## Project Root (`proof-of-work/`)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `package.json` | Dependencies: Next.js 14, React 18, Prisma, Tailwind, Zod, Lucide, bcryptjs |
|
||||
| `next.config.js` | Next.js configuration |
|
||||
| `tailwind.config.ts` | Tailwind setup with zinc color palette |
|
||||
| `tsconfig.json` | TypeScript config with `@/` path alias |
|
||||
| `middleware.ts` | Route protection — redirects unauthenticated requests away from `/main/*` |
|
||||
| `postcss.config.js` | PostCSS for Tailwind |
|
||||
| `.env` / `.env.local` | Database URL and secrets (not committed) |
|
||||
| `.env.example` | Template for env vars |
|
||||
| `Dockerfile` | Container build for production |
|
||||
| `docker-compose.yml` | Docker orchestration with volume mount for DB |
|
||||
| `.gitignore` | Standard Next.js + Prisma ignores |
|
||||
|
||||
## `app/` — Next.js App Router Pages & API
|
||||
|
||||
### Root Layout & Entry
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/layout.tsx` | Root HTML layout, global fonts, meta tags, PWA registration |
|
||||
| `app/globals.css` | Tailwind imports, CSS variables (`--sidebar-width`, `--bottom-nav-height`) |
|
||||
| `app/page.tsx` | Landing page — redirects to `/main/dashboard` if logged in |
|
||||
|
||||
### Auth (`app/auth/`)
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/auth/login/page.tsx` | Login form (email + password) |
|
||||
| `app/auth/login/actions.ts` | Server action for login — validates credentials, creates session, sets cookie |
|
||||
|
||||
### Main App (`app/main/`)
|
||||
All pages under `/main/` require authentication (enforced by middleware).
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/main/layout.tsx` | Authenticated layout with sidebar (desktop) and bottom nav (mobile) |
|
||||
| `app/main/navigation.tsx` | Navigation component — 5 links: Dashboard, Workouts, Exercises, Import, Settings + logout |
|
||||
| `app/main/actions.ts` | Server actions (logout) |
|
||||
|
||||
### Dashboard
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/main/dashboard/page.tsx` | Summary stats, recent workouts, personal bests |
|
||||
|
||||
### Workouts
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/main/workouts/page.tsx` | Workout history list with date filters, upload button linking to import |
|
||||
| `app/main/workouts/new/page.tsx` | Create new workout — pick exercises, log sets |
|
||||
| `app/main/workouts/[id]/page.tsx` | View/edit a single workout |
|
||||
|
||||
### Exercises
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/main/exercises/page.tsx` | Exercise library list with search and equipment type filter pills |
|
||||
| `app/main/exercises/[id]/page.tsx` | Exercise detail — edit name/type/muscles/fields, view workout history for this exercise |
|
||||
|
||||
### Import
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/main/import/page.tsx` | Server component wrapper — auth check, renders `ImportCSVPage` |
|
||||
| `app/main/import/page-csv.tsx` | Client component (~610 lines) — full CSV import flow: upload → review queue → approve/skip each workout |
|
||||
|
||||
### Settings
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/main/settings/page.tsx` | Server component wrapper for settings form |
|
||||
|
||||
### API Routes (`app/api/`)
|
||||
|
||||
| Route | Methods | Purpose |
|
||||
|-------|---------|---------|
|
||||
| `app/api/auth/route.ts` | POST | Login — validate credentials, create session, return token |
|
||||
| `app/api/auth/logout/route.ts` | POST | Logout — delete session |
|
||||
| `app/api/exercises/route.ts` | GET, POST | List exercises (with search), create new exercise |
|
||||
| `app/api/exercises/[id]/route.ts` | GET, PUT, DELETE | Get/update/delete a single exercise |
|
||||
| `app/api/workouts/route.ts` | GET, POST | List workouts (with date filters, pagination), create workout with sets |
|
||||
| `app/api/workouts/[id]/route.ts` | GET, PUT, DELETE | Get/update/delete a single workout |
|
||||
| `app/api/workouts/[id]/sets/route.ts` | POST, PUT, DELETE | Manage individual sets within a workout |
|
||||
| `app/api/workouts/import/route.ts` | POST | Import endpoint (used by import page) |
|
||||
| `app/api/workouts/import/save/route.ts` | POST | Save a single approved imported workout to DB |
|
||||
| `app/api/import/parse/route.ts` | POST | Parse CSV upload — maps exercise names, groups by date, returns structured data with unmapped names |
|
||||
| `app/api/preferences/route.ts` | GET, POST | Get/update user preferences (theme, weight unit, Claude AI settings) |
|
||||
| `app/api/health/route.ts` | GET | Health check endpoint |
|
||||
|
||||
## `components/` — Reusable UI Components
|
||||
|
||||
### Exercises
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `components/exercises/AddExerciseForm.tsx` | Form for creating new exercises with type, muscle groups, and tracked fields |
|
||||
| `components/exercises/ExerciseCard.tsx` | Card component for exercise list items |
|
||||
| `components/exercises/ExercisesClient.tsx` | Client-side exercises list with search/filter state |
|
||||
|
||||
### Workouts
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `components/workouts/WorkoutForm.tsx` | Full workout logging form — exercise selection, set entry, notes |
|
||||
| `components/workouts/WorkoutCard.tsx` | Expandable workout card for history list — shows date, stats line (exercises, total sets, duration), expandable exercise details with grouped set summaries |
|
||||
| `components/workouts/ExercisePicker.tsx` | Modal/form for selecting an exercise from the library when logging a workout, includes inline exercise creation |
|
||||
| `components/workouts/SetRow.tsx` | Single set row in the workout form — inputs for reps, weight, unit dropdown, RPE, duration, distance, calories, notes. Fields shown are controlled by exercise's `inputFields` |
|
||||
|
||||
### Import
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `components/import/WorkoutImportClient.tsx` | Import-related client component |
|
||||
|
||||
### Settings
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `components/settings/SettingsForm.tsx` | Settings form — theme selector, weight unit, Claude AI toggle + API key field |
|
||||
|
||||
## `lib/` — Shared Utilities & Data Access
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `lib/prisma.ts` | Singleton Prisma client instance |
|
||||
| `lib/auth.ts` | Auth utilities: `hashPassword`, `verifyPassword`, `createSession`, `validateSession`, `getCurrentUser` |
|
||||
| `lib/formatSets.ts` | `formatSetsSummary()` — groups consecutive same-weight sets into compact display (e.g., "245 x 3/3/3") |
|
||||
| `lib/exerciseSearch.ts` | Exercise search/filter logic |
|
||||
| `lib/utils.ts` | General utility functions (e.g., `cn()` for class merging with clsx + tailwind-merge) |
|
||||
| `lib/db/exercises.ts` | Database queries for exercises |
|
||||
| `lib/db/workouts.ts` | Database queries for workouts |
|
||||
| `lib/db/stats.ts` | Dashboard statistics queries |
|
||||
|
||||
## `prisma/` — Database Schema & Data
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `prisma/schema.prisma` | Full data model — 13 models (User, Session, Exercise, Workout, SetLog, Program, ProgramWeek, ProgramDay, ProgramExercise, Equipment, ContentItem, ContentChunk, AISuggestion, UserPreferences) |
|
||||
| `prisma/seed.ts` | Database seeding script |
|
||||
| `prisma/data/app.db` | **The actual SQLite database file** (not `prisma/dev.db`) |
|
||||
| `prisma/dev.db` | Stale/empty — do not use |
|
||||
|
||||
## `types/` — TypeScript Type Definitions
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `types/index.ts` | Shared types: `WorkoutWithSets`, `SetLogWithExercise`, `ExerciseWithStats`, `DashboardStats`, `SearchFilters`, `PaginationMeta`, `ParsedSet`, `ParsedExercise`, `ParsedWorkout`, `ImportParseResponse`, `ReviewedWorkout` |
|
||||
|
||||
## `public/` — Static Assets & PWA
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `public/manifest.json` | PWA manifest — app name, icons, theme color |
|
||||
| `public/sw.js` | Service worker for offline caching |
|
||||
| `public/sw-register.js` | Service worker registration script |
|
||||
| `public/icons/` | App icons at multiple sizes (72–512px) including maskable variants, plus SVG favicon |
|
||||
|
||||
## `scripts/` — Server Management
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `scripts/start.sh` | Start the Next.js server (production) |
|
||||
| `scripts/stop.sh` | Stop the server using stored PID |
|
||||
| `scripts/rebuild.sh` | Rebuild and restart |
|
||||
| `scripts/setup-autostart.sh` | Configure the app to start on boot |
|
||||
| `scripts/generate-icons.js` | Generate PWA icons from SVG source |
|
||||
|
||||
## `import-data/` — Historical Workout Data
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `import-data/proof-of-work-feb2026.csv` | Parsed handwritten workout logs (Jan–Feb 2026, ~362 rows). Format: `date,exercise,weight,reps,notes`. More pages to be added. |
|
||||
|
||||
## `docs/` — Project Documentation
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `docs/PROJECT_OVERVIEW.md` | High-level overview: features, tech stack, future phases, design philosophy |
|
||||
| `docs/FILE_REFERENCE.md` | This file — every file and folder explained |
|
||||
|
||||
## `logs/` — Server Logs
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `logs/server.log` | Server stdout |
|
||||
| `logs/server-error.log` | Server stderr |
|
||||
@@ -0,0 +1,133 @@
|
||||
# Workout Planner — Project Overview
|
||||
|
||||
_Last updated: February 18, 2026_
|
||||
|
||||
## What This Is
|
||||
|
||||
A self-hosted workout logging and planning app built for personal use. The core idea is a mobile-first tool for tracking strength training workouts — what exercises you did, how many sets/reps, at what weight — with a dark, minimal UI that stays out of your way.
|
||||
|
||||
The app runs on a local server (Raspberry Pi, NAS, or any Docker host) with no dependency on third-party services. All data lives in a local SQLite database.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework**: Next.js 14 (App Router, server components + client components)
|
||||
- **Database**: SQLite via Prisma ORM (`prisma/data/app.db`)
|
||||
- **Styling**: Tailwind CSS, dark mode only (zinc palette, `bg-[#0A0A0A]` base)
|
||||
- **Auth**: Cookie-based sessions with bcrypt password hashing, 30-day token expiry
|
||||
- **Icons**: Lucide React
|
||||
- **Validation**: Zod schemas on all API routes
|
||||
- **PWA**: Service worker + manifest for mobile install
|
||||
- **Deployment**: Docker + docker-compose, with shell scripts for start/stop/rebuild
|
||||
|
||||
## Current Features (What's Built)
|
||||
|
||||
### Workout Logging
|
||||
The main feature. You log a workout by picking exercises from your library, entering sets with weight/reps, and saving. The app records the date, all set data, optional notes, difficulty rating, and duration.
|
||||
|
||||
Sets are displayed in a compact grouped format: consecutive sets at the same weight are collapsed (e.g., "245 x 3/3/3" instead of three separate lines). This is handled by the shared `formatSetsSummary()` helper in `lib/formatSets.ts`.
|
||||
|
||||
Each set supports: reps, weight (lbs or kg), RPE (1-10), duration (seconds), distance, calories, and notes. Not every field is shown — the exercise's `inputFields` array controls which fields appear in the logging UI.
|
||||
|
||||
### Exercise Library
|
||||
~101 exercises currently in the database, each with equipment type, muscle group tags, and configurable tracked fields. Exercises are per-user and can be custom.
|
||||
|
||||
Equipment types include: barbell, dumbbell, bodyweight, cable, kettlebell, machine, cardio, eq bar, hex bar, grippers, ring, and other. These are extensible — the API accepts any string for type, muscle groups, and input fields (the "+" button feature).
|
||||
|
||||
Each exercise detail page shows a history of all logged workouts for that exercise, displayed as a clean list with date, set count, and grouped weight summary.
|
||||
|
||||
### CSV Import
|
||||
A pipeline for importing historical workout data from CSV files. The workflow was designed around the specific use case of photographing handwritten workout logs, having an LLM convert them to CSV format, then importing into the app.
|
||||
|
||||
**Import flow**: Upload CSV → API parses and maps exercise names → review each workout one-at-a-time in an editable form → approve to save to DB, or skip/delete. No data touches the database until explicitly approved.
|
||||
|
||||
The parser (`app/api/import/parse/route.ts`) includes a 26-entry name mapping table (`NAME_MAP`) that translates CSV shorthand to database exercise names (e.g., "BB Row" → "Barbell Row", "CoC" → "Captains of Crush"). It handles M/D/YYYY and ISO date formats, detects kg units from notes, and returns a list of unmapped exercise names so they can be created before import.
|
||||
|
||||
### Dashboard
|
||||
Shows summary stats: total workouts, total volume, recent workouts. The dashboard queries are in `lib/db/stats.ts`.
|
||||
|
||||
### Workout History
|
||||
Paginated list of past workouts, each rendered as an expandable card showing exercises and their set summaries. Filter by date range. Total set count displayed per workout. Upload button in the header links to the import page.
|
||||
|
||||
### Exercise Search & Filtering
|
||||
Exercises list supports real-time search and filter pills for equipment type. The filter tags are dynamic — derived from the actual exercise data rather than a hardcoded list.
|
||||
|
||||
### Settings
|
||||
Theme preference (light/dark/system), default weight unit (lbs/kg), optional Claude AI integration toggle with API key field. The rest timer setting was removed as unused.
|
||||
|
||||
### Custom Values ("+" Buttons)
|
||||
Exercise types, tracked fields, and muscle groups all support adding custom values via inline "+" buttons. The API validates with `z.string()` rather than `z.enum()`, so any value is accepted and persisted.
|
||||
|
||||
### PWA Support
|
||||
Service worker and manifest for installing as a mobile app. Icons generated at multiple sizes (72px through 512px, including maskable variants).
|
||||
|
||||
## Data Model (Key Entities)
|
||||
|
||||
- **User** — email/password auth, one user per instance in practice
|
||||
- **Exercise** — name, type (equipment), muscleGroups (JSON array), inputFields (JSON array), per-user
|
||||
- **Workout** — date, optional name/notes/duration/difficulty/calories
|
||||
- **SetLog** — links a workout to an exercise, stores reps/weight/weightUnit/RPE/duration/distance/calories/notes
|
||||
- **UserPreferences** — theme, default weight unit, Claude AI settings
|
||||
- **Program/ProgramWeek/ProgramDay/ProgramExercise** — schema exists but not yet implemented in UI
|
||||
- **Equipment** — schema exists but not yet implemented in UI
|
||||
- **ContentItem/ContentChunk** — schema exists for future knowledge base feature
|
||||
- **AISuggestion** — schema exists for future AI coaching feature
|
||||
|
||||
## Future Phases (Planned)
|
||||
|
||||
### Phase 1: More Historical Data Import
|
||||
Continue photographing and importing handwritten workout logs. The CSV format and import flow are ready; just need more pages transcribed.
|
||||
|
||||
### Phase 2: Training Programs
|
||||
The database schema already supports structured programs (Program → Weeks → Days → Exercises with sets/reps/RPE targets). The UI for creating, viewing, and following programs has not been built yet.
|
||||
|
||||
### Phase 3: Analytics & Progress Tracking
|
||||
Visualizations for tracking progress over time — charts for weight progression per exercise, volume trends, frequency heatmaps, personal records timeline. The data is all there; just needs frontend visualization.
|
||||
|
||||
### Phase 4: AI Coaching (Claude Integration)
|
||||
The settings already have an "Enable Claude AI" toggle and API key field. The plan is to use Claude to analyze workout history and provide suggestions — exercise recommendations, program adjustments, identifying plateaus, recovery recommendations. The `AISuggestion` model and `ContentItem`/`ContentChunk` models support this.
|
||||
|
||||
### Phase 5: Equipment Inventory
|
||||
Track what equipment is available in the home gym. The `Equipment` model exists with name, type, quantity, weight fields. Could inform exercise recommendations and program generation.
|
||||
|
||||
### Phase 6: Content Library
|
||||
Upload training PDFs, link YouTube videos, store training knowledge. The `ContentItem` and `ContentChunk` models support chunked text storage with page numbers and video timestamps for future RAG-style search.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
- **Dark mode only in practice** — the zinc/black palette is the primary design language. Light mode exists in the theme toggle but the app is designed dark-first.
|
||||
- **Mobile-first** — bottom navigation bar on mobile, sidebar on desktop. Touch-friendly tap targets.
|
||||
- **Minimal chrome** — data-dense views, compact set summaries, no unnecessary decoration.
|
||||
- **Client-side state for in-progress work** — workout forms and import review queue keep data in React state until explicitly saved. No auto-save to DB.
|
||||
- **Flexible schema** — exercise types, muscle groups, and tracked fields are open strings, not enums. New values can be added without code changes.
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- **Weight units**: Most exercises default to lbs. Kettlebell exercises, Turkish Get Ups, and windmills default to kg. The `defaultWeightUnit` field on Exercise controls this.
|
||||
- **Set summary format**: `formatSetsSummary()` is the single source of truth for displaying sets compactly. Used in workout cards, exercise history, and import review.
|
||||
- **Date display**: Dates show year (e.g., "Jan 27, 2026") to avoid ambiguity across year boundaries.
|
||||
- **Enter key behavior**: In set logging forms, Enter advances to the next field rather than submitting the form.
|
||||
- **API patterns**: All API routes use Zod validation, return JSON, and follow REST conventions. Auth is via `getCurrentUser()` which reads the session cookie.
|
||||
|
||||
## Running the App
|
||||
|
||||
```bash
|
||||
# Development
|
||||
npm run dev
|
||||
|
||||
# Production (Docker)
|
||||
docker-compose up -d
|
||||
|
||||
# Or via scripts
|
||||
./scripts/start.sh # start the server
|
||||
./scripts/stop.sh # stop it
|
||||
./scripts/rebuild.sh # rebuild and restart
|
||||
```
|
||||
|
||||
Database lives at `prisma/data/app.db`. Environment variables in `.env` / `.env.local` set the `DATABASE_URL` connection string.
|
||||
|
||||
## Important Notes for Handoff
|
||||
|
||||
- The actual database file is `prisma/data/app.db`, NOT `prisma/dev.db` (which exists but is empty/stale).
|
||||
- SQLite on some mounted filesystems (Docker volumes, network mounts) can have journal mode issues. If you get "disk I/O error", try copying the DB locally, modifying with `PRAGMA journal_mode=OFF`, then copying back.
|
||||
- The import CSV file at `import-data/proof-of-work-feb2026.csv` contains parsed data from handwritten logs covering January-February 2026. More pages will be added over time.
|
||||
- Exercise name mapping in the import parser (`NAME_MAP` in `app/api/import/parse/route.ts`) should be updated as new shorthand names are encountered in CSV data.
|
||||
Reference in New Issue
Block a user