feat(home): add a "Recommended for you" shelf

Surface the content-based /me/recommendations feed as a horizontal shelf on
the homepage, below the Continue-reading shelf. Fetched after the catalogue
(same non-blocking pattern), shown only when signed in and the feed is
non-empty; reuses MangaCard in a fixed-width scroller. Component unit test +
e2e (signed-in shows cards, anonymous hides).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-05 17:46:33 +02:00
parent d6a109df2d
commit 19db66a845
9 changed files with 185 additions and 5 deletions

View File

@@ -27,8 +27,10 @@
type ReadProgressSummary
} from '$lib/api/read_progress';
import { isCaughtUp } from '$lib/continueReading';
import { listMyRecommendations } from '$lib/api/recommendations';
import Chip from '$lib/components/Chip.svelte';
import ContinueReadingShelf from '$lib/components/ContinueReadingShelf.svelte';
import RecommendationShelf from '$lib/components/RecommendationShelf.svelte';
import MangaCard from '$lib/components/MangaCard.svelte';
import Pager from '$lib/components/Pager.svelte';
import SegmentedControl from '$lib/components/SegmentedControl.svelte';
@@ -44,6 +46,7 @@
let mangas: MangaCardData[] = $state([]);
let continueEntries = $state<ReadProgressSummary[]>([]);
let recommendations = $state<MangaCardData[]>([]);
let search = $state('');
let sort = $state<MangaSort>(DEFAULT_SORT);
let order = $state<SortOrder>(defaultOrderFor(DEFAULT_SORT));
@@ -320,6 +323,13 @@
// Never let a history hiccup break the catalogue — leave the
// shelf hidden.
}
try {
// Personal "Recommended for you" feed (empty for guests / no
// taste signals yet → shelf hidden).
recommendations = await listMyRecommendations();
} catch {
// Non-critical — leave the shelf hidden on failure.
}
});
// Track viewport in a separate $effect so the listener cleans up on
@@ -478,6 +488,10 @@
<ContinueReadingShelf entries={continueEntries} />
{/if}
{#if recommendations.length > 0}
<RecommendationShelf mangas={recommendations} />
{/if}
<form
onsubmit={onSubmit}
action="javascript:void(0)"