feat(home): Songs-like → dedicated row + wider spread (#1491)
test-go / test (push) Successful in 33s
test-web / test (push) Successful in 43s
android / Build + lint + test (push) Failing after 1m29s
test-go / integration (push) Successful in 4m42s

Promote the best-performing surface ("Songs like {artist}", ~8% skip /
~86% completion) out of the shared Playlists carousel into its own Home
row on both Android and web, and widen the daily build from 3 to 6 mixes
so the dedicated row shows a wider spread.

Server (internal/playlists):
- PickSeedArtists candidate pool 5 → 12; pickSeedArtistsForDay now takes
  songsLikeSeedCount (6) instead of a hardcoded 3. Graceful degradation
  and daily rotation preserved.

Android (HomeScreen.kt):
- New songsLikeSection + buildSongsLikeRow; PlaylistsRow takes a title so
  it renders both the "Playlists" and "Songs like…" rows. buildOnlineRow
  / orderedRealPlaylists no longer reserve the 3 songs-like slots.
  Offline shows cached mixes (available-first), hides the row when none.

Web (+page.svelte):
- Dedicated "Songs like…" row from songsLikeRow; dropped the 3-slot cap
  and removed songs-like from the Playlists carousel.

Tests: seed_selection_test.go, BuildPlaylistsRowTest.kt, page.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 20:41:08 -04:00
parent cb0af5efd3
commit c1d143cf4a
8 changed files with 288 additions and 87 deletions
+5 -4
View File
@@ -350,7 +350,7 @@ SELECT c.artist_id,
FROM chosen c
LEFT JOIN liked l ON l.artist_id = c.artist_id
ORDER BY score DESC, c.artist_id
LIMIT 5
LIMIT 12
`
type PickSeedArtistsRow struct {
@@ -359,7 +359,7 @@ type PickSeedArtistsRow struct {
Tier int32
}
// Top-5 most-engaged distinct artist candidates, tiered so the
// Top-12 most-engaged distinct artist candidates, tiered so the
// "Songs like X" mixes never silently vanish (#1255): the old hard
// 7-day window emptied the seed pool after a quiet week, and the
// daily atomic-replace build then deleted every existing mix until
@@ -375,8 +375,9 @@ type PickSeedArtistsRow struct {
// rule-#131 pick-kind ladder and stamps the built tracks, so metrics
// can compare mixes seeded from fresh vs stale engagement.
// Score = unskipped-play count + 5 if user has liked the artist. The
// Go-side picker (pickSeedArtistsForDay) shuffles the 5
// daily-deterministically and takes 3 so the mix set rotates.
// Go-side picker (pickSeedArtistsForDay) shuffles the 12
// daily-deterministically and takes songsLikeSeedCount (6) so the mix
// set both rotates day-to-day and fills the dedicated Home row (#1491).
func (q *Queries) PickSeedArtists(ctx context.Context, userID pgtype.UUID) ([]PickSeedArtistsRow, error) {
rows, err := q.db.Query(ctx, pickSeedArtists, userID)
if err != nil {