crabidy/plan/orphans.md

115 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Task plan — the `orphans` provider
Implements `architecture/orphans.md`, satisfying `quality/orphans.md`. Ordered by
dependency. Each task names its verification. All `cargo`/tooling runs go through
`devenv shell -- …` (see `CLAUDE.md`).
> **Done.** All tasks implemented and verified: `cargo test --workspace`,
> `cargo clippy --workspace --all-targets -- -D warnings`, `cargo fmt --check`,
> and `mdbook build docs` all pass. See "Deviations" at the end.
## Store layer (`crabidy-server/src/crabidy_store.rs`)
- [x] **T1 — `OrphanEntry` + `StoreIndex::remove`.** Move/define `OrphanEntry`
(name + display metadata) and add `StoreIndex::remove(&mut self, name,
sidecar)`, the inverse of `insert` (drop `by_hash[hash]` and each
`by_provider_id[(provider,id)]` that still maps to `name`). *Verify:* unit
test that `insert` then `remove` leaves the index empty; clippy/fmt.
- [x] **T2 — `list_orphans(&self, ref_roots) -> Result<Vec<OrphanEntry>, StoreError>`.**
Scan `store_root` for `*.cbd-store.toml` (name + parsed sidecar); require the
audio file present unless reporting malformed residue (G5). Build the
referenced set by walking each `ref_root` recursively for `*.cbd-track.toml`,
parsing each and collecting `Playable::Store(name)` (skip unreadable/bad files
with a warning). Return all referenced, metadata from `providers[0]`.
*Verify:* G1G6 tests.
- [x] **T3 — `orphan_track` / `orphan_url`.** `orphan_track(name)` reads the
sidecar and builds a `Track` (metadata from `providers[0]`, `is_captured =
true`, path left to the provider to set). `orphan_url(name)` returns
`store_root/<name>` as a string, erroring `NoDir`/`MalformedPath`-style if the
entry is gone. *Verify:* G8/G9 tests.
- [x] **T4 — `rename_orphan(&self, old, new)`.** Under the index mutex:
`validate_folder_name(new, &[])`; refuse if `name_taken(new)` (G13); rename
audio and sidecar (`tokio::fs::rename` both, cross-device fallback to
copy+remove like `ingest_file`); read the sidecar, `index.remove(old,
&sidecar)`, `index.insert(new, &sidecar)`. *Verify:* G11G13 tests.
- [x] **T5 — `delete_orphan(&self, name)`.** Under the index mutex: read the
sidecar (for index removal; tolerate a missing sidecar → still remove any
stray audio), remove audio + sidecar (idempotent on `NotFound`),
`index.remove(name, &sidecar)`. *Verify:* G15G16 tests.
## Provider (`crabidy-server/src/orphans.rs`)
- [x] **T6 — `entry_name` + leaf/root nodes.** Fill `entry_name` (decode the one
segment; reject bare root, deeper, or separator-bearing paths → `None`).
Implement `get_lib_root` and `get_lib_node`: root lists children from
`store.list_orphans(&self.ref_roots)` (flags per G7, path
`/orphans/<encode(name)>`, title `name`); leaf returns a childless queueable
node with the single `orphan_track` (path set to the leaf path). *Verify:*
G7/G8 tests; `MalformedPath` for unknown/`..` segments (G17).
- [x] **T7 — play/queue + mutations.** Implement `get_urls_for_track`
(`orphan_url`), `get_metadata_for_track` (`orphan_track`), `rename_lib_node`
(validate → `rename_orphan` → return new node), `delete_lib_node`
(`delete_orphan` → return refreshed root). *Verify:* G9, G11G17 tests via the
provider surface.
## `fsdy` accessor (`fsdy/src/lib.rs`)
- [x] **T8 — `Client::disk_root(&self) -> &Path`.** Public accessor returning the
instance's disk root, so the orchestrator can hand the `/fs` root to the
orphans provider as a reference root. Doc comment (G20). *Verify:* trivial;
compiles + fmt.
## Orchestrator wiring (`crabidy-server/src/provider.rs`, `lib.rs`)
- [x] **T9 — Declare the module.** Add `mod orphans;` (and any `pub use`) to
`crabidy-server/src/lib.rs`; this un-inerts the stub. *Verify:* workspace
compiles.
- [x] **T10 — Mount + route `/orphans`.** Add `orphans_client:
Option<Arc<OrphansProvider>>` to `ProviderOrchestrator`; construct it in
`init` when `crabidy_store` is `Some`, with `ref_roots` = `[store.tree_dir()]`
plus `fs_client.disk_root()` when present. Add `orphans_owns(path)`; route
`is_track_path`, `get_lib_node`, `get_urls_for_track`,
`get_metadata_for_track`, `create/rename/delete_lib_node`, and
`resolve_tracks_into` to it. Add the `orphans` child to `get_lib_root` when
mounted. Note: keep `annotate_captured` running on orphan nodes (already
correct — store-backed tracks are `is_captured`). *Verify:* G21 by reading;
workspace compiles; existing provider tests still pass.
## Docs
- [x] **T11 — mdbook + README.** Add an `/orphans` section to
`docs/src/store.md` (or a short `docs/src/providers/orphans.md` linked from
`SUMMARY.md`) and a line to `README.md`'s provider tree describing `/orphans`
as the store's reclamation view (rename/delete/queue unreferenced audio).
*Verify:* `devenv shell -- mdbook build docs` + markdownlint clean.
## Final gate
- [x] **T12 — Full verification.** `devenv shell -- cargo test --workspace`,
`cargo clippy --workspace -- -D warnings`, `cargo fmt --check`, markdownlint.
Re-read `quality/orphans.md` G1G21 and check each. Update
`plan/summary.md`. Commit with the co-author trailer.
## Deviations from the plan / architecture
- **Test placement (T2/T4/T5).** Rather than one test per store method, the
provider-level tests in `orphans.rs` exercise `list_orphans`/`orphan_track`/
`orphan_url`/rename/delete end-to-end through the `ProviderClient` surface
(list vs. reference, leaf-track resolution, rename-both-files + delete,
traversal rejection). One store-level test
(`rename_orphan_moves_both_files_and_repoints_the_index`) additionally asserts
G12 (index repoint) by inspecting the private index. All of G1G21 are
covered; the split just differs from the per-method layout the plan implied.
- **`list_orphans` enumerates a union (G5).** It walks both `*.cbd-store.toml`
sidecars and bare audio files, so a sidecar without audio *and* audio without
a sidecar both surface as reclaimable — as the architecture's "malformed
residue" note requires.
- **Docs home (T11).** The user-facing section landed in `docs/src/store.md`
("Reclaiming orphans — /orphans") with a bullet in `providers.md` and a line
in the README provider tree, rather than a standalone
`docs/src/providers/orphans.md` page — `/orphans` is a view over the store, so
it reads best beside the store doc, and no new `SUMMARY.md` entry was needed.
- **No proto/TUI/web changes (G10 held).** The feature reuses the existing
`is_editable`/`is_deletable`/`is_queable` child handling end-to-end; the only
non-server change is the additive `fsdy::Client::disk_root` accessor.