Skip to content

feat: user feeds — RSS sources, per-feed AI curation, telegram digests - #33

Merged
JustWallage merged 4 commits into
mainfrom
claude/feeds-feature-fijvvw
Jul 16, 2026
Merged

feat: user feeds — RSS sources, per-feed AI curation, telegram digests#33
JustWallage merged 4 commits into
mainfrom
claude/feeds-feature-fijvvw

Conversation

@JustWallage

Copy link
Copy Markdown
Owner

Adds the feeds feature: a new tab where each user creates any number of feeds, each with its own RSS/Atom sources, AI-curation preferences, and Telegram delivery slots. The HN homepage pipeline is untouched.

Spec: docs/specs/feeds/index.md · SA validation: approved round 1 · Review: approved round 2 (docs/specs/feeds/review-*.md).

What's in it

  • Schema (one additive migration): feeds, feed_sources (unique (feed_id, url)), feed_items — verdicts stored inline with pref_version reuse mirroring curations, current = member of the latest fetch, unique (feed_id, link) dedupe, and sent_at as the send-once Telegram guard.
  • RSS ingestion: RssClient seam (worker/lib/rss.ts) parsing RSS 2.0 / Atom / RDF / JSON Feed via feedsmith behind a zod re-parse; drops titleless/non-http items, keeps the 50 newest per source, rejects bodies over 5 MB while streaming (user-supplied URLs). Fake client for hermetic e2e.
  • AI curation: feeds get their own system prompt (title + link domain only) over the shared batching/parsing core; the HN prompt and select() stay byte-identical.
  • API /api/feeds: CRUD, sources add/remove (validated by fetching the feed at add time), items (newest ≤20 relevant of the last fetch), archive (every curation ever), slots (409 until Telegram is linked), and per-feed rate-limited run (429 + Retry-After off lastFetchedAt).
  • Cron: due feeds (owner's timezone) are refetched and delivered sequentially; each item is sent to Telegram at most once (sent_at stamped on exactly the delivered items; overflow rolls to the next slot). Maintenance prunes only stale never-relevant items — the archive is kept forever.
  • SPA: feeds nav tab; overview (cards with title + preferences, create form), feed page (feed-switcher dropdown, back link, Refresh, Archive, Settings, New feed), per-feed archive page, settings page (title, preferences, sources, slots via the shared SlotTimesEditor, delete). ApiRequestError now carries the server's { error } message.
  • Deferred with backlog note (docs/0-backlog/newsletter-email-sources.md): newsletter-email addresses as feed sources — needs Cloudflare Email Routing infra; schema keeps it additive.

Checks

  • Unit: 146 passed (18 files) · e2e: 32 passed · format/lint/types/knip/jscpd green.
  • check:tf skipped with user approval — terraform provider downloads are blocked in the CI-less dev container.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RWYc22xqR5mh5Qpd26hTm6


Generated by Claude Code

claude added 4 commits July 16, 2026 09:57
All pre-commit checks run manually and green except check:tf, skipped
with user approval (terraform provider downloads blocked in this env).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWYc22xqR5mh5Qpd26hTm6
New feeds tab: create multiple feeds, each with its own title, AI-curation
preferences, up to 10 RSS/Atom sources (parsed with feedsmith), three
telegram daily slots (owner's timezone), a curated items page (newest 20
relevant of the last fetch), and an archive of every curation ever.
Telegram delivery is send-once per item (feed_items.sentAt). HN homepage
pipeline untouched; feeds get their own system prompt over the shared AI
batching core.

All pre-commit checks run manually and green (unit 143, e2e 32) except
check:tf, skipped with user approval (provider downloads blocked here).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWYc22xqR5mh5Qpd26hTm6
- Enforce the 5 MB cap while streaming the response body (readBodyCapped),
  not after buffering; keep the Content-Length fast path. Unit-tested with
  a chunked stream.
- Cap keeps the 50 newest items (publishedAt-aware stable sort), not the
  first 50 in document order.
- Archive e2e now actually exercises items falling out of the current
  fetch (source swap between runs) and asserts they stay archive-only.
- Replace O(n²) toEvaluate.indexOf verdict lookup with a link-keyed map.

Checks green (unit 146, e2e 32); check:tf skipped per user approval.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWYc22xqR5mh5Qpd26hTm6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWYc22xqR5mh5Qpd26hTm6
@JustWallage
JustWallage marked this pull request as ready for review July 16, 2026 11:05
@JustWallage
JustWallage merged commit 7bb93c1 into main Jul 16, 2026
3 checks passed
@JustWallage
JustWallage deleted the claude/feeds-feature-fijvvw branch July 16, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants