feat(viewer): thumbnails drawn at load, kept on disk by hash; no picture crashes it
check / check (push) Canceled after 0s

Tiles draw a thumbnail, not the file: the top of the first page, 560
pixels wide (never scaled up) and at most 420 tall, rendered by two
workers of their own so opening a file never waits behind them. Every
file's thumbnail starts when the books load, before the documents page
is opened, and the live viewer keeps each as <sha>.png in
$XDG_CACHE_HOME (else ~/.cache)/wanderbooks/thumbs/v1-560 (private to
the user, written whole and renamed into place), so the next launch
reads small PNGs, not PDFs. A file's name is its content's hash, so a
thumbnail always matches the file the books hold; the blob is still read
through the verifying store each time, so a corrupt or missing file is
an error, never a kept picture.

From review: a picture or page past the device's largest texture is
scaled to fit (egui panicked on it, in the full view too); a renderer
panic is that file's error, not the end of its worker; a thumbnail asked
for before the cache moved is dropped, never shown over a newer one;
text thumbnails keep 2 KiB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-23 09:58:25 -06:00
1 parent 7cc08a0039
commit a9de57d6cd
4 files changed
+454 -36

No files matched your search

+19
View File
@@ -163,6 +163,7 @@ impl App {
);
let mut app = Self::bare(&cc.egui_ctx, scheme);
app.filter = filter;
app.receipts.thumb_dir = crate::receipts::thumb_dir();
app.loader = Some(Loader::spawn(source, cc.egui_ctx.clone()));
app
}
@@ -170,6 +171,7 @@ impl App {
/// An app showing fixed books (tests and screenshots).
pub fn with_books(ctx: &egui::Context, books: Books, dark: bool, start: Route) -> Self {
let mut app = Self::bare(ctx, schemes::own(dark));
app.receipts.prefetch(&books);
app.books = Some(Arc::new(books));
app.nav = Nav::new(start);
app
@@ -369,6 +371,22 @@ impl App {
}
}
/// Keep thumbnails on disk in `dir` (the live viewer keeps them in
/// the user's cache directory, `wanderbooks/thumbs`); tests point it
/// at their own.
pub fn keep_thumbs_in(&mut self, dir: std::path::PathBuf) {
self.receipts.thumb_dir = Some(dir);
self.receipts.forget_thumbs();
if let Some(b) = self.books.clone() {
self.receipts.prefetch(&b);
}
}
/// The size in pixels of a file's thumbnail, once drawn (tests).
pub fn thumb_size(&self, sha: &str) -> Option<[usize; 2]> {
self.receipts.thumb_size(sha)
}
/// Block until every receipt on screen has rendered (screenshots).
pub fn settle(&mut self, ctx: &egui::Context) {
self.receipts.settle(ctx);
@@ -1622,6 +1640,7 @@ impl eframe::App for App {
if self.books.is_some() {
self.updated_at = ui.input(|i| i.time);
}
self.receipts.prefetch(&b);
self.books = Some(b);
self.error = None;
}