4.3 KiB
Capture deletion
Superseded by
crabidy-store.md: audio now lives in a shared store that track deletion never touches, so deletion on/crabidygoes through directly with no confirmation and no disk reclamation. This whole feature is removed.
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:
/queuesand/bookmarkskeep 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 <title>? [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
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.