Ctrl K
ComunidadGuías…

README.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Lectio

[![CI](https://github.com/joshg253/Lectio/actions/workflows/ci.yml/badge.svg)](https://github.com/joshg253/Lectio/actions/workflows/ci.yml)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
![Python](https://img.shields.io/badge/python-3.15-3776AB?logo=python&logoColor=white)
![FastAPI](https://img.shields.io/badge/FastAPI-009688?logo=fastapi&logoColor=white)
![WebSub](https://img.shields.io/badge/realtime-WebSub-FF5700)
![Webhooks](https://img.shields.io/badge/automation-Webhooks-FF5700)
![GReader API](https://img.shields.io/badge/API-Google%20Reader-FF5700)
![Fever API](https://img.shields.io/badge/API-Fever-FF5700)
![Miniflux API](https://img.shields.io/badge/API-Miniflux%20v1-FF5700)
![Last commit](https://img.shields.io/github/last-commit/joshg253/Lectio)

> **Work in progress.** This README covers features and design intent. For setup, see [docs/deployment.md](docs/deployment.md).

Lectio is a self-hosted feed reader focused on fast reading triage, rich content handling, and automation. It runs well on a personal VPS with full multi-user support, and is built to keep feed reading fast, keyboard-friendly, and workflow-oriented.

---

## What it is

A self-hosted RSS reader with a triage-first interface that adapts from a three-pane desktop layout to narrower tablet and phone workflows. Built on Python + FastAPI + the [`reader`](https://github.com/lemon24/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.

---

## Screenshots

| Dark mode | Light mode |
|---|---|
| ![Dark mode](docs/screenshots/1dark.png) | ![Light mode](docs/screenshots/2light.png) |

| Read Mode (e-ink) | Saved articles |
|---|---|
| ![Read Mode](docs/screenshots/13readmode.png) | ![Saved](docs/screenshots/12saved.png) |

More shots — the phone layout, Save Article, settings, automation, feed
properties, tags, history and admin — are on the
**[Screenshots wiki page](https://github.com/joshg253/Lectio/wiki/Screenshots)**.
All of them are generated from synthetic demo data, never a real library.

---

## What it does

The full tour — every feature, organized by area — is on the
**[Features wiki page](https://github.com/joshg253/Lectio/wiki/Features)**. The
short version:

- **Triage first.** Three-pane reader, keyboard nav everywhere, context menus,
  bulk mark-as-read/unread that updates in place, per-view remembered sort, and
  a layout that collapses to two panes on a tablet and one on a phone. Checkbox
  multi-select on the post list drives bulk tag/star/playlist/move actions —
  see the [Features wiki page](https://github.com/joshg253/Lectio/wiki/Features)
  for the detail.
- **Picks up where you left off.** Close the tab, press Back once too often, or
  swipe the app away — reopening Lectio returns you to the same article, scrolled
  to the same place. It also installs to your home screen as a standalone app.
- **Built for a phone, not just shrunk onto one.** One pane at a time, with Back
  walking the view stack — article → feed → folder — and then toggling the folder
  drawer instead of closing the tab out from under you.
- **Tagging a post keeps it forever.** A tag triggers a full offline capture —
  page, readability text, every image, and any files the post links to — so a
  kept article survives the site going down. Stars are the to-do pile; tags are
  the keep pile.
- **Read-it-later built in.** Save any page (menu, bookmarklet, `/api/save`, or
  the browser extension) with no feed needed, then read it in **Read Mode**, an
  e-ink-friendly reading app at `/read` that works offline — including
  Archive, Delete and mark-read, which queue and drain when you reconnect.
- **Content that actually renders.** Feed sanitization is Lectio's own, so
  embeds survive: sandboxed players from a curated host allowlist, inline SVG
  and MathML, recovered YouTube embeds, podcast audio (even when it lives in a
  separate host feed), inline LaTeX math, and images kept at the author's
  layout and a sane size.
- **Filter this view.** Narrow the open list by text (`zelda "tears of" !sponsored`), video duration, or tag chips: `tag:games`,
  `tag:!games`, `tag:gam*`; `tagged:` matches tags you applied. Press Enter to pull in matches from further down the view as you scroll;
  it pairs with Select All for quick bulk triage.
- **Automation.** Highlight, mark-as-read, tag-filter, deduplicate, and send-to-destination rules (Instapaper, Pinterest, Reddit, Quire,
  email, YouTube playlists, webhooks) at any scope, with dry-run and run history. An optional auto-read age keeps a quiet site's first
  update from flooding the unread list. See [Features details](docs/wiki/Features-details.md).
- **Feed management that expects the real web.** Resilient auto-discovery,
  Page Feeds for feedless sites, dev.to and DeviantArt adapters (adding an
  artist Watches them; on a Watch-feed byline, the name filters to that
  artist and the ✕ Unwatches), conditional GET, per-feed and per-domain backoff, GUID-churn suppression, feed compare,
  duplicate scanning, unsubscribe that keeps your curation, and an optional
  outbound proxy with FlareSolverr/last-resort-backend escalation for feeds
  behind an anti-bot challenge. A Utilities tab groups the clean-up tools, including one that removes duplicate and unwatchable videos
  from your YouTube playlists.
- **Fix a post in place.** Edit its date, title or URL; clean up its body with
  an Aardvark-style element remover; re-fetch its content — undoably, with an
  Internet Archive fallback — one post, a multi-selection, or a whole feed or folder at a time.
  Teaser-only feeds can fetch the full article automatically as new posts
  arrive, and feeds whose RSS lacks real tags can pick up the publisher's own
  article topics as tag suggestions — each switched on per folder or per feed.
- **Yours to run.** Isolated per-user databases with shared content caches,
  Google Reader / Fever / Miniflux v1 APIs, WebSub push, Takeout-style
  export/import, and no build step: plain HTML and JS, no bundler, no framework.

---

## Technical overview

| 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 |
| Per-user SQLite DBs | Feed/entry store, app state (prefs, rules, read history), starred archive; layout in [tenancy](docs/architecture/tenancy.md) |

Dates, saved-article recovery, per-feed file capture, suggested tags, comic
episode expansion, transparent-image handling, the refresh watchdog and the
page-weight work are all described on the
**[Features wiki page](https://github.com/joshg253/Lectio/wiki/Features)** —
they are behaviour, not architecture, and this file is meant to stay short.
Design rationale for any of it lives in
[ARCHITECTURE.md](ARCHITECTURE.md).

---

## Stack

- **Backend**: Python 3.15, FastAPI, uvicorn
- **Feed library**: [reader](https://github.com/lemon24/reader) (handles HTTP, parsing, ETags, scheduling)
- **Frontend**: Vanilla JS, Jinja2 templates, no build step
- **Database**: SQLite (WAL mode), per user
- **Deployment**: Docker + docker-compose, Traefik reverse proxy

---

## Development

Enable the lint hook once per clone:

```bash
git config core.hooksPath .githooks
```

It runs `scripts/lint_changed.py` over the lines you staged (ruff, ~40ms) so a
lint problem is fixed in the commit rather than after a CI round-trip. CI runs
the same script across the whole PR. Both are scoped to *changed lines* rather
than whole files, because the repo carries a backlog of existing findings — new
code is held to the rules while the backlog shrinks file by file. `git commit
--no-verify` bypasses it.

- **Tests** — `make test` (or `uv run pytest`).
- **Rebuilding the container** — `make rebuild`; the image bakes the source in, so a rebuild is what makes a commit live.

CI, dependency audit, CodeQL, scratch cleanup and backups: [docs/development.md](docs/development.md).

---

## Status

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.