feat(home): surface Discover system playlist as a tile (web + flutter)
Server has been generating system_variant='discover' playlists since M6a (internal/playlists/system.go and POST /api/playlists/system/discover/refresh) but neither client surfaced it. The Flutter home filtered system playlists by exact match on 'for_you' and 'songs_like_artist', dropping Discover. The web home did the same. Tiles were generated server-side and silently discarded by both clients. Add a Discover slot to the playlists row in both: - Flutter: 5-slot row now (For You, Discover, 3× Songs-like) with matching placeholder state machine. - Web: same shape, same placeholderVariant() call. When the engine has built it, the real playlist tile renders; otherwise a placeholder with the same building/pending/failed status semantics as the other system tiles.
This commit is contained in:
@@ -135,7 +135,16 @@ List<_PlaylistRowItem> _buildPlaylistsRow(
|
||||
? _RealPlaylist(forYou)
|
||||
: _PlaceholderPlaylist('For You', _variantFor('for-you', status)));
|
||||
|
||||
// Slots 2-4: Songs-like (real first, padded to 3).
|
||||
// Slot 2: Discover. Server emits this with system_variant='discover'
|
||||
// when the recommendation engine has eligible tracks; before then,
|
||||
// show a placeholder in the same "building/pending" state machine
|
||||
// as For-You.
|
||||
final discover = findFirst((p) => p.systemVariant == 'discover');
|
||||
out.add(discover != null
|
||||
? _RealPlaylist(discover)
|
||||
: _PlaceholderPlaylist('Discover', _variantFor('discover', status)));
|
||||
|
||||
// Slots 3-5: Songs-like (real first, padded to 3).
|
||||
final songsLike = ownedAll
|
||||
.where((p) => p.systemVariant == 'songs_like_artist')
|
||||
.take(3)
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
(systemPlaylistsQ.data?.owned ?? []).find((p) => p.system_variant === 'for_you') ?? null
|
||||
);
|
||||
|
||||
const discoverPlaylist = $derived(
|
||||
(systemPlaylistsQ.data?.owned ?? []).find((p) => p.system_variant === 'discover') ?? null
|
||||
);
|
||||
|
||||
const songsLikePlaylists = $derived(
|
||||
(systemPlaylistsQ.data?.owned ?? [])
|
||||
.filter((p) => p.system_variant === 'songs_like_artist')
|
||||
@@ -48,7 +52,7 @@
|
||||
const userPlaylists = $derived(userPlaylistsQ.data?.owned ?? []);
|
||||
|
||||
type PlaceholderVariant = 'building' | 'failed' | 'pending' | 'seed-needed';
|
||||
function placeholderVariant(slot: 'for-you' | 'songs-like'): PlaceholderVariant {
|
||||
function placeholderVariant(slot: 'for-you' | 'discover' | 'songs-like'): PlaceholderVariant {
|
||||
const s = systemStatusQ.data;
|
||||
if (!s) return 'pending';
|
||||
if (s.in_flight) return 'building';
|
||||
@@ -71,7 +75,16 @@
|
||||
out.push({ kind: 'placeholder', label: 'For You', variant: placeholderVariant('for-you') });
|
||||
}
|
||||
|
||||
// Slots 2-4: Songs-like (real first, padded with placeholders).
|
||||
// Slot 2: Discover (real or placeholder). Server emits this with
|
||||
// system_variant='discover' once the recommendation engine has
|
||||
// eligible tracks.
|
||||
if (discoverPlaylist) {
|
||||
out.push({ kind: 'real', playlist: discoverPlaylist });
|
||||
} else {
|
||||
out.push({ kind: 'placeholder', label: 'Discover', variant: placeholderVariant('discover') });
|
||||
}
|
||||
|
||||
// Slots 3-5: Songs-like (real first, padded with placeholders).
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (songsLikePlaylists[i]) {
|
||||
out.push({ kind: 'real', playlist: songsLikePlaylists[i] });
|
||||
|
||||
Reference in New Issue
Block a user