Commit Graph

14 Commits

Author SHA1 Message Date
Test User ad19b6352e cbd-tui: queue marks, visual mode, and a paste register
The queue could only delete the row under the cursor, one at a time
(queue.rs carried the FIXME asking for exactly this), and nothing in the
system had an undo. Both gaps close with one vim-shaped concept.

Marks and visual mode move into a MarkedPane trait in list.rs, next to the
existing StatefulList, and both panes implement it — the library keeps its
behaviour verbatim (its whole visual-mode suite passes untouched), the
queue gains it with an always-allowed mark gate, since its rows carry
is_queable: false.

The register is one unnamed in-memory slot holding library paths, written
only by y (both panes) and d/c/C in the queue, and read by p (insert after
the cursor) and P (before it). So d then P restores exactly what you
deleted, d … p is a move, and clearing 200 tracks with C is finally
recoverable. Paste leaves the register intact and an empty register pastes
nothing rather than sending an empty Insert.

No server work: Remove already accepted many positions and Insert already
took a path list. Because the register holds paths, paste re-resolves — a
yanked album node expands at paste time, and a path that no longer resolves
does not come back.

The one genuinely hard part is that queue marks are positional while the
queue is server-pushed and rebuilt on every change. carry_marks() carries
marks across a snapshot by a greedy in-order match on track path, so a mark
follows its own track through appends, removals, and playback advancing
instead of silently retargeting; irreconcilable snapshots clear rather than
guess. Positions handed to Remove are always read off the newest list.

Breaking change: p in the queue pastes the register instead of inserting
the library selection. That flow is now y then p; a/L/Enter are untouched.
Library queue_insert() had no other caller and is gone.

Also rebalances the help modal's columns (Global + Queue left, Library
right). It was already overflowing at 46 rows in one column; the new
bindings made that worse. It still truncates below ~43 rows — pinned by a
test rather than hidden, and scrolling remains the real fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:24:51 +02:00
Test User 0a9456e173 cbd-tui: add library visual (paint-select) mode on v/V; move spectrum to f
Press v or V (both the same) to enter visual mode in the library pane;
movement then toggles the mark of every row it sweeps over, so a run of
items is selected by v then moving (g/G and Ctrl-d/u paint the whole
span). Entering toggles the current row (vim-style); a second v/V or Esc
leaves the mode with marks kept, and any other action leaves it first
then runs. This frees v, so the frequency-spectrum toggle moves from v
to f.

Painting reuses the existing marks (is_queable-gated, filter-mapped);
no wire, proto, or server change. Library-only for now — the queue has
no marks yet. Ran the full dev-flow; artifacts under architecture/,
quality/, plan/. 89 cbd-tui tests green (14 new); clippy and fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 16:20:55 +02:00
Test User 7eaa8fa9b4 tui: clear-filter on Esc, captured arrow marker, spectrum toggle
Three terminal-UI refinements:

- Esc in navigation clears an active / filter (new ClearSearch action);
  Enter keeps the filter and returns to navigation, / re-opens editing.
- Captured rows render a trailing down-arrow at the end of the row
  (outside the action brackets), visible while browsing any provider.
- v toggles the frequency spectrum at runtime; the spectrum client-config
  value still sets the startup default. The server keeps computing and
  streaming the bars regardless.

README and the mdbook (clients/tui.md, store.md) updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 22:35:51 +02:00
Test User dbd1b955fb Implement the crabidy content store and single /crabidy provider
Replace /queues + /bookmarks + /captures with one /crabidy fs provider
whose track tomls link into a content-addressed store that de-duplicates
audio by provider id and by content hash (architecture/crabidy-store.md).
Green-field: no data migration.

- crabidy_store.rs: CrabidyStore owns the state tree (state_dir/crabidy)
  and the data store (data_dir/crabidy); StoreIndex derived from the
  .cbd-store.toml sidecars. save() enumerates a source into a temp folder
  and swaps it in atomically (conflict refuses); capture_track dedups
  (already-stored -> provider-id -> hash -> new). Queue persistence lives
  here now (persist_current/load_current/save_snapshot/spawn_persister).
- capture.rs: reduced to enumerate + Downloader::download_to + Progress;
  removed the Sink/capture_into/download-to-toml machinery.
- orchestrator: one crabidy_client + crabidy_store, single crabidy_owns
  routing; get_lib_node annotates captured tracks via the store index.
- rpc: capture_error_status helper; save_queue link-saves the live queue
  into /crabidy. playback persists/restores via CrabidyStore.
- tidal/youtube set Track.provider_item_id (track id / video id).
- cbd-tui: /crabidy/current, captured | row marker, delete confirmation
  removed (deletes never touch the store), cache-invalidation + help text.
- delete bookmark_store/capture_store/queue_store; supersede their docs.

See plan/summary.md for deviations (SaveQueue RPC kept; fs id left empty;
shallow folder marking).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 11:34:40 +02:00
Test User ef69afdd5f Capture local files by copying them; add W to capture the queue
Two capture fixes.

Capturing already-local playables: a download capture recorded any
non-http source as skipped, so capturing an fs node or a queue mixing
streamed and local tracks produced red, audioless entries even though
the audio was on disk. fetch_track now copies a local-file source into
the capture next to its toml (source extension kept, counted against the
byte budget); a missing or unreadable source still records skipped.

Queue W: the queue pane only had w (save), so capturing the queue meant
save, navigate, then W. Shift-W in the queue now download-captures the
continuously persisted /queues/current directly.

Deferred to a later refactor: relocating the internal stores out of
.config into .local/state, and a central content-addressed audio store
so captures dedup and link instead of copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 01:08:29 +02:00
Test User 550237f69a Add a / search filter to the TUI library and queue panes
Pressing / in either pane opens a live case-insensitive substring
filter: typing narrows the visible rows, Enter keeps the filter and
returns to navigation, Esc clears it. A shared Filter helper keeps each
pane full list intact and maps view indices to real ones, so movement
keys work on the filtered view unchanged and the queue maps a filtered
selection back to the real server position before removing or setting
current. The library resets search on node change; the queue preserves
it across stream updates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 22:54:39 +02:00
Test User 1c83217745 Delete captures from disk at any depth, behind a confirmation
Deletion (d) previously reached only top-level folders of editable
stores. /captures now exposes its whole tree: nested folders delete
recursively, single tracks delete their metadata file plus the
downloaded audio next to it (never audio outside the instance root).
Tracks advertise this through the new LibraryNode.tracks_deletable
flag. Because these deletes destroy slow-to-redo downloads, the TUI
asks delete <title>? [y/N] first; cheap deletables (search terms,
bookmarks, saved queues) stay unconfirmed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 20:34:03 +02:00
Test User 214dece19f Make download captures incremental with skipped tracks and progress
Download captures now write straight into captures/<name>: satisfied
entries are reused, uncapturable tracks are recorded as skipped tomls
(a new fourth playable, marked red in the TUI and skipped by playback
with a bounded pass), and a failed run keeps its progress so capturing
the same name resumes it. The capture RPC replies on acceptance and
streams CaptureProgress over the update stream, rendered as status
lines in the library pane; help and the input overlay warn that
captures are slow. Colored list items switch to a dark foreground
under the focused selection bar so they stay readable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:45:04 +02:00
Test User 7f0e900c56 Capture library subtrees with downloaded audio
W on a downloadable library node mirrors the subtree into /captures
(a fourth fsdy instance) like a bookmark, but downloads every track's
audio next to its toml; the toml points at the sibling by relative
name, so captures play with no provider round trip. Nodes opt in via
the new is_downloadable flags — Tidal blesses queueable and
track-listing nodes. The bookmark walk is now the shared capture walk
parameterized by a per-track sink.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:45:04 +02:00
Test User 655e8054b8 Capture library subtrees as bookmarks
w on a queueable library selection snapshots the whole subtree into a
third fsdy instance at /bookmarks: the orchestrator walks the source
iteratively and mirrors it as order-prefixed folders of link track
files (shared naming with queue persistence), tmp-and-swapped with
size caps so a runaway tree cannot fill the disk. One additive rpc,
CaptureLibraryNode(path, name), carries the flow; the TUI reuses the
input overlay prefilled with the selection title.

fsdy instances can now opt into an editable top level: root child
folders carry is_editable/is_deletable and support no-merge rename and
idempotent delete. /bookmarks mounts with it, and /queues too
(reserving current), so saved queues are renamable and deletable
through the existing e/d flows without TUI changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 11:11:27 +02:00
Test User 4e44f672e8 Persist queues as fs-provider folders
Queues now survive restarts, built entirely on the fs provider:
fsdy::Client is instance-mountable and a second, read-only instance
serves <config>/crabidy/queues/ as /queues. Every queue is a folder of
order-prefixed link track files plus a hidden state sidecar, written
only by the new QueueStore (tmp-and-swap). The playback loop streams
every queue change through a latest-wins watch channel to a debouncing
persister task and restores queues/current/ (tracks, position,
modifiers) at startup without autoplay. w on the queue pane asks for a
name and drives the previously stubbed SaveQueue rpc; reloading a
saved queue is just queueing /queues/<name>, since link entries
rewrite to their targets at listing time. The old "no links into /fs"
parse rejection gave way to one-hop link semantics so queues can
reference fs tracks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:41:49 +02:00
Test User 333c6e040a Add rename and delete for modifiable library nodes
Search-term nodes created via % are now modifiable: e renames the
selected node (prefilled overlay; the new title re-runs the search,
colliding titles merge) and d deletes it, both gated on new additive
LibraryNodeChild.is_editable/is_deletable flags and marked [ed] in the
library list. Two new rpcs follow the create contract: RenameLibraryNode
returns the renamed node (the TUI navigates into it), DeleteLibraryNode
returns the refreshed parent listing. Queued tracks from a renamed or
deleted term keep playing; verified end-to-end against the live API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:34:47 +02:00
Test User d741523e53 Add Tidal search as creatable library nodes
Pressing % inside /tidal/search opens an input line; the entered term
becomes a tree node whose contents are the search results: track hits
queueable in place, artist and album hits as canonical /tidal/artists
paths. New CreateLibraryNode rpc + is_creatable flags (wire-compatible),
ProviderClient::create_lib_node routed by prefix, percent-encoded term
segments in crabidy-core, and a modal input overlay in the TUI with
creatable nodes marked [%]. Search terms live in memory for the process
lifetime; term nodes are deliberately not queueable so the resolve sweep
cannot drag whole discographies into the queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:58:44 +02:00
Test User 8586194096 Add a help modal to cbd-tui behind a declarative binding table
Pressing ? opens an overlay listing usage notes and every key binding.
The bindings now live in one declarative table (app/bindings.rs) that
both key dispatch and the help modal render from, so the help can never
drift from the real bindings. Includes the dev-flow design artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:27:12 +02:00