feat: genre mixes — a daily playlist per genre you listen to, on a "Your genres" Home row (#6904)
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m47s
release / govulncheck (push) Successful in 25s
release / integration (push) Failing after 4m38s
release / android (push) Successful in 4m3s
release / Build signed APK (push) Successful in 4m34s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped

A new system playlist kind, genre_mix: up to 10 "{Genre} mix" playlists per
user, rebuilt daily with the other system mixes.

Which genres: library genres with at least 25 playable tracks across 3
artists, ranked in tiers — taste-profile genres first (matched
case-insensitively, so ID3 "Post-Punk" and folksonomy "post-punk" are one),
then genres the user has played or liked, then the rest of the library, which
only tops a short shelf up to 4. A genre whose artist set is a near
duplicate of one already picked (Jaccard >= 0.5) is skipped, so "Lo-Fi" and
"Lo-Fi Hip Hop" don't both appear but "Alternative Rock" survives "Rock". A
genre that can't fill 15 tracks gives way to the next.

Each mix is 50 tracks: 3 in 5 the user already knows (played through or
liked), the rest unheard in that genre, interleaved, with the usual
per-album/per-artist caps. Tracks skipped twice and more often than played
are left out. The order changes daily and holds within a day. Picks are
stamped taste / taste_unheard, and genre_mix plays count toward rotation and
the recommendation metrics.

Migration 0102 adds genre_mix to the playlists variant constraints.

Web and Android each get a "Your genres" row in name order, hidden until a
mix exists. Kinds with several playlists per user now play by playlist id
wherever the client used to test seed_artist_id (web card, detail page
self-heal), keyed on the server's refreshable flag; on Android genre_mix
joins songs_like_artist as not refreshable. The web detail page's
"mix was rebuilt" reload now finds a Songs-like or genre mix by name instead
of calling the by-kind shuffle, which those kinds don't have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 22:30:41 -05:00
co-authored by Claude Opus 5.5
parent 477f453100
commit dbaa8b7d57
21 changed files with 1239 additions and 23 deletions
+3 -2
View File
@@ -531,8 +531,8 @@ type systemPlaylistKind struct {
// Discover, the discovery mixes) and so can be addressed by kind:
// the generic /system/{kind}/{refresh,shuffle} endpoints + the
// per-tile refresh affordance only apply to these. Non-singleton
// kinds (songs_like_artist → up to 3 per user) are played by
// playlist id and have no by-kind endpoint.
// kinds (songs_like_artist, genre_mix — several per user) are played
// by playlist id and have no by-kind endpoint.
Singleton bool
Produce systemPlaylistProducer
}
@@ -563,6 +563,7 @@ var systemPlaylistRegistry = func() []systemPlaylistKind {
{Key: "for_you", Singleton: true, Produce: produceForYou},
{Key: "songs_like_artist", Singleton: false, Produce: produceSeedMixes},
{Key: "discover", Singleton: true, Produce: produceDiscover},
{Key: "genre_mix", Singleton: false, Produce: produceGenreMixes},
}
// The five discovery mixes share one Produce closure factory keyed
// by a per-mix spec; spec list + factory live in system_mixes.go.