163 lines
11 KiB
Markdown
163 lines
11 KiB
Markdown
# 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, HTTP and media decoding.
|
|
|
|
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.
|
|
- **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.
|
|
- **Attachments:** images (PNG, JPEG, GIF, BMP, WebP) and videos (MP4/MOV, WebM, AVI) on tasks and comments. You can drop files onto the window, use the **Add image or video** button, or paste with Ctrl+V. The first attachment becomes the card's cover. Click a thumbnail to open the viewer; use Left/Right to move between attachments and Esc to close.
|
|
- **Screenshots:** pasting a screenshot attaches it, e.g. after Win+Shift+S. The **Snip screen** button (native) opens the Windows snipping overlay and attaches the result automatically. **Capture screen** (web) grabs a frame of a screen, window or tab you pick.
|
|
- **Video playback** in the viewer has play/pause (click or Space), seeking, time and mute.
|
|
- Natively, Windows Media Foundation decodes video and audio inside the app: H.264/HEVC/AAC and anything else Windows has codecs for. Audio plays through waveOut and drives the clock.
|
|
- In the browser, the browser's own decoder is used.
|
|
- Attaching a video stores its size, duration and a poster frame, so thumbnails need no video decoding.
|
|
- **Comments** on every task:
|
|
- threads with replies,
|
|
- editing that keeps the full edit history ("edited (2)" shows the earlier versions),
|
|
- deletion that leaves a placeholder, so replies still make sense,
|
|
- images, videos and screenshots inside comments.
|
|
- **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 project is a plain-text file that can live in your game's repository and diffs cleanly.
|
|
- Attachments are stored next to it in `<project>.taskforge.files/`.
|
|
- **Web, local:** serve `web/` with any static server (`python -m http.server -d web 8000`). The project lives in the browser, with attachments in IndexedDB. Export/Import move the project (without attachments) 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 and Priority fields, plus 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 and point a domain's A record at its IP. The smallest size is fine for tasks; pick more disk if you'll attach a lot of video.
|
|
2. From Git Bash on your machine:
|
|
```
|
|
deploy/deploy.sh root@<droplet-ip> 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 backup of the database and attachments to `/var/backups/taskforge` (database copies 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@<droplet-ip>`.
|
|
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.
|
|
|
|
The backups live on the same droplet. For real safety, also enable DigitalOcean's droplet backups, or copy `/var/backups/taskforge` off the machine.
|
|
|
|
Useful on the droplet: `journalctl -u taskforge -f` (logs), `taskforge-server --db ... list-users`, `taskforge-server --db ... set-password <user>`.
|
|
|
|
## 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`, `blobs`, `meta`. Each worker thread has its own connection; writes are short `BEGIN IMMEDIATE` transactions.
|
|
- **Attachments** are content-addressed:
|
|
- Files are named by their SHA-256 and stored in `blobs/` next to the database (`--blobs` moves it).
|
|
- Uploads (`POST /api/blob`, up to 600 MB) stream to disk while being hashed, and only images and videos are accepted.
|
|
- Downloads (`GET /api/blob/<hash>`) need a session.
|
|
- **Project sync:**
|
|
- A project is a set of records, one text line per entity (`task 42 ...`, `val 42 7 sel 5`, `comment ...`, `attach ...`), 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:** `GET /api/info`, `POST /api/login`, `/api/logout`, `/api/sync`, `/api/users`, `POST /api/blob`, `GET /api/blob/<hash>`.
|
|
|
|
Current limits:
|
|
- One project per server.
|
|
- Changes arrive by polling (up to ~2 s delay) rather than push.
|
|
- Deleting an attachment removes it from the project but leaves its file in the blob store.
|
|
- Client size limits are 50 MB for images and 500 MB for videos.
|
|
|
|
## 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 (incl. images), file picker, drag & drop, snipping, libcurl HTTP, `--sim` test harness |
|
|
| `src/video_native.jai` | Media Foundation + waveOut video playback and probing (Windows) |
|
|
| `src/platform_web.jai`, `web/taskforge.js` | WASM exports + the JS shim (WebGL2, input, fetch, localStorage/IndexedDB, image/video decoding) |
|
|
| `src/web_gl.jai` | GL declarations for WASM (same names as the GL module) |
|
|
| `src/render.jai`, `src/font.jai` | Batched renderer (shapes, text, images) and text |
|
|
| `src/ui.jai` | Immediate-mode widgets: buttons, text editing with selection, dropdowns, scrolling |
|
|
| `src/model.jai` | Fields, tasks, views, users, attachments, comments, epic rule evaluation |
|
|
| `src/storage.jai` | Record format, local file, starter projects |
|
|
| `src/sync.jai` | Sign-in and project sync with the server |
|
|
| `src/media.jai` | Blob store, image cache, attaching files, video player state |
|
|
| `src/attachments_ui.jai`, `src/comments.jai` | Thumbnails, viewer, card covers; comment threads |
|
|
| `src/app.jai`, `src/account.jai` | Screens |
|
|
| `src/sha256.jai` | SHA-256 / HMAC / PBKDF2 (shared with the server) |
|
|
| `server/server.jai` | HTTP server, API, users, sessions, blob storage |
|
|
| `server/sqlite.jai`, `server/crypto.jai` | SQLite binding; OS randomness |
|
|
| `deploy/` | Droplet setup, deploy script, systemd unit, Caddyfile |
|
|
|
|
## Keyboard
|
|
|
|
- `Esc`: close the viewer, a popup or the task panel
|
|
- `Ctrl+S`: save / sync now
|
|
- `Ctrl+V` with an image on the clipboard (task open): attach it
|
|
- In the viewer: Left/Right for previous/next, `Space` for play/pause
|
|
- In the comment box: `Ctrl+Enter` posts
|
|
- 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;drop C:/art/concept.png;type Hello;shot out.bmp;quit"
|
|
```
|
|
|
|
Commands: `move X Y`, `down`, `up`, `click X Y`, `type TEXT`, `key CODE [MODS]`, `wheel LINES`, `drop PATH`, `wait N`, `shot FILE.bmp`, `quit`.
|