Four findings from a day's cbd.log and a 121-entry queue.
**Dedup by title.** The provider-item identity removed nothing from that
queue — 121 entries, 121 distinct ids — while three "Sink Into The Hips"
sat in it: two remixes and the album version, 171/189/228s. Those are
different recordings and merging them by default would silently discard a
version the user chose, so the default stands and `DedupQueue` gains a
`by_title` flag: lowercased (artist, title), survivor = the playing entry
else the *longest* take. Own key everywhere (`U`, `queue dedup --titles`)
because it throws recordings away. Duration-tolerant matching was the
third option and is not worth it: every same-title group in that queue
differed by tens of seconds, so a safe tolerance caught nothing.
**stderr no longer points at the terminal.** tracing goes to a file
because the TUI owns the screen, but fd 2 did not — and in the bundled
`cbd` the ALSA C library shares the process, so its "underrun occurred"
printed straight onto the interface, scrolled the terminal a line and
left the layout looking shifted (the queue appearing to bleed into the
now-playing pane; ratatui repaints only changed cells, so it persisted).
The message was lost too. One dup2 before the alternate screen sends fd 2
to `<log dir>/cbd.stderr.log`, so those diagnostics are kept instead.
**The spectrum flapped ~1/s during playback**, 3664 times in one log.
tokio's default MissedTickBehavior::Burst keeps the absolute schedule, so
once the per-tick FFT lateness reaches a whole period two ticks fire back
to back and the second necessarily sees no new frames — read as silence,
which zeroed the bars. Now `Delay`, plus a FlowDetector that wants two
consecutive empty ticks before declaring idle.
**The one ERROR in the log was a shutdown race**, mislabelled: "request
to server failed: sending on a closed channel" was the orchestrator's
send to the UI channel after the UI thread exited. It now reports the UI
closing at info and stops the loop instead of spinning on a stream nobody
reads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two verbs that rewrite what the queue already holds instead of adding to
it: DedupQueue and SortQueue, on the playback loop, for every client.
Client-side was the tempting shortcut and the wrong one — a
client-computed Remove races the resolve stream, and "sorting" as a
Replace of paths re-resolves everything through providers and restarts
playback at the head.
A duplicate is the same provider item id *scoped to its provider* (the
content store keys content the same way, so unscoped ids let two
providers' numeric ids collide), or the whole path when a provider
reports no id. Deliberately not artist+title: identical metadata is
routinely a different recording, and a wrong merge is unrecoverable
queue state. Within a group the playing entry survives, else the
earliest — "keep the first" stops the music whenever the playing copy is
a later one — and the removal itself goes through remove_tracks, so one
code path maintains play_order.
Sorting permutes `tracks` and then treats the play order by mode: with
shuffle off it rebuilds it as the identity with the cursor on the current
track (the sort decides what plays next), with shuffle on it remaps it
through the permutation so the shuffled sequence and the position in it
survive. Five stable strategies (artist→album, album, title, duration,
reverse), keys built once per track, unknown sorting last in both
directions, and UNSPECIFIED refused as InvalidArgument.
DedupQueue answers with a count, alone among the queue verbs: 0 cannot be
recovered from the new snapshot and is what a user needs to hear. It
crosses as a number — the wording is each client's. TUI: `u` and a modal
`S` sort menu whose unclaimed keys are swallowed, plus the count in the
queue pane title. Web: the same keys, clickable menu rows, toolbar
buttons, count in a toast. CLI: `queue dedup` and `queue sort <key>
[--desc]` with the strategy as a ValueEnum.
Full dev-flow: architecture/queue-order.md, quality/queue-order.md,
plan/queue-order.md, plan/summary.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>