# 01 — Architecture ## 1. Decision summary | Concern | Decision | Why this and not the alternative | |---|---|---| | Engine language | **C++23** (`libveloxcore`) | You asked for speed and it is the right call here: segment scheduling, a 64 KiB–8 MiB write path, and 32 concurrent sockets are exactly where a GC and a per-chunk allocation tax show up. Rust would be an equally good engine choice, but Qt's C++ API means C++ removes an entire FFI boundary from the GUI lane. | | HTTP stack | **libcurl** (multi interface, `curl_multi_poll`) | Writing an HTTP/1.1+2 client on raw epoll costs weeks and buys nothing: curl already does connection reuse, HTTP/2 multiplexing, TLS, proxies, SOCKS5, Digest/NTLM auth, redirect and cookie semantics. One `curl_multi` handle per worker thread, N easy handles = N segments. | | GUI toolkit | **Qt 6 Widgets** (not QML, not GTK4) | IDM's UI is a dense data grid, a toolbar of big icons, a tree, and ~12 tabbed dialogs. `QTreeView` + a custom `QAbstractItemModel` renders 100k rows without breaking a sweat; QML would need that grid hand-built. GTK4/libadwaita fights you the moment you want a non-GNOME look, and gtkmm's tree/column story is worse. Qt also gives you `QSystemTrayIcon`, drag-and-drop, a global clipboard API, and `QSS` theming to get the IDM look. LGPLv3 dynamic linking is fine for an open-source app. | | Process model | **Daemon + thin clients** | Downloads must survive closing the window, and the extension must work when no window is open. One owner of state also kills every "GUI and extension disagree" bug class. | | Wire protocol | **JSON-RPC 2.0** over Unix domain socket (+ loopback WebSocket for the extension fallback) | One protocol for GUI, CLI, and extension = one contract to keep in sync, and it is trivially mockable so three lanes can build in parallel. D-Bus is more idiomatic on Linux but is painful to speak from a WebExtension and adds a second schema. A D-Bus *shim* can be added in M5 for desktop integration only. | | Persistence | **SQLite** (WAL) | Task list, segment bitmaps, categories, rules, queues, history. Crash-safe, zero admin, one file. | | Extension | **MV3 WebExtension, TypeScript** | Firefox MV3 still allows blocking `webRequest`, which is what makes true "intercept before the browser downloads it" possible — the thing Chrome MV3 took away. | | Build | **CMake ≥ 3.28 + Ninja + presets** | Presets mean every agent and CI run the identical configure line. | ## 2. Processes ### `veloxd` — the daemon Owns everything: task list, scheduler, queues, disk, sockets, settings. - Single instance, enforced by an abstract-namespace lock socket. - Started by systemd **user** unit `velox.service`, socket-activated by `velox.socket`. Also auto-started by the GUI or nmhost if not running (`systemctl --user start velox`). - Listens on: - `$XDG_RUNTIME_DIR/velox/velox.sock` (mode 0600) — GUI, CLI, nmhost. Peer credentials checked via `SO_PEERCRED`; same-UID only. No token needed, the socket permission *is* the authorization. - `127.0.0.1:` — loopback WebSocket for the extension fallback transport. **Token-authenticated + pairing prompt.** See `docs/05-extension-spec.md` §4. - Threads: 1 RPC/event loop, 1 curl-multi transfer thread per ~8 active segments (capped), 1 disk writer thread per active task, 1 timer thread for the scheduler. Never block the RPC loop on disk or DNS. ### `velox-gui` — Qt 6 client Stateless view. On start: connect → `session.hello` → `download.list` → subscribe to `event.*`. On daemon restart: exponential-backoff reconnect with a banner, never lose the window. Everything it displays comes from the daemon; it holds no download state of its own. ### `velox` — CLI Same RPC, scriptable: `velox add --dir ~/ISOs --segments 16`, `velox ls`, `velox pause `. Falls out nearly free once the generated client exists, and it is the fastest way to test the daemon before the GUI is ready. ### `velox-nmhost` — native-messaging bridge Deliberately trivial and boring: reads Firefox's 4-byte-length-prefixed JSON from stdin, writes it to the Unix socket, pumps replies back. **No business logic — ever.** Under 300 lines. It exists only because Firefox's native messaging speaks stdio and the daemon speaks sockets. If a feature needs logic, it belongs in the daemon. ## 3. The layering rule (enforced in review) ``` libveloxcore → knows nothing about RPC, JSON, SQL, or Qt. Input: a DownloadSpec. Output: bytes on disk + callbacks. veloxd → knows RPC, SQL, scheduling. Depends on core. No Qt. velox-gui → knows Qt and the generated client. Zero engine code. extension → knows the browser and the generated TS client. Zero download logic. ``` If the GUI ever needs to know what a "segment steal" is, the layering has been violated. The daemon's job is to project the engine's state into the contract's `TaskDetail` type, and that is the only shape the GUI ever sees. ## 4. How three lanes build at the same time without breaking each other This is the part that makes parallel agents work, so it is a hard process, not a habit: 1. **`contracts/` is the single source of truth** and is owned by exactly one lane (PROTO). JSON Schema for every type, method, and event, plus an OpenRPC document. 2. **Codegen, not hand-written types.** `contracts/codegen/gen_cpp.py` emits `libveloxproto` (structs + (de)serialization); `gen_ts.py` emits `@velox/protocol`. Generated files are committed so nobody is blocked on running the generator. Hand-editing a generated file is a merge-blocking offence. 3. **Golden fixtures.** Every method has request/response JSON examples in `contracts/fixtures/`. `tests/conformance/` replays them against *both* the C++ server and the TS client. Green fixtures = the two lanes are compatible, without them ever having run against each other. 4. **`tools/mockd`** — a TS mock daemon that serves the fixtures and fakes progress events. The GUI and extension lanes develop against it from day one and never wait for `veloxd`. 5. **Protocol changes are a PR to `contracts/` only**, with a `VERSION` bump and updated fixtures. Adding an optional field = minor. Removing/renaming/retyping = major, and `session.hello` refuses a mismatched major with a clear error the GUI shows as "Update Velox". ## 5. Data locations | What | Path | |---|---| | Config | `~/.config/velox/settings.json` | | Database | `~/.local/share/velox/velox.db` | | Logs | `~/.local/state/velox/velox.log` (rotated, 5×2 MiB) | | Runtime sockets | `$XDG_RUNTIME_DIR/velox/` | | Temp/partial data | configurable; default `~/.local/share/velox/temp/` | | Default download root | `~/Downloads/` with category subfolders | Partial files: the target file is created **sparse and preallocated at final size** in the destination directory as `.veloxpart`, with `.veloxpart.meta` beside it. On completion the part file is renamed in place — no copy, no second full-size write, and a half-finished download is never mistaken for a real file by other apps.