docs: add CLAUDE.md with codebase guide for Claude Code

Generated via /init. Covers workspace layout, the TorrentManager +
per-torrent scope thread + event aggregator architecture, build/test
commands, the custom-protocol release-build requirement, and XDG paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 09:30:43 +03:00
parent 150e6c7b4d
commit 266d91aa61

79
CLAUDE.md Normal file
View File

@@ -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<Command>` in, and `crossbeam_channel::Sender<AggregatedEvent>` 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 <test_name>
# 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-<version>.tar.gz` (from `git archive`) + `monsoon-<version>-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.