Feeds were already persisted, but nothing ever re-fetched them after
the initial subscribe — scoring.rs had a TODO where feed polling was
supposed to go. Add a feed_polling job that re-fetches every
subscribed feed, relies on articles.url's unique constraint to skip
ones already seen, and stamps last_fetched_at. Runs once on startup
(so reopening the app catches up immediately) and every 15 minutes
after, ahead of the scoring pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmwX8eafbMstJvt8XPSqft
sqlx's query!/query_as! macros only check raw SQL strings against the live
schema; Diesel's table!-derived DSL type-checks query structure itself at
compile time. SQLite has no native async driver, so diesel-async wraps a
blocking SqliteConnection via SyncConnectionWrapper, pooled with bb8.
Migrations move from sqlx's single-file-per-migration format to Diesel's
up.sql/down.sql pairs, run transactionally via diesel_migrations against a
throwaway sync connection at boot (MigrationHarness needs a sync
Connection), giving revertable migrations that sqlx::migrate! doesn't
support.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rust workspace with core (topic-affinity learning engine + relevance
scoring), db (sqlite/sqlx schema + repo), feeds (RSS/Atom fetch), llm
(rig + local Ollama embeddings/completion), and web (axum + Dioxus
fullstack UI, no separate JS stack). Two-stage relevance filtering
(embedding shortlist -> LLM judgment) and an engagement/surprise-based
topic affinity engine with daily decay. All crates compile and core's
affinity engine has passing unit tests; server and wasm client targets
of feedsignal-web both check clean.