# 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 .