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)
|
? _RealPlaylist(forYou)
|
||||||
: _PlaceholderPlaylist('For You', _variantFor('for-you', status)));
|
: _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
|
final songsLike = ownedAll
|
||||||
.where((p) => p.systemVariant == 'songs_like_artist')
|
.where((p) => p.systemVariant == 'songs_like_artist')
|
||||||
.take(3)
|
.take(3)
|
||||||
|
|||||||
@@ -39,6 +39,10 @@
|
|||||||
(systemPlaylistsQ.data?.owned ?? []).find((p) => p.system_variant === 'for_you') ?? null
|
(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(
|
const songsLikePlaylists = $derived(
|
||||||
(systemPlaylistsQ.data?.owned ?? [])
|
(systemPlaylistsQ.data?.owned ?? [])
|
||||||
.filter((p) => p.system_variant === 'songs_like_artist')
|
.filter((p) => p.system_variant === 'songs_like_artist')
|
||||||
@@ -48,7 +52,7 @@
|
|||||||
const userPlaylists = $derived(userPlaylistsQ.data?.owned ?? []);
|
const userPlaylists = $derived(userPlaylistsQ.data?.owned ?? []);
|
||||||
|
|
||||||
type PlaceholderVariant = 'building' | 'failed' | 'pending' | 'seed-needed';
|
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;
|
const s = systemStatusQ.data;
|
||||||
if (!s) return 'pending';
|
if (!s) return 'pending';
|
||||||
if (s.in_flight) return 'building';
|
if (s.in_flight) return 'building';
|
||||||
@@ -71,7 +75,16 @@
|
|||||||
out.push({ kind: 'placeholder', label: 'For You', variant: placeholderVariant('for-you') });
|
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++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
if (songsLikePlaylists[i]) {
|
if (songsLikePlaylists[i]) {
|
||||||
out.push({ kind: 'real', playlist: songsLikePlaylists[i] });
|
out.push({ kind: 'real', playlist: songsLikePlaylists[i] });
|
||||||
|
|||||||
Reference in New Issue
Block a user