The summarization runs server-side, but the SPA held the job only in
component state and jobs lived only in memory — so navigating away from
the Book Summarizer reset the UI (losing the handle to a still-running
job) and past summaries vanished on restart.
- PdfJobDb: SQLite store (its own file under ARCHIVE_DIR) persisting
every job on each progress update.
- JobStore: createdAt, persist-on-update, viewOf()/list() that serve
live jobs first then fall back to the durable record; non-live jobs
whose worker died surface as a new "interrupted" status.
- routes: GET /v1/pdf-jobs (newest-first list); GET /:id + summary.md
serve past jobs via viewOf; /:id/events sends a single terminal frame
for a finished/past job (no hang) and live frames otherwise.
- SPA: RecentJobs list (open any job → reconnect to live progress or
show result), localStorage resume of the active job on return, and a
"back to list (keeps running)" affordance.
- mainnet buyer compose: persistent named volume + ARCHIVE_DIR so the
archive + pdf-jobs.db survive container recreation/redeploys.
Tests: persistence-across-restart, interrupted detection, list, and
past-job serving (incl. terminal SSE). Full buyer suite green (185).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>