147 lines
8.5 KiB
Markdown
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.
|