# Capture deletion Deleting under `/captures` reclaims disk: downloaded audio is the one library content that is expensive to recreate (slow, throttled downloads — architecture/youtube-rustypipe.md), so stale captures must be removable from the TUI, and removal must actually delete the files. ## Context Before this feature, deletion (`d`) was limited to *top-level* folders of editable fsdy instances (architecture/bookmarks.md D4): whole captures could be deleted (and were removed from disk via `remove_dir_all`), but nothing below — no single album, no single track. Deletes were deliberately unconfirmed because every deletable node was cheap to recreate. ## Decisions ### D1 — deletable tree as an fsdy instance option `fsdy::Client::with_deletable_tree()` makes every folder below the instance root deletable (recursively, any depth) and every track file deletable. Only the `/captures` instance sets it: - `/queues` and `/bookmarks` keep the top-level-only contract; their nested structure mirrors a snapshot and partial edits are better done by re-saving. - Nested folders become deletable but **not renamable** — renames would break the incremental-capture merge by name (architecture/incremental-captures.md), deletes cannot: a re-capture of the same name simply re-downloads what is missing. - The instance root itself and reserved top-level names stay undeletable even on a deletable tree. ### D2 — tracks advertise deletability through their node Wire truth, not client guessing: the TUI must not hardcode which tracks are deletable. But a per-`Track` flag would touch every Track literal in every provider for a capability only fsdy uses. Instead `LibraryNode.tracks_deletable` says "this node's listed tracks may be deleted", exactly like the existing `is_downloadable` inheritance ("tracks inherit their node's blessing", architecture/captures.md D4). Child folders keep using the existing per-child `is_deletable`. ### D3 — a deleted track takes its audio with it, inside the root only Deleting a track file removes the `.cbd-track.toml` **and** the audio its `[playable] file` points to — that is the point of the feature. Safety boundary: the audio path (relative values resolved against the track file's directory) is canonicalized and must live inside the canonicalized instance root; anything else is kept and logged. So a hand-written track file referencing `~/Music/song.flac` from inside the captures folder can never delete foreign data, and `..`/symlink tricks resolve before the check. A track file that no longer parses is deleted blind (its audio cannot be located; the listing skipped it anyway). Deletes stay idempotent per the proto contract. ### D4 — confirmation in the client, scoped to /captures `d` on anything under `/captures` opens a one-line modal prompt (`delete ? [y/N]`, red) instead of sending; only `y`/`Y` confirms, any other key cancels. Every other deletable (search terms, bookmarks, saved queues) stays a single unconfirmed keypress (architecture/node-editing.md D4) — they are cheap to recreate, and a blanket confirmation would train reflexive `y`. The scoping is a path check in the TUI (like the `/captures` never-cache rule in `cbd-tui/src/rpc.rs`): the server does not know which deletes a client should consider expensive. ## Flow ```d2 direction: right tui: cbd-tui { d: "d on /captures/…" confirm: "delete …? [y/N]" d -> confirm } server: crabidy-server { provider_loop: provider loop } fsdy: fsdy /captures instance { folder: "folder: remove_dir_all" track: "track: toml + contained audio" } tui.confirm -> server.provider_loop: y → DeleteLibraryNode server.provider_loop -> fsdy.folder server.provider_loop -> fsdy.track fsdy.folder -> tui: refreshed parent listing ``` ## Risks / notes - The confirmation prompt occupies the same line as the text-input overlay; both are strictly modal and never open together. - Deleting the folder of a *running* capture is possible; the capture walk recreates directories as it goes and re-downloads on the next run, so the race wastes bandwidth but corrupts nothing.