Jump the Review list to an artist by letter

An A–Z/# bar above the Songs and Albums lists jumps to the first artist
starting with that letter. Songs come from a new /letters endpoint giving
each leading character's offset; the song list now sorts case-insensitively
like the album list so each letter stays together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-10-01 14:28:27 -07:00
1 parent 095e294f4b
commit 00147aaa9e
5 files changed
+160 -16

No files matched your search

+34 -1
View File
@@ -432,7 +432,7 @@ func (s *Store) Matches(ctx context.Context, pairID int64, f MatchFilter) ([]Mat
f.Limit = 100
}
rows, err := s.DB.QueryContext(ctx, `SELECT `+matchCols+` FROM matches WHERE `+w+
` ORDER BY rel_slave LIMIT ? OFFSET ?`, append(args, f.Limit, f.Offset)...)
` ORDER BY rel_slave COLLATE NOCASE, rel_slave LIMIT ? OFFSET ?`, append(args, f.Limit, f.Offset)...)
if err != nil {
return nil, 0, err
}
@@ -448,6 +448,39 @@ func (s *Store) Matches(ctx context.Context, pairID int64, f MatchFilter) ([]Mat
return out, total, rows.Err()
}
// LetterStart is where the paths beginning with one (lowercased) character
// start in the Matches listing of a filter.
type LetterStart struct {
Letter string `json:"letter"`
Offset int `json:"offset"`
}
// MatchLetters returns, in listing order, the offset of the first match for
// each leading character of rel_slave, for jumping to an artist by letter.
// Grouping on lower() of the first character keeps each group contiguous
// under the NOCASE order Matches uses.
func (s *Store) MatchLetters(ctx context.Context, pairID int64, f MatchFilter) ([]LetterStart, error) {
w, args := matchWhere(pairID, f)
rows, err := s.DB.QueryContext(ctx, `SELECT lower(substr(rel_slave, 1, 1)) AS l, COUNT(*) FROM matches WHERE `+w+
` GROUP BY l ORDER BY l`, args...)
if err != nil {
return nil, err
}
defer rows.Close()
out := []LetterStart{}
offset := 0
for rows.Next() {
var l string
var n int
if err := rows.Scan(&l, &n); err != nil {
return nil, err
}
out = append(out, LetterStart{Letter: l, Offset: offset})
offset += n
}
return out, rows.Err()
}
// ExportMatches calls fn for every match of the filter, ordered by slave
// path. Rows are read in pages so a slow download never holds the database
// connection (scans need it) for longer than one page.