Phoenix (LiveView) Web UI for the elixir_torrent
BitTorrent engine.
Build a stable, correct, long-running BitTorrent client UI on top of an Elixir/OTP engine. The engine lives in a separate project and this repo is the UI + desktop product shell.
- Torrent dashboard — list with progress, speeds, peers, ETA, expandable file rows
- Lifetime statistics — persisted downloaded/uploaded totals and current aggregate speeds, with an atomic Reset Statistics action in Settings
- Add & remove torrents —
.torrentupload, clipboard magnet ingestion, persisted pending magnets, and optional downloaded-data removal - Download settings — persistent default download directory with a native macOS picker
- Media actions — range-streamed video playback plus lazy previews for completed images; image clicks open in the operating system's default application
- International UI — all user-facing strings are maintained across 64 Gettext locales
- macOS desktop app —
mix mac.dmgbundles a Swift launcher + release intoElixirTorrent Web.app(browser UI on loopback), registers.torrent/magnet:handlers, rotates logs, and shows active torrents in the Dock menu
- Engine:
elixir_torrent(Hex docs · GitHub) — session persistence, graceful shutdown (stop_and_serialize/1), peer disconnect - UI server: Phoenix + LiveView (local HTTP + WebSocket)
- Desktop (macOS): Swift launcher starts the release, opens the browser, graceful shutdown on Quit
On boot, ElixirTorrentWebUI.Application calls Application.ensure_all_started(:elixir_torrent).
The Phoenix endpoint listens on loopback only by default (127.0.0.1).
UI code talks to the engine through ElixirTorrentWebUI.Engine — not ElixirTorrent.* directly.
- Elixir: 1.20.x
- Phoenix: 1.8.x
- Phoenix LiveView: 1.2.x
- Bandit: HTTP server
- Tailwind + ESBuild: assets pipeline
From the repo root:
mix setup
mix phx.serverOpen http://127.0.0.1:4000.
mix phx.server and the macOS .app share catalog, UI state, and .torrent files
via ~/Library/Application Support/ElixirTorrentWebUI/ (see ElixirTorrentWebUI.DataDir).
In dev, the process working directory stays in the project root so Phoenix code
reloading works; engine session files and downloads land under the repo unless you
symlink .elixir_torrent to Application Support. The desktop app uses that
folder as its cwd, so torrent data and the catalog stay in sync there.
Override the data root with ELIXIR_TORRENT_DATA_DIR if needed.
mix setup also runs mix mac.icon to generate app icons. That step needs
Python 3 and Pillow (pip install Pillow).
Build a local .app bundle and .dmg installer:
mix mac.dmgOutput lands in dist/ (e.g. ElixirTorrent Web.app,
ElixirTorrent Web-0.1.0-macos-arm64.dmg on Apple Silicon, or …-macos-x64.dmg
on Intel).
Icon generation (mix mac.icon, also part of mix setup):
- Liquid Glass (
Assets.carfor macOS 26+): requires Xcode 26+ (xcrun actool). Without it, the build still produces a classic.icnsfallback icon. - Python 3 + Pillow for
priv/scripts/macos/generate-app-icon.py.
The committed dependency remains the published Hex package. While both projects evolve together, validate the WebUI against a local Engine checkout by setting:
export ELIXIR_TORRENT_PATH=../ElixirTorrent
mix deps.getThe local path and Engine revision are development inputs, not committed pins. Do not commit lock-file changes caused only by switching dependency sources.
The deterministic ExUnit/ConnTest/LiveViewTest suite has a 60-second ceiling. ExCoveralls currently enforces the first 55% project gate (57.2% at introduction); the roadmap raises this to 70% and then above 80%.
export ELIXIR_TORRENT_PATH=../ElixirTorrent
mix format --check-formatted
mix quality
perl -e 'alarm shift; exec @ARGV' 60 mix coverallsGitHub Actions currently validates only Elixir: locked dependencies, Hex audit, formatting, warnings-as-errors compilation in dev/test, Credo, Dialyzer, and coverage. Native packaging remains a separate future workflow.
- The endpoint is bound to loopback (
127.0.0.1) in dev config. - Media routes accept only torrent id/file index, resolve server-side Engine state, and reject traversal, absolute, drive, UNC, and NUL paths.
- Completed image previews are allow-listed, private-cache responses with
X-Content-Type-Options: nosniff; paths are never accepted from HTTP clients. - Before exposing anything beyond localhost, add auth (token/cookie) and review endpoints that control downloads or read files.