# TrainUs

A local-first PWA fitness training application. Plan, track, and execute your workouts entirely on your device — no server needed. The dedicated website is [here](https://kaivalya.eu/projects/trainUs/).

> [!WARNING]
> **AI authorship.** The vast majority of the source code in this project was written by AI assistants (primarily Claude). The human author directed the architecture, reviewed every change, guided the implementation, and made all design and product decisions — but in terms of who typed the lines, that credit belongs mostly to the AI. This is stated here for transparency, not as a caveat: the code was reviewed and validated at every step. Why using it ? Mostly time (I have the skills but not the time) and learning (how to use it).

## Tech Stack

- **Vue 3** + **Vite** — reactive UI framework with fast build tooling
- **SQLite WASM** with **OPFS** — client-side database persistence
- **i18next** — internationalization (English, French & Danish)
- **Chart.js** — workout statistics
- **vite-plugin-pwa** + Workbox — offline support and install prompt
- **Playwright** (Python) — end-to-end testing

## Getting Started

### Prerequisites

- Node.js ≥ 18
- Python ≥ 3.10 (for tests)

### Development

```bash
npm install
npm run dev        # dev server at http://localhost:5173
npm run lint       # ESLint (src/)
npm run lint:fix   # ESLint (src/), auto-fix
npm run format     # Prettier (auto-fix)
npm run format:check  # Prettier (check only)
```

The dev server runs at `http://localhost:5173` with COOP/COEP headers enabled (precautionary; the SAH-pool VFS used by this app does not strictly require them). The Playwright test suite runs its own instance on port 5199 instead (see `tests/README.md`).

> **Note:** OPFS persistence requires a modern browser (Firefox 111+, Chrome 102+, Safari 16.4+) and does not work in private browsing mode. If OPFS is unavailable, the app displays a compatibility error.

### Build

```bash
npm run build
```

Produces a static `dist/` folder ready for deployment.

### Deploy

TrainUs uses a single deployment: copy the `dist/` folder to the site root, replacing the previous build (no per-version folders). User data is preserved across updates — it lives at a stable OPFS location, and a startup migration step applies any schema changes.

TrainUs is a static site — it works on any web server or CDN. COOP/COEP headers are not required for the SAH-pool VFS used here, but are recommended for defense-in-depth and may be required if the VFS is ever changed:

#### Nginx

```nginx
location / {
    add_header Cross-Origin-Opener-Policy "same-origin";
    add_header Cross-Origin-Embedder-Policy "require-corp";
}
```

#### Apache

```apache
<IfModule mod_headers.c>
    Header always set Cross-Origin-Opener-Policy "same-origin"
    Header always set Cross-Origin-Embedder-Policy "require-corp"
</IfModule>
```

#### Caddy

```caddy
header {
    Cross-Origin-Opener-Policy "same-origin"
    Cross-Origin-Embedder-Policy "require-corp"
}
```

#### Netlify (`public/_headers`)

```
/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp
```

#### Vercel (`vercel.json`)

```json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
        { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
      ]
    }
  ]
}
```

#### Preview production build locally

```bash
npm run preview
```

Serves `dist/` at `http://localhost:4173` with the correct headers (injected by Vite).

### Tests

#### Setup (first time)

```bash
cd tests
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/playwright install firefox chromium
```

#### Running tests

| Command                        | Browsers           | Mode                         |
| :----------------------------- | :----------------- | :--------------------------- |
| npm run test                   | firefox            | sequential                   |
| npm run test:chromium          | chromium           | sequential                   |
| npm run test:parallel          | firefox + chromium | parallel                     |
| npm run test:parallel:firefox  | firefox            | parallel                     |
| npm run test:parallel:chromium | chromium           | parallel                     |
| npm run test:parallel:n        | firefox + chromium | parallel, fixed worker count |

```bash
# Sequential — Firefox only (simple)
npm run test
# or: cd tests && .venv/bin/python -m pytest --browser firefox -v

# Parallel — both browsers, all CPU cores (fastest)
npm run test:parallel

# Parallel — Firefox only, all CPU cores
npm run test:parallel:firefox

# Parallel — both browsers, limited to N workers (default 4)
WORKERS=2 npm run test:parallel:n

# Via shell script — both browsers, auto workers
./tests/run_parallel.sh

# Via shell script — Firefox only, 4 workers
PYTEST_WORKERS=4 PYTEST_BROWSERS="firefox" ./tests/run_parallel.sh

# Single test file, Firefox only
cd tests && .venv/bin/python -m pytest test_step2_settings.py --browser firefox -v

# Single test file in parallel via shell script
./tests/run_parallel.sh test_step3_exercises.py
```

### Website

The project site (docs + blog) is a Hugo static site under `website/`.

#### Prerequisites

- [Hugo](https://gohugo.io/installation/) ≥ 0.123 extended edition

#### Development

```bash
npm run site:dev   # Hugo dev server at http://localhost:1313 (with drafts)
```

#### Build

```bash
npm run site:build   # Minified output in website/public/
```

#### Deploy

The site is a static folder — copy `website/public/` to any web host. Before building for production, set two values in `website/hugo.toml`:

```toml
baseURL = "https://yourdomain.example/trainus/"   # full URL to the site root

[params]
  appUrl = "https://yourdomain.example/app/"      # leave empty to show "Coming soon"
```

No special server headers are required for the website itself (COOP/COEP are only needed for the app).

## Documentation

You can access the website [here](https://kaivalya.eu/projects/trainUs).

### Project

- [Technical Documentation](website/content/developers/technical.en.md) -- Architecture, tech stack, data model, and feature details
- [User Documentation](website/content/docs/user-guide/index.en.md) -- How each feature works from the user's perspective
- [Implementation Plan](docs/plan.md) -- Step-by-step feature plan and progress tracking
- [Decisions Log](docs/decisions-log.md) -- All design decisions with rationale

## Credits

### Icons

The app icon is [Mighty Force](https://game-icons.net/1x1/delapouite/mighty-force.html) by [Delapouite](https://delapouite.com), sourced from [game-icons.net](https://game-icons.net) and licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). Thank you for making such a great icon library freely available!

UI icons are provided by [Bootstrap Icons](https://icons.getbootstrap.com), licensed under MIT.

### Third-Party Software

| Package                                                                                                                    | License              |
| -------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| [Vue 3](https://github.com/vuejs/core)                                                                                     | MIT                  |
| [Vue Router](https://github.com/vuejs/router)                                                                              | MIT                  |
| [Vite](https://github.com/vitejs/vite)                                                                                     | MIT                  |
| [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue)                                                            | MIT                  |
| [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa)                                                             | MIT                  |
| [Workbox](https://github.com/GoogleChrome/workbox) (bundled by vite-plugin-pwa)                                            | Apache-2.0           |
| [@sqlite.org/sqlite-wasm](https://sqlite.org/wasm/doc/trunk/index.md)                                                      | Apache-2.0           |
| [Bootstrap Icons](https://icons.getbootstrap.com)                                                                          | MIT                  |
| [Mighty Force](https://game-icons.net/1x1/delapouite/mighty-force.html) by [Delapouite](https://delapouite.com) (app icon) | CC BY 3.0            |
| [Chart.js](https://github.com/chartjs/Chart.js)                                                                            | MIT                  |
| [vue-chartjs](https://github.com/apertureless/vue-chartjs)                                                                 | MIT                  |
| [DOMPurify](https://github.com/cure53/DOMPurify)                                                                           | Apache-2.0 / MPL-2.0 |
| [marked](https://github.com/markedjs/marked)                                                                               | MIT                  |
| [i18next](https://github.com/i18next/i18next)                                                                              | MIT                  |
| [i18next-vue](https://github.com/i18next/i18next-vue)                                                                      | MIT                  |
| [Prettier](https://github.com/prettier/prettier)                                                                           | MIT                  |
| [Playwright](https://github.com/microsoft/playwright)                                                                      | Apache-2.0           |
| [pytest](https://github.com/pytest-dev/pytest)                                                                             | MIT                  |
| [pytest-playwright](https://github.com/microsoft/playwright-python)                                                        | Apache-2.0           |
| [pytest-xdist](https://github.com/pytest-dev/pytest-xdist)                                                                 | MIT                  |

## License

TrainUs — an open, local-first, offline-capable fitness tracking application where you can create and share your own training sessions.
Copyright (C) 2026 [David Florance](http://kaivalya.eu)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
