7473e98d91
The five discovery-mix producers (Deep Cuts, Rediscover, New for you, On this day, First listens) were near-identical boilerplate that differed only in (a) which SQL query they ran and (b) whether to diversity-cap the result. Folded into one produceDiscoveryMix(spec) factory + a per-mix discoveryMixSpec slice. The registry composes the factory over the spec list so adding a new mix is one struct literal + a SQL query, never a new func. Also fixes the user-reported bug that several mixes 'show the same content from yesterday'. Audit of the SQL queries: - Deep Cuts: ORDER BY md5(t.id::text || $2::text) → day-keyed - On this day: ORDER BY w.c DESC, md5(...) → day-keyed - Rediscover: ORDER BY tier, c DESC, id → invariant - New for you: ORDER BY al.created_at DESC, disc, track → invariant - First listens: ORDER BY tier, al.id, disc, track → invariant The three invariant ones produced identical content day-over-day. The unified spec carries a dailyRotate bool: when set, the producer applies a daily-deterministic offset rotate-left of the candidate pool BEFORE diversify+truncate. Rotation (not shuffle) preserves contiguous-block ordering inside each day's slice — matters for First listens which is album-coherent. Set on Rediscover + First listens (where same-content-every-day is clearly a bug). Left off New for you because 'newest album first regardless of day' is the intended UX for that surface — daily rotation there would feel wrong. Daily rotation seed: rand.New(NewSource(int64(userIDHash(userID, dateStr)))) — same primitive used by For-You's pickHeadAndTail sampling so behavior is consistent across the system playlist family. No test file referenced the deleted produceXxx functions directly, only the registry, so this is a closed refactor.