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
- 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.
- 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.
- 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.
- Show a lightweight progress indicator (e.g. "Page 137 of 400 · 34%") in the toolbar.
- 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
What's Happening
The in-app PDF reader (
app/dashboard/library/read/[bookid]/BookReaderClient.jsx, built directly onpdfjs-distwith a canvas) works, but navigating any real book is painful:Array.from({ length: pageCount }).map(...)rendering aPage Npill for every page). A 400-page book renders 400 buttons in anoverflow-x-autorow — slow to scan, heavy to render, and effectively unusable for long texts.pageNumberstarts 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,pageCountstate, fit-width/zoom logic, and render cancellation via thecancelledflag.Where to Find This
app/dashboard/library/read/[bookid]/BookReaderClient.jsx— all reader UI/stateapp/dashboard/library/read/[bookid]/page.jsx— server wrapper fetching the bookapp/api/books/[bookId]/preview/route.js— the authenticated PDF stream the reader loadsWhat We Want
localStoragekeydnb:book-progress:<bookId>storing{ page, updatedAt }); on load, resume at the saved page (clamped topageCount) with a small "Resumed at page N — start over" affordance.canAccess), and the download button.Technical Context
pdfjs-dist@^3.11— the doc handle lives indocRef; page render effect re-runs on[pageNumber, baseScale, fitWidth, containerWidth, pageCount]. New navigation should just setpageNumberthrough the existing clamp.useTransition(isTransitioning) used to keep the UI responsive between pages.typeof window).lib/utils/cache.js(localCache) can be reused for storage if preferred.Acceptance Criteria
npm run lintandnpm run buildpass.