# TrainUs
A local-first PWA fitness training application. Plan, track, and execute your workouts entirely on your device — no server needed.
> [!WARNING]
> IA has been massively used.
> I have the technical skills to carry out this kind of project, but not the time. The rise of AI tools changed the equation: what would have taken me months of evenings and weekends became achievable. AI didn’t replace expertise — it made possible what wasn’t, purely due to time constraints. It was also an opportunity to learn how to work with these tools, which is a benefit in itself: I got to test and compare quite a few models and assistants throughout the development.
## 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 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
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
#### 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 .