Confirmed against prod: exactly one track (17 plays, cold since May 21)
met the c>=5 + 30d-cold bar, and three process defects turned that into
a 1-track playlist instead of the locked placeholder.
- ListRediscoverTracks: collapse the two-tier UNION into one blended
pool. The old shallow-tier gate (WHERE NOT EXISTS deep) was
all-or-nothing — one 6-month row suppressed the entire 30-day tier —
and deep was a strict subset of shallow anyway. Eligibility drops to
>=3 non-skip plays (on a weeks-old history the >=5-play tracks are
precisely the ones still in rotation); ordering prefers >=6mo cold,
then >=5 plays, then raw count.
- Minimum viable mix floor for all five discovery mixes: below
minLen (15; 5 for the album-coherent NewForYou/FirstListens) the
variant is withheld so Home renders the 'listen more to unlock'
placeholder instead of a mix that reads as built-wrong.
- /api/events: clamp client-supplied 'at' to [user.created_at,
now+5m]. Unbounded client clocks could write arbitrarily old plays
and poison the 6-month ordering (prod data verified clean — no
scrub needed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsF3cNoKrqCYsU78cXC8U6
For-You silently vanished after ~7 days of not listening (seed query
required a non-skip play in the last 7 days) and capped at ~40 on
self-hosted libraries with no ListenBrainz similarity data.
- PickTopPlayedTracksForUser: tiered seed — last 30d top plays, else
all-time top plays, else liked tracks. For-You only disappears now
if the account has zero plays AND zero likes.
- produceForYou uses deeper candidate source limits (raised random/
tag/similar K) so it reaches ~100 even with empty lb_similar /
similar_artists; richer when LB enrichment is present.
- Rediscover: tiered — 6-month-dormant, else ≥5-play 30-day-dormant.
- On This Day: floor 60→30 days, window ±7→±10 doy; still skips
cleanly (no rows → no playlist) on insufficient history.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each is one candidate query + one registry entry; zero client work
(R2 made tiles/refresh/shuffle generic, all are singleton kinds so
web's server `refreshable` flag and Flutter's derived getter both
light up automatically).
- deep_cuts (#419): <=2-play tracks from liked / heavily-played
artists; diversity-capped.
- rediscover (#420): >=5-play tracks not heard in 6 months, by
historical affection.
- new_for_you (#421): tracks from albums added <=30d whose artist
the user likes/plays; album-coherent (no cap).
- on_this_day (#422): tracks played within ±7 day-of-year in prior
windows (>60d ago), weighted by play count.
- first_listens (#423): never-played albums, tiered liked-artist →
played-artist → rest; album-coherent.
system_mixes.go producers mirror the Discover model (SQL gives the
ranking; finishMix caps+truncates to 100 to match For-You/Discover
shuffle depth; album-coherent mixes skip the cap). Builder query
failure is non-fatal (logged, yields no playlist) like Discover.
Existing system_test existence checks are unaffected.
Closes the #411 system-playlists-v2 umbrella's new-types thread.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>