# 011 — Web reader (react-reader) **Status:** Implemented **Created:** 2026-06-03 ## Objective Replace the custom HTML chapter reader with [react-reader](https://github.com/gerhardsletten/react-reader) (epub.js) for paginated EPUB reading in the web app. Keep the existing toolbar and settings panel chrome. Block PDFs from the in-browser reader. ## Architecture ``` Library (EPUB only) → /read/:id → ReadingPage → GET /api/books/ebooks/{id}/ (metadata, format guard) → GET /api/books/ebooks/{id}/file/ (authenticated EPUB blob) → ReactReader (blob URL + CFI location) → PATCH /api/books/ebooks/{id}/progress/ (epub_location + percentage) ``` EPUB files are fetched with JWT via the API, converted to a blob URL client-side, and passed to react-reader. This avoids unauthenticated `/media/` URLs and CORS issues in dev. ## Backend changes ### `GET /api/books/ebooks/{id}/file/` - Authenticated, owner-only - Returns `FileResponse` with `Content-Type: application/epub+zip` - Returns `400` if format is not `epub` ### `ReadingProgress.epub_location` | Field | Type | Notes | |-------|------|-------| | `epub_location` | CharField(2048) | EPUB CFI string for resume position | Exposed on `GET/PATCH /api/books/ebooks/{id}/progress/` and in `EBookDetail.progress`. `current_position` stores whole-book percentage (0–100) derived from epub.js locations. ## Frontend changes | File | Change | |------|--------| | `pages/ReadingPage.tsx` | `ReactReader` replaces chapter HTML rendering | | `hooks/useEpubReader.ts` | Blob load, CFI state, debounced progress save | | `utils/epubRendition.ts` | Theme/font application via `getRendition` | | `api/books.ts` | `getEpubFile(id)` | | `pages/Library.tsx` | Block PDF open with notice | | `App.tsx` | Single route `/read/:id`; `/reader/:id` redirects | ### Removed (web-only) - `useChapters.ts`, `TableOfContents.tsx`, `Reader.tsx`, `useReadingProgress.ts` Backend `/toc/` and `/content/` endpoints remain for mobile/API consumers. ## EPUB-only policy - Library: clicking a PDF shows a dismissible notice; no navigation to reader - ReadingPage: deep-link guard if `format !== 'epub'` - Upload still accepts PDF for storage; web reader is EPUB-only ## Dependencies - Frontend: `react-reader` (^2.0.15) ## Verification 1. Open an EPUB from library → paginated reading, swipe/tap page turns, built-in TOC 2. Close and reopen → resumes at saved CFI 3. Change theme/font in settings → applies inside epub iframe 4. Click a PDF in library → notice shown, reader not opened 5. `GET /api/books/ebooks/{id}/file/` without auth → 401