Work in progress. This README covers features and design intent. Setup documentation is forthcoming.
Lectio is a self-hosted, local-first RSS reader with a focus on fast reading triage. It runs as a single-user server behind a TLS proxy and is designed to be deployed on a personal VPS.
A three-pane desktop RSS reader (folder tree → post list → article pane). Built on Python + FastAPI + the reader library, with a plain-HTML/JS frontend — no build step, no bundler, no framework.
The design priority is speed of triage: quickly marking things read, surfacing what matters, and staying out of the way.
| Dark mode | Light mode |
|---|---|
![]() |
![]() |
More shots (settings, automation, feed properties, tags, history, admin) are in the Screenshots wiki page.
Full detail lives in the wiki — Features and Multi-user & APIs. The short version:
- Fast triage — three-pane reader, keyboard nav, context menus, bulk mark-as-read, manual tags, read history, search, and a Readability/web-view proxy.
- Rich content — embeds that actually render (curated trusted-host allowlist), inline podcast players (incl. audio borrowed from a separate host feed), file attachments, recovered YouTube embeds, and bare-text feed cleanup. Reader view re-injects allowlisted players (YouTube/Spotify/Bandcamp) that the readability extractor would otherwise strip, and de-duplicates a repeated lead image.
- Lead images — per-feed extraction strategies with side-by-side comparison, smart crop/fit tuning, caption sourcing, junk-image rejection, inline-SVG art, and full-resolution webcomic panels (ComicControl thumb→full promotion).
- Automation — highlight, mark-as-read, deduplicate, email-article, and outbound-webhook rules (generic JSON or IFTTT Maker; SSRF-guarded); all fire at refresh time with a manual "Run Now".
- Feed management — OPML, RSS/Atom auto-discovery, Page Feeds, YouTube & DeviantArt sync, per-folder cadence, feed compare, fetch-history & automations tabs, and duplicate-feed scanning.
- Reliability — conditional GET, per-feed/domain backoff, GUID-churn suppression, WebSub real-time push, WAL-mode SQLite, and browser-identity fetch fallback for feeds whose servers refuse the default client.
- Optional multi-user — isolated per-user databases with shared content caches; GReader and Fever API compatibility; Instapaper & email integrations.
- Data portability — Takeout-style ZIP export/import and online-safe backups.
| Layer | What it does |
|---|---|
main.py |
FastAPI routes, Jinja2 templates, all request handling |
services/ |
Feed refresh, lead images, email, starred archive, YouTube, reader API wrapper |
reader library |
Feed fetching, parsing, storage, ETag/conditional requests |
lectio.db |
reader's SQLite feed+entry store |
lectio_meta.sqlite3 |
App state: prefs, automation rules, lead images, read history, failure tracking |
lectio_meta.sqlite |
Starred/saved entry archive |
- Backend: Python 3.14, FastAPI, uvicorn
- Feed library: reader (handles HTTP, parsing, ETags, scheduling)
- Frontend: Vanilla JS, Jinja2 templates, no build step
- Database: SQLite (WAL mode) × 3
- Deployment: Docker + docker-compose, Traefik reverse proxy
- Tests — pytest suite (unit, services, integration, scripts) under
tests/. Run withuv run pytest. - CI — GitHub Actions runs the suite on Python 3.14 for every pull request and push to
main(.github/workflows/ci.yml). Dependencies install from the lockeduv.lock(uv sync --frozen), and the run treats anyDeprecationWarningas an error so they surface immediately rather than accumulating. - Dependency audit —
uv audit(OSV-backed) scans the locked dependencies for known vulnerabilities and deprecated packages. Run it locally withmake audit; CI runs the same scan. It's a uv preview feature, so it's kept separate frommake testlocally and the CI step is informational (non-blocking) for now.
Active personal use. Not yet documented for general deployment. The codebase moves fast — APIs, DB schema, and config format may change without notice.
Issues and PRs welcome, but this is primarily a personal project.

