From 2098343621e32520a7c1dc44fbf70cb46814d496 Mon Sep 17 00:00:00 2001 From: rays Date: Mon, 5 Oct 2026 23:50:15 +0000 Subject: [PATCH] Say on a feed's Directory page why it is failing, and who subscribes (#142) daily-quests, one subscriber and no items, showed "Nothing read from it yet. A feed nobody here subscribes to is checked once a day." on its page in the Directory: the second sentence was written whatever the count, and the feed had been failing since #119 said so, which the page never showed, /api/directory/{id} sending no error. It sends `failing` now, the reason a feed's own page gives (explain_failure), or that the last check failed, never the error itself, which can name the feed's address, and the Directory names none. The page shows it under the header in the error colour, and says a feed is checked once a day only when nobody subscribes and nothing is wrong. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/web.rs | 19 ++++++++++++++++++- web/app.css | 1 + web/src/dialogs.ts | 6 +++++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7a83d5..4cdc407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - A publisher's correction to an item already in iPX, a retitled episode, mended text, new artwork or a fixed length, arrives on the next read. Since 0.9.1 an item kept what it first said: Ain't It Cool News's items each showed the text of the one before, long after the feed was fixed. +- A feed's page in the Directory says when the feed is failing, and why, and no longer says nobody here subscribes to a feed someone does. ## [0.10.1] - 2026-10-05 diff --git a/src/web.rs b/src/web.rs index 41c3dd1..43ec937 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1024,9 +1024,17 @@ struct ListedItem { struct Listed { /// What the feed says it is (#130), sanitized as its items are. description: Option, + /// Why its last check failed, when it did (#142), in the words a feed's own page uses. + failing: Option, items: Vec, } +/// A feed's last error as its Directory page says it: explained, or that the check failed, never +/// the error as it stands, which can name the feed's address, and the Directory names none. +fn failure_words(err: &str) -> String { + crate::feed::explain_failure(err).map_or_else(|| "Its last check failed.".to_owned(), |f| f.reason.to_owned()) +} + /// A listed feed's description and latest twenty items. Only a feed the Directory lists, so a /// guessed id reaches nothing private, as `subscribe_popular` checks. async fn get_listed( @@ -1041,6 +1049,7 @@ async fn get_listed( let rows = state.ctx.db.entries_in(user.id, Some(&id), crate::db::Filter::parse("all"), None, 0, 20, &order).await?; let mut sanitizer = feed_sanitizer(); let description = state.ctx.db.feed_description(&id).await?.map(|d| clean_description(&mut sanitizer, &d, None)); + let failing = state.ctx.db.feed_summary(&id).await?.last_error.as_deref().map(failure_words); let items = rows .into_iter() .map(|e| ListedItem { @@ -1052,7 +1061,7 @@ async fn get_listed( duration: e.duration, }) .collect(); - Ok(Json(Listed { description, items })) + Ok(Json(Listed { description, failing, items })) } fn sort_name(p: &PopularRow) -> String { @@ -1146,6 +1155,14 @@ impl IntoResponse for ApiError { mod tests { use super::*; + #[test] + fn a_failing_feed_says_why_in_the_directory_without_its_address() { + assert_eq!(failure_words("the feed's address answered with nothing at all"), "This address answers with nothing; the site may be gone."); + // reqwest names the URL; the Directory names none. + let raw = "error sending request for url (http://daily-quests.com/comic/?feed=atom): connection closed"; + assert_eq!(failure_words(raw), "Its last check failed."); + } + #[test] fn a_relative_image_resolves_against_the_post() { let mut b = feed_sanitizer(); diff --git a/web/app.css b/web/app.css index 1364522..0b504f3 100644 --- a/web/app.css +++ b/web/app.css @@ -717,6 +717,7 @@ body.scan-this .fhead [data-a=scan] .i,body.scan-any .fhead [data-a=scanall] .i, .show .meta{min-width:0;flex:1;display:flex;flex-direction:column;align-items:flex-start;gap:5px} .show h2{margin:0 0 2px;font-size:30px;line-height:1.15;font-weight:700;letter-spacing:-.02em;overflow-wrap:anywhere} .show .sub{margin:0;color:var(--dim);font-size:13px;font-variant-numeric:tabular-nums} +.show .dfail{color:var(--bad)} .show .acts{margin-top:8px} .dlink{padding:0;font-size:13px;color:var(--accent);text-align:left} .dlink:hover{text-decoration:underline} diff --git a/web/src/dialogs.ts b/web/src/dialogs.ts index 2dc5bed..449291f 100644 --- a/web/src/dialogs.ts +++ b/web/src/dialogs.ts @@ -138,6 +138,7 @@ function drawDirectory(box=$('#directory'), focus?: string){ // Its category's page is one click away from here. show.category?``:''}

${n?`${n} ${n===1?'person here subscribes':'people here subscribe'}`:'Nobody here subscribes yet'}

+
` +sec(show.podcast?'Latest episodes':'Latest posts','

Loading…

'); @@ -217,6 +218,8 @@ async function fillShow(box,p){ more.textContent=open?'Less':'More'; more.setAttribute('aria-expanded',String(open)); }; } + // A broken feed says so, or it reads as a quiet one (#142). + if(got.failing){ const f=$('#dfail',box); f.textContent=got.failing; f.hidden=false; } const items=got.items; list.innerHTML=items.length?items.map(e=>{ const name=entryName(e), text=name.derived?'':plainText(e.description).slice(0,500); @@ -224,7 +227,8 @@ async function fillShow(box,p){ return `
${e.link?`${esc(name.text)}`:esc(name.text)}` +(text?`

${esc(text)}

`:'') +(when.length?`${when.map(s=>`${s}`).join('')}`:'')+'
'; - }).join(''):`

Nothing read from it yet. A feed nobody here subscribes to is checked once a day.

`; + }).join(''):`

Nothing read from it yet.${p.subscribers||got.failing?'' + :' A feed nobody here subscribes to is checked once a day.'}

`; } /// A category as a tile: its name, how many feeds, and the covers of its three most subscribed