Skip to content

[Enhancement] Book reader: keyboard navigation, jump-to-page, and resume last-read position #86

Description

@zeemscript

What's Happening

The in-app PDF reader (app/dashboard/library/read/[bookid]/BookReaderClient.jsx, built directly on pdfjs-dist with a canvas) works, but navigating any real book is painful:

  • The only way to jump is a horizontal strip of one button per page (Array.from({ length: pageCount }).map(...) rendering a Page N pill for every page). A 400-page book renders 400 buttons in an overflow-x-auto row — slow to scan, heavy to render, and effectively unusable for long texts.
  • No keyboard navigation. Arrow keys, PageUp/PageDown, Home/End do nothing; the only paging affordances are two small arrow buttons.
  • No jump-to-page input — you cannot type "137".
  • Reading position is forgotten. pageNumber starts at 1 on every mount; closing the tab or navigating back to the library loses your place entirely, which matters a lot for an Islamic book library where readers return to long texts repeatedly.

The component already has clean primitives to build on: goToPage(delta) with clamping, pageCount state, fit-width/zoom logic, and render cancellation via the cancelled flag.

Where to Find This

  • app/dashboard/library/read/[bookid]/BookReaderClient.jsx — all reader UI/state
  • app/dashboard/library/read/[bookid]/page.jsx — server wrapper fetching the book
  • app/api/books/[bookId]/preview/route.js — the authenticated PDF stream the reader loads

What We Want

  1. Replace the per-page button strip with a compact pager: a numeric "Go to page" input (validated 1..pageCount) plus a range slider or prev/next — no O(pageCount) DOM nodes.
  2. Keyboard navigation while the reader is focused: ArrowLeft/ArrowRight (and PageUp/PageDown) for prev/next, Home/End for first/last. Don't hijack keys when the page-number input has focus.
  3. Persist last-read position per book (e.g. localStorage key dnb:book-progress:<bookId> storing { page, updatedAt }); on load, resume at the saved page (clamped to pageCount) with a small "Resumed at page N — start over" affordance.
  4. Show a lightweight progress indicator (e.g. "Page 137 of 400 · 34%") in the toolbar.
  5. Keep existing behavior intact: fit-width vs free zoom, devicePixelRatio-aware rendering, access gating (canAccess), and the download button.

Technical Context

  • pdfjs-dist@^3.11 — the doc handle lives in docRef; page render effect re-runs on [pageNumber, baseScale, fitWidth, containerWidth, pageCount]. New navigation should just set pageNumber through the existing clamp.
  • Wire keyboard listeners with cleanup, and mind the existing useTransition (isTransitioning) used to keep the UI responsive between pages.
  • localStorage writes should be debounced/throttled (writing on every page change is fine; just guard typeof window).
  • The generic cache helpers in lib/utils/cache.js (localCache) can be reused for storage if preferred.

Acceptance Criteria

  • Opening a 100+ page PDF renders no per-page button list; jumping to an arbitrary page via input works and clamps invalid values.
  • Arrow keys page through the document; typing in the page input does not trigger page flips.
  • Reopening a book resumes at the last-read page, with a visible way to go back to page 1.
  • Progress text/percentage updates as you read.
  • Zoom, fit-width, access gating, and download behave exactly as before.
  • npm run lint and npm run build pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    complexity:highMaps to Drips Wave High tier (200 pts)enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions