Personal health tracker with two UIs over a shared PostgREST API:
- TUI (Textual) — terminal, keyboard-driven, opens instantly.
- Web (Streamlit) — browser, GitHub-style heatmaps + inline editing.
Two features today: a daily habit tracker (click any day on the heatmap to toggle it) and a workout tracker (volume heatmap, log sets with progressive-overload guidance, edit/delete inline).
Neither UI talks to Postgres directly. Both log in through a shared, app-agnostic auth service to get a JWT, then read/write through PostgREST over HTTP:
TUI / Web ──POST /token {schema,username,password}──▶ auth ──▶ JWT (role=load_log_user, user_id)
│ │
└────────── api_client.py ──Authorization: Bearer <jwt>──▶ PostgREST ──▶ Postgres
(requests; returns dict/list[dict]/DataFrame) (apps DB · load_log schema)
- Data lives in the
load_logschema of the sharedappsdatabase. The app's data is reached only via PostgREST; Postgres enforces access through theload_log_userrole. - PostgREST and the auth service are shared server infrastructure (added once,
serve every app). The auth service lives in the sibling
postgrest-authrepo. - Direct Postgres connections remain only in ops tooling: Alembic migrations and
the
scripts/helpers.
For full design + schema details see backend/docs/,
CLAUDE.md, and the server runbook in deploy/.
- Python 3.13 (pinned in
backend/.python-version) - uv —
brew install uv(macOS) orcurl -LsSf https://astral.sh/uv/install.sh | sh(Linux) - A login user in
load_log.users(create one withscripts/create_user.py, below)
git clone <load-log remote> ~/GitHub/load-log
cd ~/GitHub/load-log
cp .env.example .env
$EDITOR .env # see keys below
cd backend && uv sync.env keys:
# Clients (web + TUI) — default to the tinkernet deployment; override to point
# at a local dev stack.
POSTGREST_URL=https://pgrest.tinkernet.me
AUTH_URL=https://auth.tinkernet.me
# The TUI is non-interactive: it logs in with these at startup.
LOAD_LOG_USERNAME=<your-username>
LOAD_LOG_PASSWORD=<your-password>
# Direct-DB tooling only (Alembic + scripts/). Not used by the app at runtime.
POSTGRES_URL=<host or LAN IP of the shared Postgres>
POSTGRES_PORT=5432
POSTGRES_USER=<user>
POSTGRES_PASSWORD=<password>
POSTGRES_DB=appsCreate your login user (writes load_log.users with a bcrypt hash):
cd backend && uv run python scripts/create_user.py --username you --password 'your-password'From backend/:
# Web (Streamlit) — http://localhost:8501 — shows a login form
PYTHONPATH=src uv run streamlit run src/web/app.py
# TUI — logs in using LOAD_LOG_USERNAME / LOAD_LOG_PASSWORD from .env
PYTHONPATH=src uv run python -m tui.appBoth default to the tinkernet PostgREST/auth; set POSTGREST_URL / AUTH_URL in
.env to point them at a local dev stack instead.
Anywhere:
h/w/q— habits screen / workouts screen / quit
Habits list:
↑/↓— move highlight between habitsenter— open the heatmap detail view for the highlighted habitspace— toggle today on the highlighted habitdelete— delete the highlighted habita— focus the "new habit" input
Habit detail and workouts heatmap:
- click any cell — select that date
t/T— toggle the selected day / jump back to today
Workouts log-a-set form:
enterin reps — jump to weight (or submit if weight is already filled)enterin weight — submit the set- weight accepts decimals (e.g.
47.5) Edit/Deletebutton on each logged set — inline edit or remove
The Footer at the bottom of every screen is the source of truth for the keyboard shortcuts available right now — and each Footer entry is clickable.
Two options:
-
docker-compose.yml(repo root) — just the Streamlit container, pointed at the shared PostgREST/auth (defaults to tinkernet). Bring it up withdocker-compose up -d --build→ web onhttp://localhost:8505. -
docker-compose.dev.yml— a fully self-contained stack (its own throwaway Postgres + PostgREST + the shared auth service + Streamlit), so you can develop without touching the server:docker compose -f docker-compose.dev.yml up --build # App http://localhost:8505 · PostgREST http://localhost:3000 · Auth http://localhost:8000First boot runs
alembic upgrade headto create the schema + tables. If PostgREST 404s a table right after,docker compose -f docker-compose.dev.yml restart postgrestto refresh its schema cache.
On macOS, Docker runs through colima — start
it once per session: colima start (or colima start --cpu 4 --memory 6).
Schema changes go through Alembic in backend/alembic/,
applied automatically on container startup by docker-entrypoint.sh. Baseline
0001 is idempotent (CREATE ... IF NOT EXISTS — a no-op on the populated prod
DB, builds a fresh dev DB); 0002 adds the users table. Migrations are
non-destructive by default. Run by hand if needed:
cd backend && uv run alembic upgrade headTake a read-only CSV/Parquet backup before any risky change:
cd backend && uv run --with pyarrow python scripts/backup_to_files.py # writes data/backup_<ts>/cd backend
uv run flake8 src/
uv run black src/ && uv run isort src/
uv run mypy src/The shared postgrest + auth services and the load-log-streamlit service live
in ~/GitHub/Docker/docker_compose_projects.yaml, reverse-proxied by SWAG at
pgrest.tinkernet.me, auth.tinkernet.me, and loadlog.tinkernet.me. The
ordered server runbook (roles, schema move, rebuild, verification) is in
deploy/; deploy commands and the systemd unit are in
~/GitHub/Docker/README.md.