Implement: Customizable Mobile Reading Experience #25

Merged
crisleo94 merged 5 commits from feature/customizable-mobile-reading-experience into main 2026-05-29 13:48:06 +00:00
Owner

Summary

Full customizable mobile reading experience with reading view, font/themes settings, table of contents, and orientation support.

Backend Changes

  • Chapter model (apps/books/models.py) — stores chapter title, number, and content per book with unique constraint on (book, number)
  • ReadingProgress model (apps/books/models.py) — tracks user's current chapter, position, and percentage per book
  • ReadingSettings model (apps/reader/models.py) — OneToOne with User, stores font_family, font_size, line_height, margin_width, colors, brightness, orientation_lock, theme
  • New apps/reader app — serializers with validation, GET/PUT/PATCH view for settings auto-creation
  • BookViewSet additions/api/books/{id}/chapters/ (TOC), /api/books/{id}/chapters/{number}/ (content), /api/books/{id}/progress/ (GET/PUT)
  • URL registered at /api/reader/settings/

Frontend Changes

  • ReadingPage — full-screen reading view with chapter content, swipe/keyboard navigation
  • TableOfContents — slide-in left drawer listing all chapters with current highlighted
  • ReadingSettingsPanel — slide-in right drawer with Theme presets (Sepia/Dark/Light/Paper), Font family, Font size slider, Line height slider, Margin slider, Brightness slider, Orientation lock
  • useReadingSettings hook — fetches, applies CSS custom properties, persists changes
  • useChapters hook — fetches TOC, manages current chapter, provides next/prev navigation
  • useReadingProgress hook — fetches/saves progress with debounced auto-save
  • reader.css — 400+ lines of mobile-first styles, custom properties for theming, responsive layout
  • BookDetail now has a "Start Reading" button

Docs

docs/001-customizable-mobile-reading-experience.md — full spec with models, API contracts, component tree, types, theme presets


Run migrations after merge: python manage.py makemigrations && python manage.py migrate

## Summary Full customizable mobile reading experience with reading view, font/themes settings, table of contents, and orientation support. ## Backend Changes - **Chapter model** (`apps/books/models.py`) — stores chapter title, number, and content per book with unique constraint on (book, number) - **ReadingProgress model** (`apps/books/models.py`) — tracks user's current chapter, position, and percentage per book - **ReadingSettings model** (`apps/reader/models.py`) — OneToOne with User, stores font_family, font_size, line_height, margin_width, colors, brightness, orientation_lock, theme - **New `apps/reader` app** — serializers with validation, GET/PUT/PATCH view for settings auto-creation - **BookViewSet additions** — `/api/books/{id}/chapters/` (TOC), `/api/books/{id}/chapters/{number}/` (content), `/api/books/{id}/progress/` (GET/PUT) - URL registered at `/api/reader/settings/` ## Frontend Changes - **ReadingPage** — full-screen reading view with chapter content, swipe/keyboard navigation - **TableOfContents** — slide-in left drawer listing all chapters with current highlighted - **ReadingSettingsPanel** — slide-in right drawer with Theme presets (Sepia/Dark/Light/Paper), Font family, Font size slider, Line height slider, Margin slider, Brightness slider, Orientation lock - **useReadingSettings** hook — fetches, applies CSS custom properties, persists changes - **useChapters** hook — fetches TOC, manages current chapter, provides next/prev navigation - **useReadingProgress** hook — fetches/saves progress with debounced auto-save - **reader.css** — 400+ lines of mobile-first styles, custom properties for theming, responsive layout - BookDetail now has a "Start Reading" button ## Docs `docs/001-customizable-mobile-reading-experience.md` — full spec with models, API contracts, component tree, types, theme presets --- **Run migrations after merge:** `python manage.py makemigrations && python manage.py migrate`
marko added 1 commit 2026-05-29 02:56:52 +00:00
- Backend: Chapter, ReadingProgress, ReadingSettings models
- Backend: Chapter API (TOC + content), progress tracking, settings CRUD
- Frontend: ReadingPage with chapter navigation
- Frontend: TableOfContents drawer
- Frontend: ReadingSettingsPanel (theme, font, size, orientation)
- Frontend: Custom hooks for settings, chapters, progress tracking
- CSS: Mobile-first reading view with sepia/dark/light/paper themes
- Route: /reader/:bookId reading view from book detail page
- Docs: 001-customizable-mobile-reading-experience.md
marko added 1 commit 2026-05-29 05:53:30 +00:00
- Backend: Replace PR's Book-only models with main's full models
  (EBook, BookChapter, DownloadRecord, ReadingProgress, ReadingSettings)
- Backend: Add reader app (ReadingSettings model, serializer, view, URL)
- Frontend: Port reader feature files from web/ to frontend/ (api, hooks,
  components/reader/, pages/ReadingPage, types/reader, reader.css)
- Frontend: Add /read/:id route to App.tsx for chapter-based reading view
- All imports adjusted for frontend/src directory structure
marko added 1 commit 2026-05-29 05:59:29 +00:00
Resolve merge conflicts:
- backend/apps/books/: Keep main's models (EBook, BookChapter, etc.)
- frontend/src/App.tsx: Keep /read/:id route + main's all routes
- web/ files: Accept deletion (content ported to frontend/)
Owner

Reid's Review — PR #25

Verdict: 🔴 Changes Required


🔴 Blocking Issues

These MUST be resolved before this PR can be merged.

  • [File: backend/apps/reader/models.py] Missing migrations for new model
    The ReadingSettings model was added but no migration file was included. Django requires migrations to be committed alongside model changes. Run python manage.py makemigrations apps.reader and include the generated migration.

  • [File: backend/apps/books/models.py] Missing Chapter and ReadingProgress models
    The spec documents Chapter and ReadingProgress models that should exist under apps.books, but no changes to this file are in the diff. The frontend API client (frontend/src/api/reader.ts) and web/src/api/reader.ts call /api/books/${bookId}/chapters/ and /api/books/${bookId}/progress/ endpoints that will fail without these models.

  • [File: backend/apps/books/views.py | backend/apps/books/urls.py] Missing chapter and progress endpoints
    The frontend API client calls:

    • GET /api/books/{id}/chapters/ (list TOC)
    • GET /api/books/{id}/chapters/{number}/ (chapter content)
    • GET /api/books/{id}/progress/ (get progress)
    • PUT /api/books/{id}/progress/ (update progress)

    None of these endpoints exist in the backend diff. Without these, the reading view cannot function.

  • [File: frontend/src/pages/ReadingPage.tsx | web/src/pages/ReadingPage.tsx] XSS vulnerability — dangerouslySetInnerHTML without sanitization
    The code uses dangerouslySetInnerHTML={{ __html: currentChapter.content }} to render chapter content. This is a potential XSS vulnerability if the content contains user-supplied or untrusted HTML. Either:

    1. Sanitize the HTML with a library like DOMPurify before rendering, or
    2. Configure a Content Security Policy header on the backend to mitigate XSS

🟡 Suggestions (Non-Blocking)

These are recommendations for improvement. Not required for merge.

  • [File: frontend/src/reader.css | web/src/reader.css] Typo in media query
    Line 52: @media (orientation: landascape) should be @media (orientation: landscape). The typo means landscape orientation styles will never apply.

  • [File: frontend/src/App.tsx] Duplicate ReadingPage definition
    Line 13 defines ReadingPage lazy-loaded from ReadingPage export, but there's already a ReaderPage imported from pages/Reader. This creates confusion between two similarly-named components. Consider renaming or consolidating.

  • [File: frontend/src/pages/ReadingPage.tsx] Unused book state variable
    The book state is fetched via booksApi.getBook(bookId) but the result is never used (the component works with settings and chapters from hooks). The book.title reference on line 78 will crash if book is null while other data has loaded.


📋 AC Coverage

Based on docs/001-customizable-mobile-reading-experience.md

  • Chapter model — Spec requires apps.books.models.Chapter but not implemented
  • ReadingProgress model — Spec requires apps.books.models.ReadingProgress but not implemented
  • ReadingSettings model — Implemented in apps/reader/models.py
  • Chapters endpoint/api/books/{id}/chapters/ missing from backend
  • Chapter content endpoint/api/books/{id}/chapters/{number}/ missing from backend
  • Reading progress endpoints/api/books/{id}/progress/ missing from backend
  • Reader settings endpoints — /api/reader/settings/ implemented correctly
  • Frontend ReadingPage — Implemented with chapter navigation
  • TableOfContents component — Implemented
  • ReadingSettingsPanel component — Implemented
  • useReadingSettings hook — Implemented
  • useChapters hook — Implemented
  • useReadingProgress hook — Implemented
## Reid's Review — PR #25 **Verdict:** 🔴 Changes Required --- ### 🔴 Blocking Issues > These MUST be resolved before this PR can be merged. - **[File: backend/apps/reader/models.py]** Missing migrations for new model The `ReadingSettings` model was added but no migration file was included. Django requires migrations to be committed alongside model changes. Run `python manage.py makemigrations apps.reader` and include the generated migration. - **[File: backend/apps/books/models.py]** Missing Chapter and ReadingProgress models The spec documents `Chapter` and `ReadingProgress` models that should exist under `apps.books`, but no changes to this file are in the diff. The frontend API client (`frontend/src/api/reader.ts`) and `web/src/api/reader.ts` call `/api/books/${bookId}/chapters/` and `/api/books/${bookId}/progress/` endpoints that will fail without these models. - **[File: backend/apps/books/views.py | backend/apps/books/urls.py]** Missing chapter and progress endpoints The frontend API client calls: - `GET /api/books/{id}/chapters/` (list TOC) - `GET /api/books/{id}/chapters/{number}/` (chapter content) - `GET /api/books/{id}/progress/` (get progress) - `PUT /api/books/{id}/progress/` (update progress) None of these endpoints exist in the backend diff. Without these, the reading view cannot function. - **[File: frontend/src/pages/ReadingPage.tsx | web/src/pages/ReadingPage.tsx]** XSS vulnerability — dangerouslySetInnerHTML without sanitization The code uses `dangerouslySetInnerHTML={{ __html: currentChapter.content }}` to render chapter content. This is a potential XSS vulnerability if the content contains user-supplied or untrusted HTML. Either: 1. Sanitize the HTML with a library like DOMPurify before rendering, or 2. Configure a Content Security Policy header on the backend to mitigate XSS --- ### 🟡 Suggestions (Non-Blocking) > These are recommendations for improvement. Not required for merge. - **[File: frontend/src/reader.css | web/src/reader.css]** Typo in media query Line 52: `@media (orientation: landascape)` should be `@media (orientation: landscape)`. The typo means landscape orientation styles will never apply. - **[File: frontend/src/App.tsx]** Duplicate ReadingPage definition Line 13 defines `ReadingPage` lazy-loaded from `ReadingPage` export, but there's already a `ReaderPage` imported from `pages/Reader`. This creates confusion between two similarly-named components. Consider renaming or consolidating. - **[File: frontend/src/pages/ReadingPage.tsx]** Unused `book` state variable The `book` state is fetched via `booksApi.getBook(bookId)` but the result is never used (the component works with `settings` and `chapters` from hooks). The `book.title` reference on line 78 will crash if `book` is null while other data has loaded. --- ### 📋 AC Coverage > Based on docs/001-customizable-mobile-reading-experience.md - [ ] **Chapter model** — Spec requires `apps.books.models.Chapter` but not implemented - [ ] **ReadingProgress model** — Spec requires `apps.books.models.ReadingProgress` but not implemented - [x] ReadingSettings model — Implemented in `apps/reader/models.py` - [ ] **Chapters endpoint** — `/api/books/{id}/chapters/` missing from backend - [ ] **Chapter content endpoint** — `/api/books/{id}/chapters/{number}/` missing from backend - [ ] **Reading progress endpoints** — `/api/books/{id}/progress/` missing from backend - [x] Reader settings endpoints — `/api/reader/settings/` implemented correctly - [x] Frontend ReadingPage — Implemented with chapter navigation - [x] TableOfContents component — Implemented - [x] ReadingSettingsPanel component — Implemented - [x] useReadingSettings hook — Implemented - [x] useChapters hook — Implemented - [x] useReadingProgress hook — Implemented
marko added 1 commit 2026-05-29 06:38:10 +00:00
🔴 Blocking fixes:
- Add reader app migration (apps/reader/migrations/0001_initial.py)
- Remove duplicate ReadingSettings from apps.books to resolve model clash
  with apps.reader.ReadingSettings (keep richer reader version)
- Redirect books serializers/views to use reader app's ReadingSettings
- Update frontend API endpoints to match main's backend routes:
  /api/books/ebooks/{id}/toc/ (chapters)
  /api/books/ebooks/{id}/content/?page=N (chapter content)
  /api/books/ebooks/{id}/progress/ (GET/PATCH progress)
- Add DOMPurify sanitization for dangerouslySetInnerHTML (XSS fix)

🟡 Non-blocking fixes:
- Fix CSS typo: landascape -> landscape in reader.css
- Add null-safe fallback for book.title in ReadingPage
Owner

Reid's Review — PR #25 (Updated)

Verdict: 🔴 Changes Required


🔴 Blocking Issues

These MUST be resolved before this PR can be merged.

  • [File: web/src/pages/ReadingPage.tsx] XSS vulnerability — dangerouslySetInnerHTML without sanitization
    The code uses dangerouslySetInnerHTML={{ __html: currentChapter.content }} without sanitizing the content. The frontend version (frontend/src/pages/ReadingPage.tsx) correctly added DOMPurify, but this web/ version does not. Either add DOMPurify sanitization or configure CSP headers on the backend.

  • [File: web/src/reader.css] Typo in media query
    Line ~52: @media (orientation: landascape) should be @media (orientation: landscape). The typo means landscape orientation styles will never apply on the web version.

  • [File: web/src/api/reader.ts] API endpoint mismatch
    The API client calls /api/books/${bookId}/chapters/ and /api/books/${bookId}/progress/ but the existing EBookViewSet endpoints use /api/books/ebooks/${bookId}/toc/ and /api/books/ebooks/${bookId}/progress/. Either:

    1. Update the web/api/reader.ts to use the correct endpoints matching the backend, or
    2. Add new endpoints to EBookViewSet with the /chapters/ and /progress/ paths

🟡 Suggestions (Non-Blocking)

These are recommendations for improvement. Not required for merge.

  • [File: frontend/src/pages/ReadingPage.tsx] Unused book state variable
    The book state is fetched via booksApi.getBook(bookId) but only book?.title is used in the toolbar. Consider whether the book fetch is necessary or if title can come from another source.

  • [File: frontend/src/App.tsx | web/src/App.tsx] Duplicate ReadingPage/ReaderPage components
    Two similar components exist (ReaderPage and ReadingPage). The code comments reference both /reader/:id and /read/:id routes. Consider consolidating or clarifying the naming.


Fixed Issues

Issues from first review that have been addressed.

  • ReadingSettings model — Now has migrations included
  • BookChapter model — Added to apps/books/models.py
  • ReadingProgress model fields — Added device_id, device_name, version
  • XSS vulnerability in frontend — DOMPurify sanitization added
  • CSS typo in frontend — Fixed to landscape
  • Backend endpoints — toc and content actions exist in EBookViewSet

📋 AC Coverage

Based on docs/001-customizable-mobile-reading-experience.md

  • ReadingSettings model — Implemented in apps/reader/models.py with all spec fields
  • Chapters endpoint — EBookViewSet.toc exists (GET /api/books/ebooks/{id}/toc/)
  • Chapter content endpoint — EBookViewSet.content exists (GET /api/books/ebooks/{id}/content/?page=N)
  • Reading progress endpoints — EBookViewSet.progress exists (GET/PUT /api/books/ebooks/{id}/progress/)
  • Reading settings endpoints — /api/reader/settings/ implemented correctly
  • Frontend ReadingPage — Implemented with DOMPurify sanitization
  • TableOfContents component — Implemented
  • ReadingSettingsPanel component — Implemented
  • useReadingSettings hook — Implemented
  • useChapters hook — Implemented with API mapping
  • useReadingProgress hook — Implemented with API mapping
## Reid's Review — PR #25 (Updated) **Verdict:** 🔴 Changes Required --- ### 🔴 Blocking Issues > These MUST be resolved before this PR can be merged. - **[File: web/src/pages/ReadingPage.tsx]** XSS vulnerability — dangerouslySetInnerHTML without sanitization The code uses `dangerouslySetInnerHTML={{ __html: currentChapter.content }}` without sanitizing the content. The frontend version (`frontend/src/pages/ReadingPage.tsx`) correctly added DOMPurify, but this `web/` version does not. Either add DOMPurify sanitization or configure CSP headers on the backend. - **[File: web/src/reader.css]** Typo in media query Line ~52: `@media (orientation: landascape)` should be `@media (orientation: landscape)`. The typo means landscape orientation styles will never apply on the web version. - **[File: web/src/api/reader.ts]** API endpoint mismatch The API client calls `/api/books/${bookId}/chapters/` and `/api/books/${bookId}/progress/` but the existing EBookViewSet endpoints use `/api/books/ebooks/${bookId}/toc/` and `/api/books/ebooks/${bookId}/progress/`. Either: 1. Update the web/api/reader.ts to use the correct endpoints matching the backend, or 2. Add new endpoints to EBookViewSet with the `/chapters/` and `/progress/` paths --- ### 🟡 Suggestions (Non-Blocking) > These are recommendations for improvement. Not required for merge. - **[File: frontend/src/pages/ReadingPage.tsx]** Unused `book` state variable The `book` state is fetched via `booksApi.getBook(bookId)` but only `book?.title` is used in the toolbar. Consider whether the book fetch is necessary or if title can come from another source. - **[File: frontend/src/App.tsx | web/src/App.tsx]** Duplicate ReadingPage/ReaderPage components Two similar components exist (`ReaderPage` and `ReadingPage`). The code comments reference both `/reader/:id` and `/read/:id` routes. Consider consolidating or clarifying the naming. --- ### ✅ Fixed Issues > Issues from first review that have been addressed. - [x] ReadingSettings model — Now has migrations included - [x] BookChapter model — Added to apps/books/models.py - [x] ReadingProgress model fields — Added device_id, device_name, version - [x] XSS vulnerability in frontend — DOMPurify sanitization added - [x] CSS typo in frontend — Fixed to `landscape` - [x] Backend endpoints — toc and content actions exist in EBookViewSet --- ### 📋 AC Coverage > Based on docs/001-customizable-mobile-reading-experience.md - [x] ReadingSettings model — Implemented in `apps/reader/models.py` with all spec fields - [x] Chapters endpoint — EBookViewSet.toc exists (GET /api/books/ebooks/{id}/toc/) - [x] Chapter content endpoint — EBookViewSet.content exists (GET /api/books/ebooks/{id}/content/?page=N) - [x] Reading progress endpoints — EBookViewSet.progress exists (GET/PUT /api/books/ebooks/{id}/progress/) - [x] Reading settings endpoints — `/api/reader/settings/` implemented correctly - [x] Frontend ReadingPage — Implemented with DOMPurify sanitization - [x] TableOfContents component — Implemented - [x] ReadingSettingsPanel component — Implemented - [x] useReadingSettings hook — Implemented - [x] useChapters hook — Implemented with API mapping - [x] useReadingProgress hook — Implemented with API mapping
marko added 1 commit 2026-05-29 07:04:56 +00:00
Reid's second review flagged that the old web/ files still had:
- XSS vulnerability (no DOMPurify)
- CSS typo (landascape)
- API endpoint mismatches

All fix: delete web/ entirely. The correct, sanitized, fixed versions
already live in frontend/src/.
crisleo94 approved these changes 2026-05-29 13:48:02 +00:00
crisleo94 merged commit 730c748f5f into main 2026-05-29 13:48:06 +00:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No labels
3 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: HermesFactory/cloud-reader#25