Files
vdm/docs/01-architecture.md
T
samiandClaude Opus 5 8bb683b09d scaffold: project structure, wire contract, roadmap and agent briefs
Lays out Velox Download Manager (IDM-class download manager for Ubuntu
26.04) as a monorepo ready for parallel lane development. No implementation
code by design.

- docs/: architecture, roadmap M0-M7, IDM-parity GUI spec, engine design,
  Firefox extension spec, risks/spikes, packaging
- contracts/: wire-contract skeleton (JSON Schema + fixture templates) —
  the single synchronization point between lanes
- docs/agents/: one brief per lane (PROTO, CORE, DAEMON, GUI, EXT, PKG/QA)
  with owned directories, build order and definition of done
- CLAUDE.md: rules of engagement — lane ownership, layering, non-negotiables
- CMake scaffolding with dev/tsan/release/ci presets

Two environment findings shape the design: Firefox here is the Mozilla snap
(native-messaging risk, so the extension carries a loopback-WebSocket
fallback), and Wayland forbids passive clipboard monitoring (so clipboard
capture is explicit-action-first).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-09 18:21:11 +04:00

7.0 KiB
Raw Blame History

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 KiB8 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:<ephemeral, recorded in $XDG_RUNTIME_DIR/velox/ws.port> — 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.hellodownload.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 <url> --dir ~/ISOs --segments 16, velox ls, velox pause <id>. 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 <name>.veloxpart, with <name>.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.