# Plan: queue de-duplication and sorting Executes `architecture/queue-order.md` against the gates in `quality/queue-order.md`. The wire and the stubs already exist (api-design); what follows fills them in, in dependency order. Every task names how it is verified. ## 1. The wire - [x] **T1 — Proto: `DedupQueue`, `SortQueue`, `QueueSort`.** Two RPCs, the strategy enum, `DedupQueueResponse.removed`, with the semantics documented at the wire. *Verified by:* it compiles through `tonic-prost-build`; G21 by reading. - [x] **T2 — Rights matrix.** Both methods as `QueueOwner`, added to the pinned method list. *Verified by:* `auth.rs::the_method_table_pins_every_rpc_of_the_service` (G11). ## 2. Pure order logic (`crabidy-server/src/queue_order.rs`) - [x] **T3 — `track_id`.** Provider-scoped item id, else the whole path. *Verified by:* `a_provider_item_id_identifies_a_track_across_paths`, `ids_are_scoped_to_their_provider`, `a_captured_copy_is_not_its_streaming_source`, `without_an_id_the_path_is_the_identity` (G2). - [x] **T4 — `duplicate_positions`.** Survivor = current, else earliest; ascending, unique, never the current position; safe on an empty queue and an out-of-range current. *Verified by:* `duplicates_after_the_first_are_removed`, `the_playing_entry_survives_its_group`, `nothing_to_do_is_an_empty_list`, `positions_come_back_ascending_and_are_unique` (G1). - [x] **T5 — `sort_permutation`.** Decorate–sort–undecorate with keys built once; compound artist key; case-insensitive text; unknown-last in both directions; `Reverse` ignores the direction; `Unspecified` is the identity. *Verified by:* `every_strategy_returns_a_permutation`, `sorting_by_artist_groups_albums_and_keeps_track_order`, `text_sorts_ignore_case`, `descending_reverses_the_key_but_not_the_blanks`, `unknown_durations_sort_last_in_both_directions`, `reverse_flips_the_current_order_and_ignores_the_direction`, `an_unspecified_strategy_changes_nothing`, `sorting_nothing_does_not_panic` (G3, G4, G5). ## 3. Queue state (`crabidy-server/src/lib.rs`) - [x] **T6 — `QueueManager::dedup`.** Delegates the removal to `remove_tracks`; returns the count. *Verified by:* `dedup_removes_later_copies_and_reports_the_count`, `dedup_keeps_the_playing_track_playing`, `dedup_of_a_clean_queue_changes_nothing`, `dedup_leaves_the_queue_playable` (G1, G6, G7). - [x] **T7 — `QueueManager::sort`.** Permute `tracks`; shuffle off ⇒ identity play order with `current_offset` on the current track, shuffle on ⇒ remap. *Verified by:* `sorting_reorders_the_queue_and_follows_the_current_track`, `sorting_without_shuffle_changes_what_plays_next`, `sorting_with_shuffle_on_preserves_the_shuffled_play_sequence`, `sorting_with_shuffle_on_still_reorders_the_visible_queue`, `sorting_an_empty_or_single_queue_does_not_panic`, `an_unspecified_sort_leaves_the_queue_alone` (G6, G5). ## 4. Server plumbing - [x] **T8 — Playback loop arms.** Lock, mutate, broadcast; dedup replies with its count and skips the broadcast when it removed nothing. *Verified by:* `dedup_command_removes_duplicates_and_replies_with_the_count`, `dedup_command_answers_zero_without_broadcasting`, `dedup_command_survives_a_dropped_result_channel`, `sort_command_reorders_the_queue_and_broadcasts_it`, `queue_order_commands_reach_the_persist_channel` (G8, G9, G10, G12). - [x] **T9 — RPC handlers.** `Unspecified`/unknown → `InvalidArgument` before the loop sees it; the count passes through verbatim. *Verified by:* `sort_queue_refuses_an_unnamed_strategy`, `sort_queue_forwards_the_strategy_and_direction`, `dedup_queue_returns_the_loops_count` (G10, G12). ## 5. TUI - [x] **T10 — Client RPC methods** (`dedup_queue`, `sort_queue`) and the `MessageFromUi`/`MessageToUi` variants. *Verified by:* compiles; exercised through the dispatch tests. - [x] **T11 — Bindings.** `u` dedup, `S` sort menu, queue scope only. *Verified by:* `queue_order_keys_are_bound_in_the_queue_only`, `chords_are_unique_within_scope` (G17). - [x] **T12 — `sort::choose` + the menu table.** Lowercase ascending, uppercase descending, reverse case-insensitive, unclaimed keys unclaimed. *Verified by:* `a_lowercase_key_sorts_ascending_and_its_capital_descending`, `reverse_ignores_the_case`, `keys_the_menu_does_not_offer_are_not_claimed`, `the_table_is_consistent` (G15). - [x] **T13 — `sort::render` + `popup_area`.** Centered, `Clear`-backed, clamped to the frame. *Verified by:* `the_overlay_lists_every_strategy_with_its_key`, `a_tiny_frame_clamps_the_popup`. - [x] **T14 — `App::handle_sort_key`.** Modal: sort-and-close, `Esc`/`q`/`S` close, anything else ignored. *Verified by:* `a_sort_menu_key_sends_the_strategy_and_closes_the_menu`, `escape_closes_the_sort_menu_without_sorting`, `an_unknown_key_leaves_the_sort_menu_open` (G15). - [x] **T15 — Dispatch + event-loop routing + render hook.** Empty-queue guards on both actions; the menu is checked before the bindings table. *Verified by:* `dedup_asks_the_server_only_with_a_non_empty_queue`, `the_sort_menu_opens_only_with_a_non_empty_queue`. - [x] **T16 — The pane's dedup notice.** `show_dedup_result` + `notice()` expiry + title precedence (visual > filter > notice > register). *Verified by:* `the_dedup_result_appears_in_the_title`, `the_dedup_result_expires`, `an_active_search_outranks_the_dedup_result` (G18). ## 6. CLI - [x] **T17 — `queue dedup` / `queue sort [--desc]`.** `ValueEnum` strategy. *Verified by:* `queue_dedup_takes_no_arguments`, `queue_sort_parses_a_strategy_and_an_optional_direction` (G19). - [x] **T18 — `wire_sort` / `sort_label` and the printed results.** Dedup prints its count including `0`. *Verified by:* the parse tests plus reading (G18, G19). ## 7. Web client - [x] **T19 — RPC methods, `Dialog::Sort`, actions, keymap entries, toolbar buttons, key routing.** *Verified by:* `queue_order_keys_match_the_tui`, `the_sort_table_is_consistent` (G16, G17). - [x] **T20 — `sort_menu_key`.** The web port of `sort::choose`, plus the close keys. *Verified by:* `the_sort_menu_maps_keys_to_strategies_and_closes_on_escape` (G15, G17). - [x] **T21 — `SortMenu` component + `notify`.** Rows clickable and typeable, dedup count in a toast. *Verified by:* the wasm build (`build-web`) plus reading (G16, G18). - [x] **T22 — Web help overlay entries** for `u` and `S`. *Verified by:* `every_action_reachable_from_help_table` plus reading (G20). ## 8. Documentation - [x] **T23 — `docs/src/queue.md`**: the two order operations in the queue model, the duplicate rule, the strategies, and the shuffle interaction. - [x] **T24 — `docs/src/clients/tui.md`** key table and **`docs/src/clients/cli.md`** command list. *Verified by:* reading; the book builds (G20). ## 9. Close-out - [x] **T25 — Full check.** `cargo test` (workspace), `cargo clippy --all-targets -D warnings`, `cargo fmt --check`, `cargo test -p cbd-tui --no-default-features`, and the web build. Then `plan/summary.md`.