# TaskForge Task management for game projects, written in Jai. The same client code runs as a native Windows app (OpenGL 3.3) and in the browser (WebGL2 via WASM). All UI and rendering is custom immediate-mode code; the only web code is a small JS shim that forwards GL calls, input and HTTP. It runs in two modes: - **Local:** a single user, with the project in a text file (native) or the browser's storage (web). - **Server:** a team. `taskforge-server` (also Jai) stores users and the shared project in SQLite and serves the web client. People sign in from the browser or the native app, and changes sync between everyone. ## Features - **Custom fields** (Fields page): Text, Number, Checkbox, Select (one option), Tags (several options) and User (assign people). Options have colours, and a field can apply to tasks, epics or both. Add, rename, reorder and delete fields as you like. - **Views** for each context: - A **Board** turns any Select or Checkbox field into kanban columns, with drag & drop. - A **Checklist** turns each task into a checkbox. "Done" means a field has a chosen value, and items can be grouped by epic. - Views can filter by epic, by an option (e.g. `Discipline: Art`) or by a user (e.g. `Assignee: Me`), and choose which fields appear on cards. - **Epic rules** (Epic rules page): set a field on an epic from the state of its tasks, e.g. *Set **Art** to **Done** when **all** of its **Art** tasks have **Status** is **Done***. For each epic and field, the first matching rule from the top wins. If none match, the field keeps its value. - **Users** (server mode): sign-in with sessions, admins and deactivation. Assign people with User fields. Admins add users and reset passwords on the Users page; everyone can change their own password. - **Text editing:** selection with the mouse (drag, double-click selects a word, triple-click selects all) and the keyboard (Shift+arrows/Home/End, Ctrl+A). Copy, cut and paste work on the selection, and typing replaces it. ## Building Requires the Jai compiler (tested with beta 0.2.030). ``` jai build.jai # native client: bin/TaskForge.exe (+ fonts.bin, libcurl.dll) jai build.jai - release # native client, optimized jai build.jai - web # web client: web/taskforge.wasm (add "release" for optimized) jai build.jai - server # server for this OS: server/bin/taskforge-server(.exe) jai build.jai - server-linux # Linux x64 server object file, linked on the droplet (see below) ``` Each client build bakes `data/Karla-Regular.ttf` (SIL Open Font License) into `fonts.bin` at compile time, so the app needs no font rasterizer at runtime. ## Running locally - **Native, local file:** `bin/TaskForge.exe [path/to/project.taskforge]`. The plain-text project file can live in your game's repository and diffs cleanly. - **Web, local:** serve `web/` with any static server (`python -m http.server -d web 8000`). The project lives in the browser; Export/Import move it to or from a file. - **Server on your machine:** ``` cd server/bin taskforge-server add-user daniel "Daniel" --admin # asks for a password taskforge-server --web ../../web --port 8080 # http://localhost:8080 ``` `--demo` on the first start seeds the sample game project; without it you get a clean starter project (Status, Assignee, Discipline, Priority; Board and My tasks views). On Windows the server needs `sqlite3.dll` next to it (the build copies the one in `server/dev/`). - **Native app against a server:** `bin/TaskForge.exe --server https://tasks.example.com`. The session is remembered in `%APPDATA%\TaskForge\session.txt`. ## Deploying to a DigitalOcean droplet 1. Create an Ubuntu 24.04 droplet (the smallest size is plenty) and point a domain's A record at its IP. 2. From Git Bash on your machine: ``` deploy/deploy.sh root@ tasks.example.com ``` The first run sets up the droplet (`deploy/setup-droplet.sh`): - installs SQLite, a C toolchain and Caddy, - creates a `taskforge` system user and a hardened systemd service, - configures HTTPS for your domain through Caddy (automatic Let's Encrypt), - enables the firewall, - adds a daily SQLite backup to `/var/backups/taskforge` (kept for 14 days). Every run builds the web client and the Linux server, uploads them, links the server on the droplet and restarts it. For later updates, run `deploy/deploy.sh root@`. 3. Create the first admin on the droplet: ``` sudo -u taskforge /opt/taskforge/bin/taskforge-server --db /var/lib/taskforge/taskforge.db add-user daniel "Daniel" --admin ``` Then open `https://tasks.example.com`, sign in, and add the rest of the team on the Users page. Why link on the droplet: the server is cross-compiled to a Linux object file on Windows, but linking a Linux executable needs Linux's system libraries, which the droplet has. Alternatively, copy your Jai distribution to the droplet and run `jai-linux build.jai - server` there. Useful on the droplet: `journalctl -u taskforge -f` (logs), `taskforge-server --db ... list-users`, `taskforge-server --db ... set-password `. ## How the server works - **Database:** SQLite in WAL mode, loaded at runtime (`libsqlite3.so.0` / `sqlite3.dll`). Tables: `records` (the project), `users`, `sessions`, `login_failures`, `meta`. Each worker thread has its own connection; writes are short `BEGIN IMMEDIATE` transactions. - **Project sync:** a project is a set of records, one text line per entity (`task 42 ...`, `val 42 7 sel 5`, ...), the same format as the local file. Every write bumps a global revision. Clients send the records they changed (found by diffing against what the server last confirmed) and receive everything changed since their last revision. The last writer wins per record, so two people editing different fields of the same task don't overwrite each other. Clients poll every 2 seconds, and immediately after local edits. New ids come in blocks of 1000 per client, so they never collide. - **Security:** - Passwords are hashed with PBKDF2-HMAC-SHA256 (310,000 iterations, random salt). - Session tokens are 256-bit random values, stored only as SHA-256 hashes; they expire after 30 days of inactivity. - After 10 failed logins in 10 minutes, the username is locked for a while. - Changing a password signs out that user's other sessions. - Static file serving only accepts plain file names from the web directory. - HTTPS is handled by Caddy; the server listens on 127.0.0.1 only. - **HTTP API** (plain text bodies): `GET /api/info`, `POST /api/login`, `/api/logout`, `/api/sync`, `/api/users`. Current limits: one project per server, and changes arrive by polling (up to ~2 s delay) rather than push. ## Code layout | File | Purpose | |---|---| | `build.jai` | Build metaprogram for all targets + font atlas baker | | `src/main.jai` | Client entry; picks the platform layer | | `src/platform_native.jai` | Win32 window, WGL, input, files, clipboard, libcurl HTTP, `--sim` test harness | | `src/platform_web.jai`, `web/taskforge.js` | WASM exports + the JS shim (WebGL2, input, fetch, localStorage) | | `src/web_gl.jai` | GL declarations for WASM (same names as the GL module) | | `src/render.jai`, `src/font.jai` | Batched renderer and text | | `src/ui.jai` | Immediate-mode widgets: buttons, text editing with selection, dropdowns, scrolling | | `src/model.jai` | Fields, tasks, views, users, epic rule evaluation | | `src/storage.jai` | Record format, local file, starter projects | | `src/sync.jai` | Sign-in and project sync with the server | | `src/app.jai`, `src/account.jai` | Screens | | `server/server.jai` | HTTP server, API, users and sessions | | `server/sqlite.jai`, `server/crypto.jai` | SQLite binding; SHA-256 / HMAC / PBKDF2 / OS randomness | | `deploy/` | Droplet setup, deploy script, systemd unit, Caddyfile | ## Keyboard - `Esc`: close a popup or the task panel - `Ctrl+S`: save / sync now - In text fields: - `Shift`+arrows, `Home`, `End`: select - `Ctrl`+arrows: move by word - `Ctrl+A`: select all - `Ctrl+C` / `X` / `V`: copy, cut, paste - `Ctrl+Backspace`: delete a word - `Enter` submits a single-line field; `Ctrl+Enter` leaves a multi-line one ## Testing The native client can be scripted, for screenshots and regression checks: ``` bin/TaskForge.exe test.taskforge --sim "wait 2;click 710 230;wait 2;type Hello;shot out.bmp;quit" ``` Commands: `move X Y`, `down`, `up`, `click X Y`, `type TEXT`, `key CODE [MODS]`, `wheel LINES`, `wait N`, `shot FILE.bmp`, `quit`.