crabidy/plan/build-features.md

147 lines
8.5 KiB
Markdown

# Plan — build features
Executes `architecture/build-features.md` against `quality/build-features.md`.
Ordered by dependency; each task names how it is verified. Three commits:
**(A)** the mount-registry refactor (no feature change), **(B)** the features
themselves, **(C)** packaging + docs.
Build commands run through devenv with the session-local target dir:
```sh
devenv shell -- bash -lc 'CARGO_TARGET_DIR="$(pwd)/target-claude" cargo … '
```
## A — Mount registry (behaviour-preserving refactor)
- [ ] **A1 — `Mount` + registry types.** In `provider.rs`: `struct Mount {
root: &'static str, name: &'static str, client: Arc<dyn ProviderClient> }`,
`Mount::new(root, name, Arc<impl ProviderClient>) -> Mount` (the `Arc`
coerces at the call site), and `Mount::owns(&self, path) -> bool` matching
`path == root || path.starts_with("<root>/")` — so `/fsx` is not `/fs`.
*Verifies:* `mount_owns_its_root_and_children_only` (G10).
- [ ] **A2 — `ProviderOrchestrator` holds `mounts: Vec<Mount>`.** Replace the
nine `*_client` fields (keep `crabidy_store`). Add
`from_mounts(Vec<Mount>) -> Self` (sorts the mounts crabidy-first /
orphans-last / rest alphabetical, and creates the bounded channel), used by
`build` and the tests. *Verifies:* `root_lists_mounted_providers_in_order`
(G12).
- [ ] **A3 — One dispatch helper.** `owner(&self, path) -> Option<&dyn
ProviderClient>` plus `owner_or(&self, path, err)` that warns with the same
message and returns the typed error. Delete the nine `*_owns` functions and
the nine `*_provider` accessors. *Verifies:* `unowned_paths_are_typed_errors`
(G11).
- [ ] **A4 — Rewrite the eight dispatch methods** (`is_track_path`,
`get_urls_for_track`, `get_metadata_for_track`, `get_lib_node`,
`create_lib_node`, `rename_lib_node`, `delete_lib_node`,
`resolve_tracks_into`) as a single lookup each, preserving: the synthetic
root short-circuit and `annotate_captured` in `get_lib_node`,
`MalformedPath` for lookups, `NotSupported` for mutations, and the
`warn!`s. *Verifies:* `dispatch_reaches_the_owning_mount`,
`overridden_resolve_tracks_into_is_dispatched`,
`no_mounts_serves_an_empty_root` (G10, G11, G13).
- [ ] **A5 — `get_lib_root` maps the sorted mounts** to `LibraryNodeChild::new`
(no per-provider block, no re-sort). *Verifies:*
`root_lists_mounted_providers_in_order` (G12).
- [ ] **A6 — `build()` registers mounts.** Each provider's init block stays as
is; on success it pushes a `Mount` instead of assigning a field. `/crabidy`
and `/orphans` push after the store exists. *Verifies:* full test suite +
`cargo clippy -- -D warnings` (G2).
- [ ] **A7 — Commit A.** `cargo test -p crabidy-server`, clippy, fmt all clean;
no `Cargo.toml` change in this commit (G1/G2 hold trivially).
## B — The features
- [ ] **B1 — Manifests** (done in api-design; re-verify): `crabidy-server`
(`default = all-providers + opus + spectrum + web-ui`, gated deps optional),
`audio-player` (`opus`), `cbd-tui` (`notifications`), `cbd` (pass-through of
all of them). *Verifies:* G4, G5; `cargo tree -p crabidy-server` at defaults
matches the pre-change tree (G1).
- [ ] **B2 — `settings`: `BUILT_IN_PROVIDERS`, `provider_enabled` bounded by
it, `unavailable_providers()`, `ensure_default` writing only built-ins.**
Replace the `todo!()`. *Verifies:*
`an_absent_providers_key_enables_every_built_in_provider`,
`unavailable_providers_reports_built_out_and_unknown_names`,
`ensure_default_lists_only_built_in_providers`,
`a_built_out_provider_cannot_be_enabled` (G4, G25, G26).
- [ ] **B3 — Per-provider `#[cfg]` in `build()` only.** Wrap each provider's
init+registration block in `#[cfg(feature = "…")]`; the `enabled.*` toggle
read stays inside the block. Provider `use`/imports move to the same gate.
*Verifies:* the matrix (B12) compiles each provider alone; G9 by reading.
- [ ] **B4 — `fs`: gate the store half.** `#[cfg(feature = "fs")]` on the
`crabidy_store`, `capture`, and `orphans` modules (`lib.rs`), on
`ProviderOrchestrator::crabidy_store`/its field, on `Playback`'s store field
and `restore_current` and the persister, and on the `/fs`, `/crabidy`,
`/orphans` registrations. `Playback::new` keeps its arity — the store
parameter becomes `#[cfg]`-free by threading `Option<Arc<CrabidyStore>>`
only under the feature (adjust the two call sites in `lib.rs` and the
playback tests). *Verifies:* G14, G17; `--no-default-features` build (B12).
- [ ] **B5 — `fs`: RPC degradation.** Without `fs`, `CaptureLibraryNode` and
`SaveQueue` return `Status::unimplemented` naming the feature; with `fs`,
unchanged. No panic, no hang. *Verifies:* G15 by reading + a
`--no-default-features` smoke run.
- [ ] **B6 — `fs`: the `scan` command.** Gate `AUDIO_EXTENSIONS`,
`is_audio_file`, `scan`, `scan_dir`, `scan_file` and their tests behind
`fs`; without it the dispatcher in `crabidy-server/src/main.rs` and
`cbd/src/main.rs` prints "this binary was built without the `fs` feature"
and exits non-zero. The clap surface does not change. *Verifies:* G16, G9.
- [ ] **B7 — `opus` in `audio-player`.** Gate `mod opus_source` and the
`is_ogg_opus` sniff in `player_engine::build_source`; without the feature an
Ogg-Opus header produces `Err` naming the missing feature (the existing
"failed to decode" path), never a panic. *Verifies:* G18, G19 (read + the
`opus`-off matrix entry); `opus_source`'s own tests stay under the feature.
- [ ] **B8 — `opus` in `scan`.** `AUDIO_EXTENSIONS` includes `"opus"` only
under the feature. *Verifies:* `opus_is_scannable_only_with_the_decoder`,
`scan_walks_past_opus_files_it_cannot_play` (G20).
- [ ] **B9 — `spectrum`.** Gate `mod spectrum`, `spawn_spectrum_task`, and its
call; `realfft` optional. `Player::spectrum_tap()` unchanged. *Verifies:*
G21; the `spectrum`-off matrix entry.
- [ ] **B10 — `notifications` in `cbd-tui`.** Gate the `notify_rust` import and
the notify call in `app/now_playing.rs`; the config key stays accepted so an
existing `cbd-tui.toml` still parses. *Verifies:* G23; `cbd-tui
--no-default-features` build + `cargo test -p cbd-tui`.
- [ ] **B11 — `features` command.** `cli::build_features()` +
`cli::features()` (replace the `todo!()`s), wired into
`ServerCommand::Features` and `CbdCommand::Features`; plus one `info!` line
at startup listing the
build, and one `warn!` per `unavailable_providers()` entry. *Verifies:*
`build_features_lists_providers_then_extras` (G24, G25).
- [ ] **B12 — The build matrix as a devenv script.** `check-features` runs
`cargo clippy -- -D warnings` (and `cargo test` where meaningful) over:
defaults · `--no-default-features` · each of the seven providers alone ·
defaults-minus-`opus` · minus-`spectrum` · minus-`web-ui` · `-p cbd-tui
--no-default-features` · `-p audio-player --no-default-features`. Add
`cargo-hack` to devenv for ad-hoc deeper sweeps. *Verifies:* G6, G7.
- [ ] **B13 — Commit B** once the whole matrix is green and no
`todo!()`/`unimplemented!()` from the stub stage remains (G28).
## C — Packaging and docs
- [ ] **C1 — `flake.nix`.** Native build: replace the bare
`--no-default-features` with an explicit
`--no-default-features --features all-providers,opus,spectrum`; leave the
aarch64 cross build on defaults (`web-ui` on). *Verifies:* G8;
`nix build .#crabidy` if the sandbox allows, otherwise read + the equivalent
cargo invocation.
- [ ] **C2 — `docs/src/build-features.md`** — the feature table, what each one
costs to lose, the two worked examples (local-files appliance:
`--no-default-features --features fs,opus`; streaming box:
`--no-default-features --features tidal,web-ui,opus,spectrum`), the
compile-time vs runtime distinction, and the `features` command. Linked from
`SUMMARY.md`; `docs/src/config.md` cross-references it from the `providers`
key. *Verifies:* G27 + `mdbook build`.
- [ ] **C3 — `README.md`** gains a short "tailored builds" pointer.
*Verifies:* G27.
- [ ] **C4 — `plan/summary.md`** entry describing what shipped, the decisions
taken autonomously, and what stayed out (auth, HLS, spectrum tap — with the
reason). *Verifies:* dev-flow convention.
- [ ] **C5 — Commit C.**
## Deferred / not done (recorded, not silently dropped)
- Gating `[auth]`/`argon2` — refused (fail-open security risk, D9).
- Gating `hls.rs`, `windowed_http.rs`, `spectrum_tap.rs` — no dependency
payoff (D7, D8).
- A `minimal` convenience feature — users compose their own set (D1).
- CI enforcement of the matrix — there is no CI yet; `check-features` is the
manual gate.