About Tech Feed Reader
A self-hosted RSS reader that ranks, summarizes, and triages your feed for you — built single-user-first.
Why this exists
Most people who read news online spend an hour a day swivel-chairing — Hacker News, then The Verge, then Lobsters, then five team-specific blogs, then a podcast app, then back to the top because "did I check that one yet?". The platforms call this engagement. It's just lost time.
Algorithmic feeds (Twitter/X, Reddit, TikTok) solve part of the swivel-chair problem by aggregating, but they swap one cost for another: they decide what you see, optimised for the platform's engagement metrics, not yours. Your data leaves your machine. Your attention is sold.
Tech Feed Reader is the third option: aggregate everything you chose to follow, in your order, on your machine. Use AI to do the boring work — summarising, ranking, triaging — but never to decide what's important. That stays with you.
The anti-swivel-chair argument
The default RSS-reader UX (Feedly, NewsBlur) shows you every unread item in chronological order. With 50 feeds, that's a 200-item list at 8am every day. You either:
- Mark it all as read (the firehose problem)
- Skim every title and click into 10–15% (the time problem)
- Just stop using it (most people)
This app's answer:
- Personal ranking. The For You sort scores every article against your bookmarks, thumbs-ups, and active reading time. Sort by relevance, not chronology.
- AI triage. Once a night Claude reads your unread queue plus a sample of your positive + negative corpus and emits three groups: must-read, optional, skip. With rationales.
- Summaries inline. Every article gets an extractive summary at import time. One click adds a Claude summary cached on the row. Skim mode hides the noise.
- Skip the swipe. Bulk-action checkboxes; mute rules for keywords / authors / feeds; archive what you'll never get to.
How it works
- Ingest: a 10-minute cron pulls every subscribed feed via Ruby's
FeedParser; HN/Lobsters teasers fall back to a readability fetch. - Storage: SQLite with FTS5 for full-text search, WAL mode for concurrent reads. One file at
data/app.db. No external services required. - Ranking: a simple cosine-overlap score against your positive/negative corpus, plus a recency factor and per-feed weight that adapts from your engagement.
- Summaries: extractive (TextRank) at import, optional Claude (Sonnet 4.6) on demand. Cached so re-visits don't re-spend tokens.
- Triage: Claude reads up to 30 unread + 20 corpus exemplars per topic. Three runs nightly (cross-topic, technology-only, sports-only). Browse history at /triage.
- Sports: ESPN's unofficial API supplies scores, standings, fixtures, tennis rankings. Calendar exports as
.icsfor Apple/Google Calendar subscription. - Audio: a Hotwire Turbo +
data-turbo-permanentmini-player at the bottom of every page survives navigation; podcast episodes share the same article queue with a 🎧 badge.
What's different
- Single-user-first. No signup, no accounts, no auth boundary. The whole app trusts whoever opens it. (Multi-user is on the roadmap, but the simplicity comes first.)
- No analytics, no tracking, no third-party JS. The only outbound calls are the feeds you subscribed to + Claude's API for the summaries you opt into.
- Your data on your disk. Bookmark a backup of
data/app.dband you're portable. Even the rotating background images are yours to swap. - AI as a tool, not the master. Claude summarises and triages. It doesn't decide what you read, what's "for you," or what gets boosted.
Tech stack
- Ruby 3.4.1 / Sinatra / ERB / RSpec
- SQLite (FTS5, WAL mode)
- Anthropic Claude Sonnet 4.6 for triage + summaries; Opus 4.7 for chat widget
- Hotwired Turbo for SPA-style navigation; Chart.js for the Activity chart
- Cron-driven background jobs via Make targets (
make refresh-feeds,make triage,make digest,make sync-sports)
Get started
Already running? Head to today's queue or browse
feeds at /feeds. Ops stats live at
/admin/dashboard.
New here? The README in the repository covers make install
and the seed-feed flow.