# Quality gates: queue de-duplication and sorting Criteria an implementation of `architecture/queue-order.md` must satisfy. Automatic tests cover the behaviour (see "Tests" at the end); the gates here are the things a reader has to check by reading the code. Each is pass/fail. ## Correctness of the operations - [ ] **G1 — Dedup never removes the playing entry.** In every group of duplicates the survivor is the entry at the current position when it is in the group, otherwise the earliest one (D4). Check the survivor selection directly, not only through a test: a "keep the first" shortcut is a silent playback stop the moment the playing copy is a later one. - [ ] **G2 — The default duplicate identity is provider-scoped.** The key is `(first path segment, provider_item_id)` when the id is non-empty and the whole path otherwise (D3). Artist/title matching exists **only** under the opt-in `by_title` identity (D3a), never on the default path, and the flag is never defaulted to true anywhere between key and wire. - [ ] **G2a — `by_title` keeps the longest take** and still yields to the playing entry (D3a), and every client surface that reaches it is distinct from the safe one (its own key, its own flag) — a user cannot get it by mistyping. - [ ] **G3 — Sort is stable and total.** `sort_permutation` returns a permutation of `0..len` for every strategy and direction, equal keys keep their queue order, and blanks/unknown durations sort last in *both* directions (D7, D8). No comparator may panic (no `unwrap` on a partial comparison, no float keys). - [ ] **G4 — Sort keys are computed once per track.** Decorate–sort–undecorate, not a comparator that lowercases inside the comparison (A5). A ten-thousand entry queue must not allocate per comparison. - [ ] **G5 — Reverse ignores the direction flag** (D6), and `QueueSort::Unspecified` reaching `QueueManager::sort` is a no-op rather than an arbitrary default. - [ ] **G6 — The play-order invariant holds after both operations.** `play_order` indexes every track exactly once and `current_offset` points into it (the invariant `shuffle_insert_keeps_order_unique` guards for inserts). Shuffle off ⇒ rebuilt from the new order; shuffle on ⇒ remapped through the permutation, so the remaining play sequence is unchanged (D10). - [ ] **G7 — Dedup goes through `remove_tracks`.** There is exactly one place that removes queue entries (D5). A second removal implementation that maintains `play_order` itself is a fail even if its tests pass. ## Server discipline - [ ] **G8 — Both operations run on the playback loop and nowhere else.** No queue mutation outside a `PlaybackCommand` arm; the queue lock is never held across an `await` (the pattern every existing arm follows). - [ ] **G9 — One broadcast site.** Both go out through `broadcast_queue`, so the persister and the update stream cannot drift (D12). A dedup that removed nothing broadcasts nothing. - [ ] **G10 — No panics on client input.** `SortQueue` with an unset or unknown enum value answers `InvalidArgument`; `DedupQueue` with a dropped result receiver logs and continues; an empty queue is a no-op for both. No `unwrap`/`expect`/indexing that a client can reach. - [ ] **G11 — Both RPCs are in the rights matrix as `QueueOwner`** (D1), and the pinned method-list test in `auth.rs` includes them. A new RPC must not be able to reach the wire unmapped. - [ ] **G12 — The dedup count crosses the boundary as a number.** No server-formatted human string on the wire or in a `MessageToUi` (D2, D15); wording belongs to each client. - [ ] **G13 — Nothing is logged that could carry a credential.** Sort logs the strategy, dedup logs a count. No track paths, no URLs — a queue entry's path can be a `/rss` item whose URL is a per-subscriber token. ## Clients - [ ] **G14 — No client computes an order.** No comparators, no duplicate-finding, and no `Remove`-derived dedup in `cbd-tui`, `cbd-web` or `cbd-cli` (A1, Option A rejected). Clients send the verb and render what comes back. - [ ] **G15 — The sort menu is strictly modal.** While it is open the bindings table is unreachable and an unclaimed key leaves it open rather than falling through — a stray `c` must not clear the queue while the user thinks they are picking a sort (D14). - [ ] **G16 — Both operations are reachable without a keyboard in the web client** (D16): toolbar buttons dispatching the same actions as `u` and `S`, and the strategy list exists once (`SORT_CHOICES`), driving both the overlay and the button. - [ ] **G17 — The TUI and web keymaps agree**: `u` = dedup, `S` = sort menu, and the same five menu letters with the same uppercase-is-descending rule. Where they differ, the difference is deliberate and documented. - [ ] **G18 — Feedback exists for both outcomes of a dedup.** `0` renders as plainly as `7` (D2/D15) — a dedup that found nothing must not look like a dropped keypress. - [ ] **G19 — The CLI strategy is a `ValueEnum`** (D17), so a typo is a parse error, the shell completes it, and the printed confirmation names the strategy in the same spelling the user typed. - [ ] **G20 — Every new binding is documented where the others are**: the TUI help modal (derived from `BINDINGS`, so automatic), the web help overlay table, `docs/src/clients/tui.md`, `docs/src/clients/cli.md`, and `docs/src/queue.md`'s queue-operation list. ## Documentation and honesty - [ ] **G21 — The proto documents the semantics at the wire.** What a duplicate is, which copy survives, that unknowns sort last, that `Reverse` ignores `descending`, and that `UNSPECIFIED` is refused. - [ ] **G22 — The known gaps are written down, not left to be discovered**: a captured copy and its streaming source are not duplicates (D3), a sort with shuffle on does not change what plays next (D10), and tracks still resolving land unsorted after the fact (D11). - [ ] **G23 — Public items have doc comments** stating intent and edge behaviour, in the register the surrounding code uses (why, not what). ## Tests Behaviour is pinned by these, all of which must pass: | Area | Test home | Pins | | --- | --- | --- | | Identity, survivors, permutations | `queue_order.rs` | D3, D4, D6–D9 | | Play-order invariants | `lib.rs` (`QueueManager`) | D5, D10, empty queues | | Wiring, broadcast, persistence | `playback.rs` | D2, D9, D12 | | Argument validation | `rpc.rs` | D6, the count | | Rights matrix | `auth.rs` (existing pinned test) | D1 | | Keys and modality | `cbd-tui/src/app/{bindings,sort,mod}.rs` | D14 | | Pane feedback | `cbd-tui/src/app/queue.rs` | D15 | | Command parsing | `cbd-cli/src/lib.rs` | D17 | | Keymap parity | `cbd-web/src/keymap.rs` | D16, D17 | The server rows live under `crabidy-server/src/`. Plus the repository's standing gates: `cargo clippy` clean, `cargo fmt`, no new `unwrap` on client-reachable paths, and the book building.