From 7fe4326923f3e587f140176425f7f9fb0542b866 Mon Sep 17 00:00:00 2001 From: Test User Date: Sun, 26 Jul 2026 12:04:03 +0200 Subject: [PATCH] queue: insert at a position, not after it, and show queue marks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs from the register work, both reported from actual use. **Paste landed one row too far.** `QueueManager::insert_tracks` spliced at `position + 1` — it inserted *after* the given index, while its own CLI help says "insert tracks/subtrees at a position". So `p` (which sent cursor + 1) landed two below the cursor and `P` (cursor) landed one below, exactly as reported. The clients were already computing the right indices for an insert-*at* API. Fixed at the primitive rather than in the clients, because "after" cannot express the front of the queue: the earliest reachable index was 1, so pasting before the first row — and therefore undoing a delete of it — was impossible. `insert_tracks` now inserts **at** `position`, pushing that row down, with 0 the front and past-the-end an append. The two callers that genuinely mean "after" pass `N + 1`: `ResolveKind::InsertAfter` (which keeps its name and its streaming-chunk arithmetic) and `queue_tracks` (play-next, `L`). All existing behaviour is preserved — the whole server suite passes untouched — and three tests pin the new front/interior/play-next cases. `queue insert ` on the CLI shifts by one accordingly, which brings it in line with what its help always claimed. Documented in the proto, the CLI help, and the book. **Queue marks were invisible.** The TUI rendered no mark indicator, so `s` and visual mode had no feedback. Marked rows now carry the library's `*` prefix and the same green bold; the playing row keeps `>` and its red, and a row that is both shows `> * title`. The web client already rendered marks (its `.marked .title` rule), but neither client showed visual mode outside the TUI's pane title — both panes there now get a VISUAL badge in the toolbar. Co-Authored-By: Claude Opus 5 (1M context) --- cbd-cli/src/lib.rs | 3 +- cbd-tui/src/app/queue.rs | 46 ++++++++++++++++++++--- cbd-web/src/app.rs | 6 +++ cbd-web/style.css | 12 ++++++ crabidy-core/crabidy/v1/crabidy.proto | 3 ++ crabidy-server/src/lib.rs | 54 ++++++++++++++++++++++----- docs/src/clients/cli.md | 2 + docs/src/queue.md | 4 ++ 8 files changed, 114 insertions(+), 16 deletions(-) diff --git a/cbd-cli/src/lib.rs b/cbd-cli/src/lib.rs index 32d5f57..57bf0f4 100644 --- a/cbd-cli/src/lib.rs +++ b/cbd-cli/src/lib.rs @@ -76,7 +76,8 @@ pub enum QueueCmd { Show, /// Append tracks/subtrees (by library path) to the end of the queue. Append { paths: Vec }, - /// Insert tracks/subtrees at a position. + /// Insert tracks/subtrees at a position, pushing what was there down + /// (0 = front, past the end = append). Insert { position: u32, paths: Vec }, /// Replace the whole queue with the given tracks/subtrees. Replace { paths: Vec }, diff --git a/cbd-tui/src/app/queue.rs b/cbd-tui/src/app/queue.rs index e310d49..6d99d91 100644 --- a/cbd-tui/src/app/queue.rs +++ b/cbd-tui/src/app/queue.rs @@ -10,7 +10,7 @@ use ratatui::{ use crabidy_core::proto::crabidy::Queue as QueueData; use super::{ - carry_marks, Filter, MarkedPane, MessageFromUi, StatefulList, UiItem, UiItemKind, + carry_marks, Filter, MarkedPane, MessageFromUi, StatefulList, UiItem, UiItemKind, COLOR_GREEN, COLOR_PRIMARY, COLOR_PRIMARY_DARK, COLOR_RED, COLOR_SECONDARY, }; @@ -229,13 +229,24 @@ impl Queue { .map(|(idx, (real, item))| { let active = real == self.current_position; - let title = if active { - format!("> {}", item.title) - } else { - item.title.to_string() - }; + // Markers, in the library's vocabulary: `>` is the playing + // track, `*` is a mark (`s`, or painted in visual mode). A + // row can be both. + let mut title = String::new(); + if active { + title.push_str("> "); + } + if item.marked { + title.push_str("* "); + } + title.push_str(&item.title); let mut style = if active { Style::default().fg(COLOR_RED).add_modifier(Modifier::BOLD) + } else if item.marked { + // Same green as a marked library row. + Style::default() + .fg(COLOR_GREEN) + .add_modifier(Modifier::BOLD) } else if item.is_skipped { // No playable audio: rendered red (not bold — the // playing marker keeps precedence), skipped by @@ -455,6 +466,29 @@ mod tests { /// Renders and returns the buffer plus the y of the row containing /// `needle` and the x of its first character. + #[test] + fn marked_rows_render_a_star_like_the_library() { + let (tx, _rx) = flume::unbounded(); + let mut queue = Queue::new(tx); + queue.update_queue(queue_data(&["one", "two"], false)); + queue.select(Some(1)); + queue.toggle_mark(); + let rows = rendered_rows(&mut queue); + // The playing row keeps `>`; the marked row gets `*`. + assert!( + rows.iter().any(|row| row.contains("* artist - two")), + "marked row needs a star: {rows:?}" + ); + // And the title says so while visual mode is on. + queue.toggle_visual(); + assert!( + rendered_rows(&mut queue) + .iter() + .any(|row| row.contains("VISUAL")), + "visual mode needs a title indicator" + ); + } + fn render_and_find(queue: &mut Queue, needle: &str) -> (ratatui::buffer::Buffer, u16, u16) { let backend = TestBackend::new(40, 8); let mut terminal = Terminal::new(backend).expect("test terminal"); diff --git a/cbd-web/src/app.rs b/cbd-web/src/app.rs index 034eded..f7959a3 100644 --- a/cbd-web/src/app.rs +++ b/cbd-web/src/app.rs @@ -884,6 +884,9 @@ fn LibraryView(store: Store) -> impl IntoView { "‹" {pane.title.clone()} + + "VISUAL" +