diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..970cd60 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Workspace layout + +Cargo workspace (Rust 2024 edition, resolver 3) with three crates, two frontends, and an RPM packaging pipeline: + +- `monsoon-core/` — shared library: `config`, `manager` (TorrentManager + per-torrent scope threads), `registry` (on-disk torrent list), `magnet`, `types`. Both host crates depend on this; almost all torrent logic lives here. +- `src-tauri/` — Tauri desktop binary (crate name `monsoon`). Thin wrapper: `lib.rs` boots Tauri and the event aggregator, `commands.rs` exposes `#[tauri::command]` shims into `monsoon-core::manager::TorrentManager`. +- `monsoon-server/` — headless `monsoon-server` axum binary. REST API (`api.rs`), WebSocket event stream (`ws.rs`). Reuses the same `TorrentManager` wrapped in a `tokio::sync::Mutex` via `AppState`. +- `src/` + `package.json` (root) — Svelte 5 + Vite frontend for the desktop app. Built to `dist/`, embedded into the Tauri binary when the `custom-protocol` feature is enabled. +- `monsoon-web/` — separate Svelte 5 + Vite frontend for the headless server. Built to `monsoon-web/dist/`, served by `monsoon-server` via `tower-http::ServeDir` (found via `MONSOON_WEB_DIR`, or `../monsoon-web/dist` relative to the binary, or `monsoon-web/dist` relative to cwd). + +Version is single-sourced from `[workspace.package]` in `Cargo.toml` and stamped by CI into `src-tauri/tauri.conf.json`, both `package.json` files, and `monsoon.spec`. + +## Architecture: the torrent manager + +`monsoon-core::manager::TorrentManager` is the single owner of all active torrents. The host (Tauri app or axum server) holds it inside a `Mutex` and is responsible for running an **event aggregator thread**: + +1. Each torrent runs in its own *scope thread* spawned by `add_torrent` and managed via `run_torrent_scope` (from `vortex-bittorrent`). Communication is via `SyncSender` in, and `crossbeam_channel::Sender` out. +2. All scope threads share one `crossbeam_channel` receiver (`manager.event_rx`). The host clones it and spawns a thread that loops on `recv()`. +3. For every event the aggregator must call `mgr.apply_event(&event)` to update the in-memory snapshot. Then it emits to the UI: the Tauri app uses `app_handle.emit(...)`, the server broadcasts JSON over `tokio::sync::broadcast` to WebSocket clients and optionally fires `webhook_url`. +4. Persistence lives in `monsoon-core::registry::Registry` (a JSON list of `TorrentEntry { info_hash, name, source: TorrentFile|MagnetLink, paused }`). `restore_torrents()` re-adds entries on startup. + +Queue management (`max_concurrent_downloads`, `min_peers_before_queue`, `STALL_TICKS_THRESHOLD`, `STALL_SPEED_THRESHOLD`) is enforced inside `TorrentManager`; stalled torrents rotate to the back of the queue. + +When making changes that touch both hosts, the change usually belongs in `monsoon-core`. The Tauri commands and axum handlers should stay thin — they translate transport (Tauri IPC vs. HTTP/WS) into manager calls. + +## Common commands + +```bash +# Rust workspace (these are the CI gates — run them before claiming "done") +cargo fmt --check --all +cargo clippy --workspace -- -D warnings +cargo build --workspace +cargo test --workspace + +# Run a single test +cargo test -p monsoon-core + +# Desktop app: build frontend (root) then the binary. custom-protocol embeds dist/. +pnpm install && pnpm build +cargo build --release -p monsoon --features custom-protocol + +# Desktop dev with hot reload +cargo tauri dev + +# Headless server: build its own frontend, then the binary +cd monsoon-web && pnpm install && pnpm build && cd .. +cargo build --release -p monsoon-server +MONSOON_WEB_DIR=$PWD/monsoon-web/dist ./target/release/monsoon-server + +# Validate desktop/AppStream metadata (CI runs these) +desktop-file-validate data/cafe.lair.monsoon.desktop +appstreamcli validate --no-net data/cafe.lair.monsoon.metainfo.xml +``` + +There is no JS/TS test or lint script — frontend validation is just `pnpm build` (typecheck via svelte/vite). + +## Build features and packaging + +- `custom-protocol` (in `src-tauri/Cargo.toml`) — required for **release builds** of the desktop app; without it Tauri expects a running Vite dev server. Always pass `--features custom-protocol` when building `-p monsoon` outside `cargo tauri dev`. +- `release` profile is `lto = "fat"`, `codegen-units = 1`, `strip = "symbols"` — release builds are slow on purpose. +- The `vortex-bittorrent` dependency is a pinned git rev (see `[workspace.dependencies]` in `Cargo.toml`). The RPM build vendors all deps including this git source; if you bump the rev, the vendor tarball regenerates on next CI run. +- `dist.sh` produces `monsoon-.tar.gz` (from `git archive`) + `monsoon--vendor.tar.gz` (from `cargo vendor`). `.copr/Makefile` calls this then `rpmbuild -bs monsoon.spec`. CI in `.gitea/workflows/ci.yml` runs the same flow on `v*` tags and publishes to the `grenade/monsoon` COPR project. +- The CI tag workflow stamps the new version into every manifest and then commits the bump back to `main` — do not also bump versions manually when cutting a release; just tag. + +## XDG paths (resolved by `monsoon-core::config::resolve_paths`) + +| Purpose | Default | +|---|---| +| Config | `~/.config/monsoon/config.toml` | +| Data + registry | `~/.local/share/monsoon/` (torrents.json lives here) | +| Downloads | `~/.local/share/monsoon/downloads/` | +| Logs | `~/.local/state/monsoon/monsoon.log` | +| DHT bootstrap cache | `~/.cache/monsoon/dht_bootstrap_nodes` | + +Both binaries call `config::load_or_create(None)` then `config::resolve_paths(&cfg)` at startup and create the directories before initializing the manager — keep that order if adding new path-dependent state.