# 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
