* v3 library: artist aliases + Tidy-up merge UI — P4
Fixes the "ACDC vs AC/DC" split without touching a single file or row:
a never-purged artist_alias table (raw_name -> canonical_name) applied
at DISPLAY time. The scanner keeps writing whatever the pack says; one
alias row fixes every matching song.
- query_artists dedupes/groups/orders on the effective artist, with a
zero-cost fast path when no aliases exist; the artist filter expands
a canonical name to its raw variants (index-friendly, keyset-safe);
query_page re-labels row artists through the alias map.
- CRUD + merge API: list aliases, list raw artists (variants + counts
for the picker), set/merge/remove; a self-alias clears (= un-merge).
- "Tidy up artists..." in the filter drawer (local library only): a
searchable raw-variant checklist, merge-into-canonical, and a
current-merges list with per-row un-merge. The artist dropdown + tree
pick up canonical names with no dropdown code changes.
- Sort + A-Z rail stay on the RAW artist (keyset-safe): a cross-letter
alias shows its canonical label but buckets under the raw letter
until effective columns are materialized (the grouping engine's
work_key already resolves aliases when this table exists, so merged
artists group correctly there).
11 tests. tailwind.min.css regenerated (generated file - on a merge
conflict, re-run scripts/build-tailwind.sh).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nm7tHs1Yvjjtnnu4nzJgdN
* fix(v3): flatten transitive artist-alias chains + cycle guard so sequential merges unify (PR #705 review)
merge_artists looped set_artist_alias which stored one hop, so sequential
merges (ACDC->AC/DC then AC/DC->AC-DC) left a two-hop chain that the
single-hop effective_artist/grouping/filtering split into two groups. Add
_single_hop_canonical + _terminal_canonical (visited-set cycle break),
resolve the canonical to its terminal before storing, forward-flatten
existing rows that pointed at the raw name, and reject cycles (409). Batch
merge now runs under one lock + one commit for atomicity.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>