Strength Log is a mobile-first, local-first workout tracker built on Next.js. It's designed to be instantaneously responsive by writing purely to local IndexedDB (Dexie) and queueing asynchronous syncs to a Prisma/SQLite backend whenever a network is available.
Important
π€ ARE YOU AN AI AGENT?
Before making any code changes or tool calls, you MUST read AGENTS.md. It contains strict surgical edit rules, local-first architecture bounds, and the core files you are forbidden from modifying without explicit permission.
Strength Log employs a strict Local-First Sync Architecture. The app remains incredibly fast because the UI never waits on a network request to load or save data.
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β React UI (Next.js App Router + Tailwind) β
β ββ useLiveQuery() reads from Dexie reactively β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Repository layer (src/lib/*-repository.ts) β
β ββ workout-, exercise-, sync-, utils modules β
β ββ All writes go to Dexie β enqueueSync() β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Sync Engine (src/lib/syncEngine.ts) β
β ββ Drains syncQueue β POST /api/workouts β
β ββ Auto-triggers on `online` event β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β API Routes (src/app/api/**/route.ts) β
β ββ Prisma $transaction upserts to SQLite β
β ββ Auto-backup on every workout sync β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SQLite (prisma/dev.db) β
β ββ Final persistent ledger β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Data Rule: All UI components read and write to IndexedDB. Components never call fetch() directly for mutations.
This app supports two primary development paths depending on whether you are adjusting UI components or testing the Progressive Web App (PWA) installation flow.
Use this path for general feature development, UI changes, and testing backend logic.
- First, set up your local environment variables:
Fill in the required variables:
cp .env.example .env
Variable Required Notes DATABASE_URLβ file:./prisma/dev.dbfor local devJWT_SECRETβ openssl rand -hex 32RP_IDβ for Passkeys localhostfor local devRP_ORIGINβ for Passkeys http://localhost:3000for local devSESSION_TTL_DAYSoptional defaults to 7 - Install dependencies:
npm ci
- Start the development server:
This now runs a local DB bootstrap first: Prisma migrations + conditional seeding for a fresh or partially repaired
npm run dev
prisma/dev.db. - Run automated assurance checks:
npm run check
Use this path if you need to test the service worker, offline caching, or natively installing the app to a mobile device.
- Boot the entire stack (Next.js + Persistent SQLite Volume):
docker compose up --build -d
(Tip: To test PWA installation on a local mobile device, navigate to brave://flags or chrome://flags on your phone, and explicitly allow http://<your-local-ip>:3400 under "Insecure origins treated as secure")
To protect the integrity of the off-line syncing mechanisms, explicit boundaries exist within the codebase.
The app runs as a single Docker container (strength-log). HTTPS is provided by Tailscale Funnel running on the host β no Caddy container is needed. PWA installation requires HTTPS, which Tailscale provides via its globally-trusted certificate.
| File | Purpose | Per-deployment edit? |
|---|---|---|
Dockerfile |
Multi-stage build: deps β build β lean runner (~180 MB) | No β universal |
docker-compose.yml |
App container + volumes; binds to 127.0.0.1:3000 |
No β universal |
docker-entrypoint.sh |
Validates JWT_SECRET, runs migrations, seeds on first boot |
No β universal |
next.config.ts |
Next.js config with security headers | No |
.env |
Required secrets β copy from .env.example |
Yes β set before first boot |
db/strength_diary.db |
Live SQLite database (created on first boot, or migrated) | Optional β bring an existing DB |
db/backups/ |
Append-only numbered backups (auto-created) | No |
logs/app.log |
Entrypoint and runtime logging | No |
The container will refuse to start if JWT_SECRET is missing or is the placeholder value.
cp .env.example .env
# Then edit .env and fill in:
# JWT_SECRET β required. Generate with:
openssl rand -hex 32
# RP_ID / RP_ORIGIN β required for Passkey (WebAuthn) auth.
# Set to your Tailscale domain:
# RP_ID=your-machine.ts.net
# RP_ORIGIN=https://your-machine.ts.net# 1. Ensure .env is populated (see above)
# 2. Build and start
docker compose up --build -d
# 3. Verify
docker ps # strength-log should be Up
docker logs strength-log # should show version banner and "Migrations applied"# ========= On the Mac =========
# 1. Build for the target architecture (arm64 for Pi, amd64 for x86 NAS)
docker buildx build --platform linux/arm64 -t strength-log:latest --output type=docker,dest=strength-log.tar .
# 2. Transfer the image + config to the target machine
scp strength-log.tar docker-compose.yml .env user@<TARGET_IP>:~/strength-app/
# ========= On the target machine =========
# 3. Load the pre-built image
docker load -i strength-log.tar
# 4. Edit docker-compose.yml β use the image instead of building
# Change: build: .
# To: image: strength-log:latest
# pull_policy: never
# 5. Start
docker compose up -dUpdating the app: Repeat steps 1β3. The new image replaces the old one. Run
docker compose down && docker compose up -don the target.
The container listens on 127.0.0.1:3000. Tailscale Funnel proxies HTTPS traffic from your Tailscale domain to localhost:3000.
# On the host machine (not inside Docker):
tailscale funnel 3000The app is then reachable at https://<your-machine>.ts.net. Tailscale provides a globally trusted TLS cert β no manual certificate installation needed on client devices.
Tip: Set
RP_ID=<your-machine>.ts.netandRP_ORIGIN=https://<your-machine>.ts.netin.envso WebAuthn Passkeys work correctly on the public domain.
- Copy your latest backup (or live DB) from the old machine:
scp old-machine:~/strength-app/db/strength_diary.db ./db/strength_diary.db - Place it in the
db/folder on the new machine before runningdocker compose up. - Do NOT copy
-journal,-wal, or-shmfiles β these belong to the old machine's running SQLite instance and will cause "Unable to open database" errors.
| Symptom | Cause | Fix |
|---|---|---|
ERROR: JWT_SECRET is not set at container start |
Missing or placeholder secret | Set a real JWT_SECRET in .env and restart |
Error code 14: Unable to open the database file |
Wrong DATABASE_URL or permission issue | Ensure DATABASE_URL=file:/app/prisma/strength_diary.db. Run sudo chown -R 1001:1001 db/ on host. |
| App loads but shows no data | Browser IndexedDB is empty on first visit | Go to Settings β "Pull from Server" to force bootstrap, or hard-refresh the page. |
| Passkey registration fails | RP_ID / RP_ORIGIN mismatch | Must exactly match the domain in the browser address bar. |
| "Failed to clear server data" | API can't write to the database | Check docker logs strength-log for Prisma errors. Usually a permissions issue (see above). |
Stale -journal file from a different DB |
Copied a journal file alongside the DB from another machine | Delete db/strength_diary.db-journal and restart. |
-
π΄ DANGER ZONE (Do not touch without Extreme Care):
src/lib/syncEngine.ts- The heart of the network queue.src/lib/workout-repository.ts,src/lib/exercise-repository.ts,src/lib/sync-repository.ts- The decomposed repository layer. All domain mutations flow through here.src/lib/db.ts- Schema migrations and Dexie config. Any modifications here risk corrupting user data or breaking the sync loop. SeeAGENTS.mdfor "Surgical Edit Rules".
-
π’ SAFE ZONES:
src/components/**- UI views and components.src/app/**- Next.js routes and layouts.
To maintain synchronization stability and code legibility, please adhere to the following when submitting PRs:
- Use Conventional Commits: e.g.,
feat: offline pin auth,fix: zombie row bug in sync - Agent Hand-off: If an AI agent assisted with your contribution, ensure they have updated
AGENTS.mdor/docswith any new context before terminating the session. - Troubleshooting IndexedDB: If you enter a broken local state during development, IndexedDB might be corrupt. Go to
Chrome Dev Tools -> Application -> Storage -> Clear site dataand reload. Do not attempt to forcefully delete migrations unless you know what you are doing.
Before undertaking major refactors, consult the static reference files located in /docs. You can open these natively in any browser:
docs/data_model.html- Visual Prisma + Dexie schema map (all models and relationships).docs/api_schema.html- API route inventory (endpoints, auth requirements, payloads).docs/sync_architecture.html- Sync engine deep dive: offline strategy, queue lifecycle, error handling.docs/meta_processes.html- Deep dive into resolving local-first sync conflicts.docs/folder_structure.html- Comprehensive codebase map.docs/tech_stack.html- Reasons behind tooling choices (Dexie, Serwist, Prisma).AGENTS.md- Operating guide, current project status, and strict rules for AI Agents.
The application is functionally complete covering the full workout loop, with automated E2E tests, robust data integrity tooling, and an integrated global command palette.
-
Unit tests use
Vitestfor puresrc/lib/**logic. -
Fast CI runs
lint,typecheck,test:unit, andbuildon every branch push and on pull requests intomain. -
Playwright E2E (
npm run test:e2e) covers the full workout lifecycle and JSON export/import. Requires a running dev server or auto-starts via PlaywrightwebServerconfig. -
Progressive Web App (PWA): Installs natively. Boots instantly. Offline-first routing via
Serwist. -
Internet-facing via Tailscale Funnel: Exposed publicly with a globally-trusted HTTPS cert β no self-signed CA needed.
-
WebAuthn Passkey Authentication: Face ID / fingerprint login as primary auth. Legacy bcrypt PIN retained as fallback. Brute-force rate limiting (5 tries / 15-min window).
-
Invite-code Registration: Admins generate single-use 8-char codes (48h TTL). New users self-register at
/registerβ no open sign-up. -
Cardio & Non-gym Tracking:
time_onlytracking mode for treadmill, elliptical, yoga, etc. Non-gym workouts are automatically excluded from the gym session cost counter. -
Security Hardening: HTTP security headers (HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy),
JWT_SECRETvalidated at container start. -
Automated DB Backups: Every server sync creates a rolling numbered backup in
prisma/backups/. -
Zero-Latency UI: All CRUD operations happen logically at 0ms latency thanks to Dexie and background syncing.