# Task plan — jamendo provider Ordered, independently verifiable tasks for `/jamendo` (architecture/jamendo-provider.md, quality/jamendo.md). Each cites the gate/test it satisfies. ## Crate scaffold - [x] **P1** — Add `jamendody` to `Cargo.toml` workspace `members`; create `jamendody/Cargo.toml` mirroring `soundclouddy` (deps: `async-trait`, `crabidy-core`, `reqwest`, `serde`, `thiserror`, `tokio`, `toml`, `tracing`; dev-dep `tokio` full). *Verify:* `cargo metadata` lists the crate. ## Network seam (api.rs) - [x] **P2** — Domain types `JamTrack` (id, title, artist, album, duration_secs) and `JamAlbum` (id, title, artist, tracks); `FetchError` (`Http`, `Decode`, `NotFound`, `Unauthorized`, `NotStreamable`). *Verify:* compiles. (G2) - [x] **P3** — `Jam` trait: `search_tracks`, `search_albums`, `album_tracks`, `track_detail`, `track_stream`. Doc each with error behavior. *Verify:* compiles. (G2/G10) - [x] **P4** — `JamApi` (reqwest): `new(client_id, audioformat, timeout)` with a timeout-bearing client and redacting `Debug`; `get_json` helper adding `client_id` + `format=json`, decoding the `{headers, results}` envelope, mapping non-success/404/401 to `FetchError`. `limit.min(200)` cap. *Verify:* `cargo build`. (G3/G4/G5/G9) - [x] **P5** — Defensive DTOs (`#[serde(default)]`) for track/album/envelope and `into_track`/`into_album`. *Verify:* build + a decode unit test on a JSON fixture. (G9) ## Provider logic (lib.rs) - [x] **P6** — `PROVIDER_ROOT`, segment consts, `JamPath` enum + `parse_path`. *Verify:* T2, `foreign_and_malformed_paths_reject`. (D3) - [x] **P7** — `Settings` (`client_id: Option` + bounds) with redacting `Debug`. *Verify:* T10 `settings_debug_redacts_client_id`. (G3) - [x] **P8** — `Client` + `with_api`, search-term store helpers (`store_add/remove/snapshot`), `search_limit`/`album_limit`. *Verify:* compiles. (D3) - [x] **P9** — Node builders: `root_node` (search only), `store_node`, `search_term_node` (tracks + albums), `album_node`, `list_node`, `jam_track`. *Verify:* T1, T4, T5, T7, T11. (D3/D4) - [x] **P10** — `ProviderClient` impl: `init` (require non-empty `client_id`, else `Config`), `settings`, `is_track_path`, `get_urls_for_track` (→ `track_stream`), `get_metadata_for_track`, `get_lib_root`, `get_lib_node` (download blessing), `create/rename/delete_lib_node`. *Verify:* T3, T6, T8, T9. (D4/G1/G2/G6) ## Tests (tests.rs) - [x] **P11** — `FakeJam` + `FailingJam` fixtures and all T1–T11. *Verify:* `cargo test -p jamendody`. (all tests) ## Orchestrator + settings wiring (crabidy-server) - [x] **P12** — `settings.rs`: `jamendo` in `ALL_PROVIDERS` (8→9), `ProviderToggles.jamendo`, defaults, `provider_toggles()`. *Verify:* build + existing settings tests. (G6) - [x] **P13** — `provider.rs`: `jamendo_client` field, `jamendo_owns`, `jamendo_provider`, non-fatal `build()` block (reads `jamendo.toml`), `is_track_path` arm, `get_lib_root` child (gated on `is_some`), `get_lib_node` arm, `get_urls_for_track`/`get_metadata_for_track` arms, create/rename/delete arms. *Verify:* `cargo build -p crabidy-server`. (D1/G6/G8) ## Verification gate - [x] **P14** — `cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo test` green across the workspace. Re-read G1–G10 against the diff. - [ ] **P15 (live gate, R1)** — with a real `client_id` in `jamendo.toml`, browse `/jamendo/search/`, queue and **play** a track: confirm audio plays via the existing windowed-HTTP path, seek bar correct (duration from metadata), clean EOS, no panic. Deferred to a run with network + a key.