Investigation: the contents drawer read book.toc, which makePDF
builds from pdf.getOutline() — verified against the real library PDF
(Head First SQL) through the exact vendored pdf.js build AND the exact
range transport the browser uses: 18 chapter entries come back. So
the source is right; manga-scan PDFs and CBZs simply have no embedded
outline, which made Contents look broken exactly where users expect
page-based navigation.
- TOC now populates eagerly right after the book opens (toggle-time
lazy population removed), so an existing outline can never silently
miss due to timing; the drawer keeps the honest empty-state text
for books without outlines.
- New 'Pages' tab in the contents drawer for fixed-layout books:
a Kavita-style thumbnail grid (3-up, current page highlighted and
scrolled into view, click to jump — recorded on the back-to-
location stack). Thumbnails render client-side: PDFs via the
in-memory pdf.js document (small viewport render, Alpine.raw
unwrap); comics via the page's image blob drawn down to a 110px
canvas, then unloading the full-size blob so thumbnailling doesn't
hoard page images. Lazy via IntersectionObserver scoped to the
drawer's scroll container (200px margin), canvases cached at module
level so revisits are instant; failures warn in console and allow
retry. The backend /readers/thumbnails endpoint turned out to be an
empty stub, so nothing server-side was worth wiring.