# CLAUDE.md — Phase 3: qui Integration and Torrent Visibility

## Scope of This File

This file instructs you (Claude Code) to implement **Phase 3 only** of the Seedbox Command Center.

**Do not implement phases 4 through 15.** Stay in scope.

## Authoritative Specification

`PROJECT.md` §7.1 is authoritative for qBittorrent and qui integration. Also read:
- `PROGRESS.md` and `DECISIONS.md` for state and binding decisions.
- The Phase 1 `internal/integrations/trackers` package — Phase 3's adapter shape mirrors it.

## Phase 3 Mission

Read-only torrent visibility across one (or more) configured torrent clients. The operator sees their torrent list and per-torrent detail in the Command Center, with live updates pushed from the backend so the view stays current without polling.

The Command Center is **not** trying to be a better qBittorrent UI — that's what qui is for. Phase 3 surfaces cross-instance, cross-tracker awareness the underlying clients cannot produce individually. Deep manipulation (pause/resume/recheck/delete) is supported but proxied through the underlying client's API; the Command Center owns none of that logic.

## Deliverables Checklist

Phase 3 is complete when every item below is true:

- [ ] `internal/integrations/qbit` (or `internal/integrations/torrentclient`) defines a `TorrentClient` interface (`List`, `Get`, `Add`, `Pause`, `Resume`, `Recheck`, `Delete`, `Subscribe`) and ships **two** concrete implementations:
    - `qbit_direct.go` — talks to qBittorrent's WebUI API directly with cookie auth and the efficient `/sync/maindata` diff endpoint.
    - `qbit_qui.go` — talks to qui's reverse-proxy endpoint exposed via [qui's TransparentProxy feature].
  Operators choose between them with a per-client config flag (`proxy_via_qui: true`).
- [ ] SQLite migration `004_phase3_torrents.sql` adds the `torrent_clients` table (the `torrents` and `torrent_trackers` tables landed in Phase 1; reuse those).
- [ ] DuckDB schema additions: `torrent_snapshots` was created in Phase 0; Phase 3 starts writing to it via the snapshot collector.
- [ ] Snapshot collector: a background goroutine per client polls (using the sync endpoint where available) at the configured interval; each cycle writes one `torrent_snapshots` row per torrent into DuckDB and updates the SQLite `torrents` table with last-seen-at and metadata changes.
- [ ] Client definitions are operator-managed via `config/torrent-clients.yaml`. Credentials stored in `secrets` keyed `torrent_client:<id>:password`. Loader and watcher follow the Phase 0 pattern.
- [ ] API endpoints (all require auth from Phase 2):
    - `GET /api/torrents?client=...&tracker=...&category=...&state=...` — paginated.
    - `GET /api/torrents/:hash`
    - `GET /api/torrents/:hash/history?range=24h|7d|30d` — from `torrent_snapshots`.
    - `POST /api/torrents/add` — accepts magnet or .torrent upload.
    - `POST /api/torrents/:hash/pause` | `/resume` | `/recheck`.
    - `DELETE /api/torrents/:hash?deleteFiles=true|false`.
    - `PATCH /api/torrents/:hash` — category, tags.
- [ ] WebSocket endpoint `WS /ws` allows clients to subscribe to topic streams. Phase 3 implements only one topic: `torrents` (broadcasts diffs from the snapshot collector). The websocket framework lives in `internal/eventbus` (a tiny in-process pub/sub) and is reused by every later phase; do not implement a one-off solution.
- [ ] Frontend:
    - `/torrents` route renders a sortable, filterable list with virtualized rendering for thousands of rows.
    - `/torrents/:hash` route renders a detail panel: trackers, peers (live via WS), files tree, history graph.
    - Cross-tracker awareness: each row shows the trackers the torrent is announcing to with per-tracker indicators (FL, seedtime accumulated/required if known).
    - "qui Deep Dive" button on detail page — if `proxy_via_qui` is enabled, opens the qui UI in a new tab at the matching torrent.
- [ ] Tests: adapter tests use `httptest.Server` to simulate qBittorrent / qui responses; snapshot collector tested against the test server; WebSocket subscription tested with `httptest.NewServer` and a real WS client.
- [ ] `PROGRESS.md` and `DECISIONS.md` updated.

## Phase 3 Database Scope

Migration `004_phase3_torrents.sql` creates:
- `torrent_clients`

The `torrents` and `torrent_trackers` tables already exist (Phase 1). DuckDB's `torrent_snapshots` table already exists (Phase 0). Do not re-create.

## Repository Layout

```
internal/
  integrations/
    qbit/
      adapter.go             # TorrentClient interface
      qbit_direct.go         # direct qBittorrent adapter
      qbit_qui.go            # qui reverse-proxy adapter
      *_test.go
  eventbus/
    bus.go                   # typed in-process pub/sub
    topics.go                # named topics (Phase 3 ships "torrents")
    *_test.go
  snapshot/
    torrents.go              # per-client poller + DuckDB writer
    *_test.go
config/
  torrent-clients.yaml
```

Files added under existing packages:
- `internal/server/routes_torrents.go`
- `internal/server/ws.go` — WebSocket handler using `nhooyr.io/websocket` (or `gorilla/websocket`; document choice).

Frontend additions:
- `web/src/pages/Torrents.tsx`
- `web/src/pages/TorrentDetail.tsx`
- `web/src/components/TorrentTable.tsx` — uses `@tanstack/react-virtual`.
- `web/src/lib/ws.ts` — single shared WebSocket client.

## Working Rules

**Don't reimplement qBittorrent.** Mutations are proxied through the underlying client. The Command Center keeps no parallel state machine for torrent transitions.

**Use the sync endpoint.** qBittorrent's `/sync/maindata` returns diffs (rid-based). Polling the full list every cycle is wasteful. The qui adapter passes the same call through transparently.

**One WebSocket, many topics.** A single WS connection per browser tab; subscriptions are managed by the eventbus on the server side and by a topic registry in `web/src/lib/ws.ts` on the client.

**Snapshot writes are append-only.** Never `UPDATE` `torrent_snapshots`. Rollup queries live in the intelligence phase; Phase 3 only writes.

**Tracker association is what makes this different from qui.** Make sure the per-torrent detail page surfaces every tracker the torrent is talking to, with status, seedtime, and last announce. Cross-reference with the `trackers` table from Phase 1.

## What "Phase 3 Complete" Looks Like

After Phase 3 ships, the operator can:

1. Add a qBittorrent or qui client via `config/torrent-clients.yaml` (hot-reload picks it up).
2. Open the Command Center on their phone, see the full torrent list update live as torrents complete or change state.
3. Tap a torrent to see its detail page: per-tracker status, seed-time accumulated, recent ratio.
4. Pause / resume / recheck / delete a torrent and see the change reflected within a second.
5. Read the snapshot history graph for a torrent, populated from DuckDB.
6. Stop a torrent client. The dashboard shows "client unreachable" within the configured grace window; resuming the client recovers without restart.

## Begin

Read `PROJECT.md §7.1` and the qBittorrent WebUI API docs. Implement the eventbus first (it's a primitive every later phase reuses), then the adapter interface, then the two implementations, then the snapshot collector, then routes and frontend. Stop at the end of Phase 3.
