- Convert: warn when title+author already exists in library (preload check) - Library: Duplicates sidebar section with grouped view and live counter - Fix: Duplicates view cover loading now uses same canvas/two-pass pattern as renderBooksGrid - Docs: add TODO-PERF-library-load.md with four identified bottlenecks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2.6 KiB
TODO: Performance improvements — Library "All Books" load speed
Observed: loading all books (especially on hard refresh CTRL+F5) is slow. Root cause analysis identified four bottlenecks, ordered by impact.
1. Lazy cover loading (HIGH impact)
Problem: On every full render, a <img> is created immediately for every book card that has a cover. With 500+ books this fires 500+ simultaneous HTTP requests to /library/cover-cached/. The browser throttles these and the server gets hammered.
Fix: Use an IntersectionObserver to defer image loading until a card scrolls into the viewport. Cards outside the viewport stay as canvas placeholders until they appear.
Affected: static/library.js — renderBooksGrid() second pass (lines ~981–1005)
2. HTTP caching on /api/library (HIGH impact)
Problem: Every refresh (including soft refresh) fetches the full book list JSON from the server with no caching. No ETag, Last-Modified or Cache-Control headers are set.
Fix: Add an ETag header based on a hash of the serialized response (or a DB row count + last updated_at). Browser sends If-None-Match; server returns 304 Not Modified when nothing changed — zero data transfer.
Affected: routers/library.py — GET /api/library endpoint; static/library.js — loadLibrary()
3. Double DOM pass in renderBooksGrid (MEDIUM impact)
Problem: renderBooksGrid makes two full iterations over the book list:
- Build all card HTML and append to the grid.
- Query the DOM again for each
canvas-*andwrap-*element to set up image loading.
This causes two reflows and 500+ getElementById calls after the DOM is already populated.
Fix: Combine both passes into one: build the card element, immediately set up the <img> element and canvas, then append. No second iteration needed.
Affected: static/library.js — renderBooksGrid() (lines ~904–1005)
4. Tags via separate query + Python merge (LOW impact)
Problem: list_library_json() fetches book tags via a separate SELECT * FROM book_tags query and then merges them in Python using a dict. For large libraries this means two round-trips and an O(n) in-process merge.
Fix: Use a PostgreSQL JSON aggregation in the main query:
COALESCE(
json_agg(json_build_object('tag', bt.tag, 'tag_type', bt.tag_type))
FILTER (WHERE bt.tag IS NOT NULL),
'[]'
) AS tags
This returns tags inline per book row, eliminating the second query and the Python merge loop.
Affected: routers/common.py — list_library_json() (lines ~397–458)