Mahdi
All projects

Fluxget

Every desktop app that needs a UI ends up shipping a browser engine and a bundler. vails inverts that by rendering into the OS webview, but it was fourteen commits old with no tags and no CI, and its own roadmap announced a breaking change to the exact subsystem Fluxget depends on. So the real question was never "how do I download files" — it was how to build on an unstable framework without becoming hostage to it.

VTypeScriptaria2 JSON-RPCWebView2PowerShell
Three mechanisms, not good intentionsIsolation from an unstable framework is only real if something enforces it. Discipline is not an enforcement mechanism, so the boundary is expressed three ways, each one checkable:MechanismWhat it doesWhereThe seamOnly main.v and platform/ may import vails. Everything else — rpc/, engine/, the entire frontend — is plain V with no framework knowledge. Enforced by a script, not by habit.scripts/check-isolation.ps1The contract~34 vails symbols Fluxget depends on, checked in two layers. Compiling platform/contract.v is the check, and contract_test.v proves the behaviour still holds.platform/contract.vThe pinvendor/vails is a submodule and vails.lock records the last revision that passed the gate. The sync script always tries upstream main and rolls back automatically if the gate goes red.scripts/sync-vails.ps1The pin is the important one. Upstream is always tried, so improvements land on their own — but a failing gate reverts the revision rather than leaving the app broken. You keep the upside and drop the risk.grep -r COMPAT lists every place Fluxget reimplements something the framework will ship in a later release wave, with the upstream service named.The domain model came firstCONTEXT.md defines the ubiquitous language before any UI exists, and it is strict about vocabulary borrowed from upstream. A GID is aria2's 16-hex download id: opaque, never parsed. A Status is passed through unchanged, so an aria2 release cannot invent a state the UI has never heard of.Two entries do most of the work:Snapshot — the whole world at one instant, fetched with a single system.multicall per tick. That is why the UI never makes three round trips.Notice — a queued event, delivered in the next snapshot rather than pushed. A handler may not emit from a worker thread, so push is not an option. core/ owns the queue.The phrase that makes this honest: a notice is a transition noticed by diffing two ticks. Nothing pushed it.Scheduling as a pure functionschedule/eval has the signature (rules, conditions, now, state) -> Effect. Pure, no I/O, no clock reads.That is what makes the scheduler testable against a table of times instead of by waiting — a year of schedules can be asserted in milliseconds. Rules define recurring windows, conditions are events that override them, and the effect is a decision: set a preset, pause everything, stop seeding.A Preset is a named Limit (night = 1 MiB/s), and a schedule always chooses a preset by name rather than hard-coding numbers.What is actually runningFluxget finds aria2c, spawns it, speaks its JSON-RPC on loopback, and shows a live queue. The screenshot is not a mockup: a real window, the real dev server, the real bridge.Router, a real child process, and a real 100% HTTP transfer.The capability model is worth noting: a Command name is a capability grant in vails.json, so there is no second list to keep in sync. An ungranted command is refused with forbidden:.StatusPhase 1 complete — the window opens, the bridge round-trips, and a real download runs to completion. Phase 2 owns the design system and the multi-page frontend.

Keep reading