POST /api/archives/:id/captures now returns 202 Accepted immediately with
{ job_uid, status: "pending" }. The capture runs in a tokio::task::spawn_blocking
thread and updates the job status to running → completed/failed in the archive DB.
Core DB (database.rs):
- capture_jobs table: id, job_uid, archive_id, run_uid, status, error_text, created_at, updated_at
- create_capture_job, update_capture_job_status, get_capture_job, fail_stalled_capture_jobs
Core archive (archive.rs):
- CaptureJobSummary type + get_capture_job query
Server (routes.rs):
- capture_handler: creates job, spawns background capture, returns 202 immediately
- GET /api/archives/:id/capture_jobs/:job_uid: poll job status
- main.rs: on startup, marks any 'running' jobs from previous session as 'failed'
Frontend:
- api.js: submitCapture returns job object; pollCaptureJob added
- CaptureDialog.jsx: polls every 500ms after submit, shows 'Running…' state,
handles completed (close + refresh) and failed (show error), cleans up interval
163 tests green. Frontend builds cleanly.
- capture.rs: add archive_id: Option<&str> to perform_capture; when Some,
call font_extractor::extract_and_rewrite before hashing HTML, register
each font as a deduplicated blob + 'font' artifact
- main.rs: pass None as archive_id (CLI keeps fonts embedded)
- routes.rs: add GET /api/archives/:id/blobs/:sha256 (serve_blob handler),
pass Some(&archive_id) to perform_capture in capture_handler,
add ApiError::internal constructor
- fix(hash): hash raw bytes instead of lossy UTF-8; add hash_bytes
- feat(database): add get_blob_by_sha256 lookup
- feat(font_extractor): extract embedded font data-URIs from archived HTML
- --user-agent: realistic Chrome UA so servers don't block headless string
- --browser-args=[--disable-web-security, --user-data-dir]: lets single-file
inline fonts from any cross-origin CDN (e.g. fonts.gstatic.com) regardless
of ACAO headers; user-data-dir required for --disable-web-security to take
effect in newer Chromium builds (otherwise silently ignored)
Font fidelity:
- --browser-wait-delay=2000: Cloudflare Fonts injects @font-face CSS after
HTML parse; the font hook needs extra time to see it after networkidle2
- --remove-unused-fonts=false: preserve @font-face rules even when fonts
haven't rendered yet (font-display:swap, off-screen text)
- --remove-alternative-fonts=false: preserve unicode-range subsets instead
of stripping them as 'alternatives'
ES module viewer error fix:
- Write a user script (sf-strip-scripts.js) that listens for
single-file-on-before-capture-start and removes all <script> elements
(except application/ld+json) from the live DOM before serialization.
Scripts still execute during capture for CSS fidelity; none end up in
the saved file, so no data:-URL base ES module resolution errors.
Defaults that were destroying CSS fidelity:
- --remove-unused-styles=true: strips CSS nesting rules (site uses & selector)
and any rule targeting JS-applied classes
- --remove-alternative-medias=true: deletes @media blocks that don't match
the capture viewport, breaking responsive layout
- --block-scripts=true: prevents JS from applying classes before CSS snapshot
All three now set to false.