feat(home): add a Continue-reading shelf with new-chapter badges

Surface a horizontal "Continue reading" shelf at the top of the homepage,
fed by the user's read-progress history. Each card jumps back to the exact
page they left off (deep-linking `?page=N` past the first) and flags how
many chapters have landed since — the personal new_chapters_count from the
read-progress list. The shelf is fetched after the catalogue load so the
public browse path stays unauthenticated; guests get an empty list (401
swallowed) and the shelf stays hidden.

Add new_chapters_count to the frontend ReadProgressSummary type to match
the backend. Component unit tests cover href/deep-link, badge visibility,
and the no-chapter fallback; e2e covers signed-in (shelf + badge) and
anonymous (hidden).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-04 20:57:08 +02:00
parent f318c3bf51
commit 2267a83f6c
10 changed files with 355 additions and 5 deletions

View File

@@ -22,7 +22,12 @@
sortLabel as composeSortLabel,
sortUrlParams
} from '$lib/mangaSort';
import {
listMyReadProgressOrEmpty,
type ReadProgressSummary
} from '$lib/api/read_progress';
import Chip from '$lib/components/Chip.svelte';
import ContinueReadingShelf from '$lib/components/ContinueReadingShelf.svelte';
import MangaCard from '$lib/components/MangaCard.svelte';
import Pager from '$lib/components/Pager.svelte';
import SegmentedControl from '$lib/components/SegmentedControl.svelte';
@@ -37,6 +42,7 @@
const PAGE_SIZE = 50;
let mangas: MangaCardData[] = $state([]);
let continueEntries = $state<ReadProgressSummary[]>([]);
let search = $state('');
let sort = $state<MangaSort>(DEFAULT_SORT);
let order = $state<SortOrder>(defaultOrderFor(DEFAULT_SORT));
@@ -301,6 +307,16 @@
}
await hydrateFromUrl();
await load();
// Fetch the "Continue reading" shelf after the catalogue so the
// public browse path stays unauthenticated and unblocked. Returns
// empty for guests (401 swallowed), which hides the shelf.
try {
const progress = await listMyReadProgressOrEmpty();
continueEntries = progress.items;
} catch {
// Never let a history hiccup break the catalogue — leave the
// shelf hidden.
}
});
// Track viewport in a separate $effect so the listener cleans up on
@@ -455,6 +471,10 @@
</a>
</div>
{#if continueEntries.length > 0}
<ContinueReadingShelf entries={continueEntries} />
{/if}
<form
onsubmit={onSubmit}
action="javascript:void(0)"