Release: web UX overhaul + Android native port + server polish #59

Merged
bvandeusen merged 298 commits from dev into main 2026-06-01 16:11:21 -04:00
2 changed files with 39 additions and 5 deletions
Showing only changes of commit bf61d6cfa3 - Show all commits
@@ -709,10 +709,18 @@ enum class OfflinePoolKind(val label: String) {
/**
* Builds the Home Playlists row. When [offline], the two cache-backed
* pool cards (Recently played, Liked) lead the row — mirrors Flutter's
* `_buildPlaylistsRow`. Then For You + Discover + 3× Songs-like fixed
* slots (real card when generated, placeholder otherwise), then any
* user-owned playlists.
* pool cards (Recently played, Liked) lead the row. Then For You +
* Discover + 3× Songs-like fixed slots (real card when generated,
* placeholder otherwise), then the secondary system kinds (deep cuts /
* rediscover / new for you / on this day / first listens) when they
* exist — no placeholders for these since they're conditional on
* library shape, not guaranteed singletons. Finally user-owned
* playlists.
*
* Diverges from Flutter (`flutter_client/lib/library/home_screen.dart`
* `_buildPlaylistsRow`) which only shows the 5 fixed slots and never
* surfaces the secondary kinds on Home. Operator authorized the
* divergence on 2026-06-01; web UI catch-up tracked as task #53.
*/
private fun buildPlaylistsRow(
owned: List<PlaylistRef>,
@@ -736,6 +744,15 @@ private fun buildPlaylistsRow(
?.let { PlaylistRowItem.Real(it) }
?: PlaylistRowItem.Placeholder("Songs like…", variantFor("songs-like", status))
}
// Secondary system kinds in server-registry order. Only included
// when actually generated — these depend on library shape (Deep
// cuts needs deep albums, On this day needs prior history, etc.)
// so a missing one means "not enough data" rather than "still
// building".
for (variant in SECONDARY_SYSTEM_VARIANTS) {
owned.firstOrNull { it.systemVariant == variant }
?.let { out += PlaylistRowItem.Real(it) }
}
owned.filter { it.systemVariant == null }.forEach { out += PlaylistRowItem.Real(it) }
return out
}
@@ -749,6 +766,19 @@ private fun variantFor(slot: String, s: SystemPlaylistsStatus): String = when {
private const val SONGS_LIKE_SLOTS = 3
/**
* The 5 system playlist kinds the server generates that aren't pinned
* to Home's primary slots. Order matches `systemPlaylistRegistry` in
* `internal/playlists/system.go`.
*/
private val SECONDARY_SYSTEM_VARIANTS = listOf(
"deep_cuts",
"rediscover",
"new_for_you",
"on_this_day",
"first_listens",
)
@Composable
private fun ArtistsRow(
title: String,
@@ -159,7 +159,11 @@ private fun systemLabelFor(variant: String?): String = when (variant) {
"for_you" -> "For You"
"discover" -> "Discover"
"songs_like_artist" -> "Songs like…"
"todays_mix" -> "Today's mix"
"deep_cuts" -> "Deep cuts"
"rediscover" -> "Rediscover"
"new_for_you" -> "New for you"
"on_this_day" -> "On this day"
"first_listens" -> "First listens"
null -> ""
else -> variant.replace('_', ' ').replaceFirstChar { it.uppercase() }
}