diff --git a/ui/src/routes/books/[slug]/chapters/+page.server.ts b/ui/src/routes/books/[slug]/chapters/+page.server.ts
new file mode 100644
index 0000000..9352791
--- /dev/null
+++ b/ui/src/routes/books/[slug]/chapters/+page.server.ts
@@ -0,0 +1,32 @@
+import { error } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
+import { getBook, listChapterIdx, getProgress } from '$lib/server/pocketbase';
+import { log } from '$lib/server/logger';
+
+export const load: PageServerLoad = async ({ params, locals }) => {
+ const { slug } = params;
+
+ const book = await getBook(slug).catch((e) => {
+ log.error('chapters', 'getBook failed', { slug, err: String(e) });
+ return null;
+ });
+
+ if (!book) error(404, `Book "${slug}" not found`);
+
+ let chapters, progress;
+ try {
+ [chapters, progress] = await Promise.all([
+ listChapterIdx(slug),
+ getProgress(locals.sessionId, slug, locals.user?.id)
+ ]);
+ } catch (e) {
+ log.error('chapters', 'failed to load chapters', { slug, err: String(e) });
+ throw error(500, 'Failed to load chapters');
+ }
+
+ return {
+ book: { slug: book.slug, title: book.title, cover: book.cover ?? '', totalChapters: book.total_chapters },
+ chapters,
+ lastChapter: progress?.chapter ?? null
+ };
+};
diff --git a/ui/src/routes/books/[slug]/chapters/+page.svelte b/ui/src/routes/books/[slug]/chapters/+page.svelte
new file mode 100644
index 0000000..89a8ed9
--- /dev/null
+++ b/ui/src/routes/books/[slug]/chapters/+page.svelte
@@ -0,0 +1,203 @@
+
+
+
No chapters match "{searchQuery}"
+ {:else} +No chapters available yet.
+ {/if} +{:else} + + {#if searchQuery} +{visibleChapters.length} result{visibleChapters.length === 1 ? '' : 's'}
+ {/if} + +