A spinner on the feed being checked, not toasts; check only your own feeds

The scan's events reach everyone, so every browser showed "<feed>: N new" and
"Scanning…" toasts, and refreshed, for everyone's feeds. Now a feed's row, and
its folder's, carries a spinner between feed_start and its done, skip or error;
the list refreshes only for the reader's own feeds; the scan toasts are gone, and
"Downloaded" is said only for a file on screen.

"Check every feed" from the web UI sent a scan of every feed on the server.
Command::Fetch takes an optional `feeds` list -- those feeds and the feeds
inside any OPML among them -- and the web fills it with the asker's
subscriptions. The schedule and the CLI send none, meaning every feed.

Closes #37.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 01:17:40 +00:00
parent 5f6bdbfbcb
commit 905dfa0b02
11 changed files with 115 additions and 24 deletions

View File

@@ -1239,7 +1239,7 @@ async fn add_feed(
/// succeeded either way, so a daemon not taking commands is only logged.
async fn scan_soon(state: &WebState, feed: Option<String>) {
let force = feed.is_some();
if state.cmds.send(Command::Fetch { feed, force }).await.is_err() {
if state.cmds.send(Command::Fetch { feed, force, feeds: vec![] }).await.is_err() {
tracing::warn!("could not queue a scan: the daemon is not accepting commands");
}
}
@@ -1527,11 +1527,21 @@ struct FetchBody {
async fn fetch_now(
State(state): State<WebState>,
user: crate::db::User,
Json(body): Json<FetchBody>,
) -> Result<StatusCode, ApiError> {
// "Check every feed" is every feed this person reads, not everyone's (issue #37).
let feeds = if body.feed.is_some() {
vec![]
} else {
state.ctx.db.subscriptions_for(user.id).await?.into_iter().map(|s| s.feed_id).collect()
};
if body.feed.is_none() && feeds.is_empty() {
return Ok(StatusCode::ACCEPTED); // nothing of theirs to check; an empty list would mean all
}
state
.cmds
.send(Command::Fetch { feed: body.feed, force: body.force })
.send(Command::Fetch { feed: body.feed, force: body.force, feeds })
.await
.map_err(|_| anyhow::anyhow!("the daemon is not accepting commands"))?;
Ok(StatusCode::ACCEPTED)