- TypeScript 88%
- CSS 7.5%
- JavaScript 3.5%
- HTML 0.8%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- 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 |
||
| .claude/reference | ||
| .piton | ||
| .suspense | ||
| admin | ||
| spec | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| convex.json | ||
| docker-compose.yml | ||
| Dockerfile | ||
| index.html | ||
| nginx.conf | ||
| package-lock.json | ||
| package.json | ||
| piton.config.pi | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
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
tasksandtimeEntriestables. Better Auth's own tables (user,session,accountand the rest) are under thebetterAuthcomponent, picked from the component selector. - Functions: the queries and mutations in
src/convex/tasks.tsand the auth functions. - Logs: every function call, with its errors.
- Settings → Environment Variables:
BETTER_AUTH_SECRETandSITE_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.jsonpoints 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.exampleand this README