diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 5649a904..a6e0d6a7 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -49,6 +49,30 @@ .slice(0, 3) ); + // Secondary system kinds the server generates that don't get pinned + // to fixed Home slots: surfaced after the Songs-like slots when they + // exist, in server-registry order (internal/playlists/system.go). + // No placeholders for these — they depend on library shape (Deep + // cuts needs deep albums, On this day needs prior history, etc.) so + // a missing one means "not enough data," not "still building." + // Mirrors Android's SECONDARY_SYSTEM_VARIANTS (HomeScreen.kt). Operator + // request 2026-06-01 for web parity. + const SECONDARY_SYSTEM_VARIANTS = [ + 'deep_cuts', + 'rediscover', + 'new_for_you', + 'on_this_day', + 'first_listens' + ] as const; + + const secondarySystemPlaylists = $derived( + SECONDARY_SYSTEM_VARIANTS + .map((variant) => + (systemPlaylistsQ.data?.owned ?? []).find((p) => p.system_variant === variant) + ) + .filter((p): p is Playlist => p != null) + ); + const userPlaylists = $derived(userPlaylistsQ.data?.owned ?? []); type PlaceholderVariant = 'building' | 'failed' | 'pending' | 'seed-needed'; @@ -93,6 +117,11 @@ } } + // Secondary system kinds in server-registry order, when generated. + for (const p of secondarySystemPlaylists) { + out.push({ kind: 'real', playlist: p }); + } + // User playlists trail (server returns most-recently-updated first). for (const p of userPlaylists) { out.push({ kind: 'real', playlist: p }); diff --git a/web/src/routes/page.test.ts b/web/src/routes/page.test.ts index a4d6b76f..51df7407 100644 --- a/web/src/routes/page.test.ts +++ b/web/src/routes/page.test.ts @@ -109,4 +109,42 @@ describe('home Playlists section', () => { // real For-You tile. expect(placeholders).toHaveLength(4); }); + + test('renders secondary system kinds (deep_cuts / new_for_you) after Songs-like slots', () => { + // Operator backflow 2026-06-01: web Home surfaces the 5 secondary + // system kinds when generated. No placeholders for them — they + // depend on library shape, so missing means "not enough data." + const makePlaylist = (id: string, variant: string, name: string): Playlist => ({ + id, + user_id: 'u1', + owner_username: 'u1', + name, + description: '', + is_public: false, + kind: 'system', + system_variant: variant, + refreshable: variant !== 'songs_like_artist', + seed_artist_id: null, + cover_url: '', + track_count: 25, + duration_sec: 1500, + created_at: '2026-05-04T00:00:00Z', + updated_at: '2026-05-04T00:00:00Z' + }); + const owned = [ + makePlaylist('fy', 'for_you', 'For You'), + makePlaylist('dc', 'deep_cuts', 'Deep cuts'), + makePlaylist('nfy', 'new_for_you', 'New for you') + ]; + (createPlaylistsQuery as unknown as ReturnType).mockImplementation( + (kind?: string) => + kind === 'system' + ? readable({ data: { owned, public: [] }, isPending: false, isError: false }) + : readable({ data: emptyPlaylistsResponse, isPending: false, isError: false }) + ); + render(Page); + expect(screen.getByText('For You')).toBeInTheDocument(); + expect(screen.getByText('Deep cuts')).toBeInTheDocument(); + expect(screen.getByText('New for you')).toBeInTheDocument(); + }); });