Restoring a backup copied the backup's schema_version into the live DB. Once SCHEMA_VERSION moves past 1, restoring an older backup (already adapted to the current schema by the migration scripts) would leave an old stamp, so the next launch re-runs migrations against an already-adapted schema and blocks the app. schema_version is now excluded from the restore copy; the live stamp is correct by construction. Regression test tampers a backup's stamp and asserts the live one survives. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .claude | ||
| .github/workflows | ||
| docs | ||
| public | ||
| scripts | ||
| src | ||
| tests | ||
| website | ||
| .gitignore | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| eslint.config.js | ||
| index.html | ||
| jsconfig.json | ||
| LICENSE.md | ||
| notice.md | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| vite.config.js | ||
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.
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
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
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
location / {
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Embedder-Policy "require-corp";
}
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
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)
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
]
}
]
}
Preview production build locally
npm run preview
Serves dist/ at http://localhost:4173 with the correct headers (injected by Vite).
Tests
Setup (first time)
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 |
# 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 ≥ 0.123 extended edition
Development
npm run site:dev # Hugo dev server at http://localhost:1313 (with drafts)
Build
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:
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.
Project
- Technical Documentation -- Architecture, tech stack, data model, and feature details
- User Documentation -- How each feature works from the user's perspective
- Implementation Plan -- Step-by-step feature plan and progress tracking
- Decisions Log -- All design decisions with rationale
Credits
Icons
The app icon is Mighty Force by Delapouite, sourced from game-icons.net and licensed under CC BY 3.0. Thank you for making such a great icon library freely available!
UI icons are provided by Bootstrap Icons, licensed under MIT.
Third-Party Software
| Package | License |
|---|---|
| Vue 3 | MIT |
| Vue Router | MIT |
| Vite | MIT |
| @vitejs/plugin-vue | MIT |
| vite-plugin-pwa | MIT |
| Workbox (bundled by vite-plugin-pwa) | Apache-2.0 |
| @sqlite.org/sqlite-wasm | Apache-2.0 |
| Bootstrap Icons | MIT |
| Mighty Force by Delapouite (app icon) | CC BY 3.0 |
| Chart.js | MIT |
| vue-chartjs | MIT |
| DOMPurify | Apache-2.0 / MPL-2.0 |
| marked | MIT |
| i18next | MIT |
| i18next-vue | MIT |
| Prettier | MIT |
| Playwright | Apache-2.0 |
| pytest | MIT |
| pytest-playwright | Apache-2.0 |
| 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
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/.