No description
  • TypeScript 88%
  • CSS 7.5%
  • JavaScript 3.5%
  • HTML 0.8%
  • Dockerfile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Derek McTavish e12a306734 - Remove physics, immediately discard completed tasks
- Update spec for respecting category on task creation
- Add Ctrl+Home and Ctrl+End keyboard shortcuts
- Spec Ctrl+Enter to create tasks in draft rows
- Remove pile physics; complete tasks immediately
- Make new items respect previously selected category
- Implement Ctrl+Home and Ctrl+End list navigation shortcuts
- Make Ctrl+Enter create new tasks same as Enter
2026-10-09 13:32:57 +07:00
.claude/reference - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
.piton - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
.suspense - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
admin - Spec admin area with dashboard links and Better Auth 2026-10-06 01:13:00 +07:00
spec - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
src - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
.dockerignore - Write task tracker specification 2026-10-02 17:35:24 +07:00
.env.example - Spec admin area with dashboard links and Better Auth 2026-10-06 01:13:00 +07:00
.gitignore - Write task tracker specification 2026-10-02 17:35:24 +07:00
convex.json - Write task tracker specification 2026-10-02 17:35:24 +07:00
docker-compose.yml . 2026-10-05 20:32:02 +02:00
Dockerfile - Write task tracker specification 2026-10-02 17:35:24 +07:00
index.html - Write task tracker specification 2026-10-02 17:35:24 +07:00
nginx.conf . 2026-10-05 20:32:02 +02:00
package-lock.json - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
package.json - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
piton.config.pi - Write task tracker specification 2026-10-02 17:35:24 +07:00
README.md - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00
tsconfig.json - Write task tracker specification 2026-10-02 17:35:24 +07:00
vite.config.ts - Remove physics, immediately discard completed tasks 2026-10-09 13:32:57 +07:00

Tasks

A quiet, keyboard-driven task tracker: React on the front, a self-hosted Convex backend in Docker behind it, with sign-in by Better Auth running inside that same backend. Each account sees only its own tasks.

Run it

All commands run from the repository root.

# 1. Configuration
cp .env.example .env

# 2. Start the stack: the backend, the dashboard and the app server on 8080, the only published port
docker compose up -d --build

# 3. Generate an admin key and paste the key line into CONVEX_SELF_HOSTED_ADMIN_KEY in .env
docker compose exec backend ./generate_admin_key.sh

# 4. Set Better Auth's secret and the app's origin on the backend (never committed)
npm install
npx convex env set BETTER_AUTH_SECRET "$(openssl rand -base64 32)"
npx convex env set SITE_URL http://localhost:8080,http://127.0.0.1:8080

# 5. Push the schema and functions (the CLI reaches the backend through the app server)
npx convex deploy -y

Open http://localhost:8080 (or http://127.0.0.1:8080) and create an account with Ctrl+Enter. The app server on 8080 forwards /api/auth/ to the backend's HTTP actions (3211) and every other /api/ path to its API (3210) over an internal Docker network, so the browser only talks to the address you opened, and the session cookie is the page's own.

To open the app from another address, such as http://192.168.1.20:8080 on your network, add that origin to SITE_URL, comma-separated:

npx convex env set SITE_URL http://localhost:8080,http://127.0.0.1:8080,http://192.168.1.20:8080

For development, run npx convex dev (pushes functions on change) and npm run dev (Vite, which forwards /api/ to the app server on 8080, so keep the stack running), and add http://localhost:5173 to SITE_URL.

Data lives in the convex-data Docker volume and survives restarts. Set INSTANCE_SECRET in .env before first start if admin keys must survive recreating the volume.

Access

Address What it is
http://localhost:8080 The app. Also at http://127.0.0.1:8080.
http://dashboard.localhost:8080 The Convex dashboard, served by the app server by host name.
http://127.0.0.1:8080 The backend, as the Convex CLI and the dashboard reach it: the app server forwards /api/ and /version to it.

8080 is the only port the stack opens. The backend and the dashboard sit on an internal Docker network with no ports of their own; only the app server reaches them. Browsers resolve any *.localhost name to this machine, so the dashboard needs no hosts-file entry.

Backend

The backend has no web page of its own; http://127.0.0.1:8080/version answers when it is up. Reach it with the Convex CLI from the repository root, which reads CONVEX_SELF_HOSTED_URL and CONVEX_SELF_HOSTED_ADMIN_KEY from .env:

npx convex data                         # list the app's tables
npx convex data tasks                   # read a table
npx convex data --component betterAuth  # list Better Auth's tables
npx convex env list                     # environment variables
npx convex logs                         # stream function logs

Admin pages

http://localhost:8080/admin/ links to every dashboard: the Convex dashboard, the built-in Better Auth dashboard at /admin/auth/ (accounts and sessions, with Reset link, Sign out everywhere and Delete), the backend's /version and the task list. They use the app's own sign-in, and only accounts whose email is in ADMIN_EMAILS may use them:

npx convex env set ADMIN_EMAILS you@example.com,someone@example.com

With ADMIN_EMAILS unset, nobody is an admin.

Password resets

The stack sends no email. When someone asks for a reset on the sign-in form (Ctrl+Shift+Enter, then their email), the backend writes the link to its logs instead, and whoever runs the tracker passes it on:

npx convex logs | grep "Password reset link"

or open Logs in the dashboard. A line reads Password reset link for someone@example.com: http://localhost:8080/?reset=…; the link uses the first origin in SITE_URL, works once and expires after an hour.

Dashboard

Open http://dashboard.localhost:8080. The deployment URL is the backend through the app server, http://127.0.0.1:8080, and is filled in already. The password it asks for is the admin key, kept in CONVEX_SELF_HOSTED_ADMIN_KEY in .env. Print it with:

grep ^CONVEX_SELF_HOSTED_ADMIN_KEY .env | cut -d= -f2-

If it is lost, generate a new one (and put it back in .env):

docker compose exec backend ./generate_admin_key.sh

In the dashboard you'll find:

  • Data: the app's tasks and timeEntries tables. Better Auth's own tables (user, session, account and the rest) are under the betterAuth component, picked from the component selector.
  • Functions: the queries and mutations in src/convex/tasks.ts and the auth functions.
  • Logs: every function call, with its errors.
  • Settings → Environment Variables: BETTER_AUTH_SECRET and SITE_URL.

Keys

Key Does
Up / Down Previous / next task
Ctrl+Up / Ctrl+Down First / last task
Left (at the start of a title) Pick or create a category
Enter New task below
Ctrl+Space Start or stop tracking
Ctrl+, Account: change password or delete the account
Ctrl+Shift+Space Timer: type a duration (25, 25m, 1h30m, 1:30), Enter to count down; an alarm sounds at zero
Ctrl+Enter Complete the task (it leaves the list for good)
Alt+Up / Alt+Down Move the task
Backspace (empty title) Delete the task (it is hidden, never erased)
Ctrl+Shift+Backspace Delete the task
Escape Discard an empty new task
Ctrl+Shift+L Sign out

On the sign-in form, Up / Down move between fields, Enter signs in and Ctrl+Enter switches to creating an account.

Layout

  • src/convex/ schema and functions (tasks.ts), Better Auth (convex.config.ts, auth.ts, auth.config.ts, http.ts). convex.json points the Convex CLI here.
  • src/app/ React components, hooks and styles
  • At the root: package.json, vite.config.ts, tsconfig.json, index.html, convex.json, docker-compose.yml, Dockerfile, nginx.conf, .env.example and this README