A feed's page follows its row after a check (#316)

The header was drawn once, on opening the feed. feed_row put the new row in S.feeds and redrew the
feed list but not the open page, so a check that cleared an error left the error showing, with the
old "checked" time, until the feed was opened again. Seen on Eli Bendersky's website: a connect
timeout at 15:13, a clean forced check at 15:28, and the timeout still on the page.

Only the stat line and the error banner are redrawn: the whole page would close the open item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 15:37:19 +00:00
parent 0696583547
commit 84b4203daf
4 changed files with 33 additions and 5 deletions

View File

@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Scrolling to the end of a dialog or the phone's feed list no longer scrolls the page behind it (#312).
- Cover artwork has a faint edge, so a dark cover stands off a dark theme (#313).
- The tab bar's corners follow the tabs inside it (#314), and the Directory's covers fan out smoothly (#315).
- A feed's page drops an error once a later check succeeds, and its item count and when it was last checked stay current, instead of keeping what they were when you opened it (#316).
- Settings shows iPX's and the page's versions at its foot: the logo's tooltip was out of reach of a keyboard and of a phone (#309).
## [1.2.0] - 2026-10-19

View File

@@ -1,4 +1,21 @@
/* ---------------- feed page ---------------- */
function statHTML(f: Feed){
return `<div class="sub stat" title="Checked every ${everyText(f.every_mins)}${f.next_check?`, next ${due(f.next_check)}`:''}">${
plural(f.entries,'item')}, ${f.downloaded} downloaded · checked ${ago(f.last_checked)}${
f.subscribers>1?` · shared with ${f.subscribers-1} other ${f.subscribers===2?'person':'people'}`:''}</div>`;
}
/// The open feed's counts, last check and error, from a row that came after its page was drawn:
/// the page kept an error a later check had cleared (#316). Not the whole page, which would close
/// the open item.
function patchFeedHead(f: Feed){
const meta=document.querySelector<HTMLElement>('#content .fhead .meta'); if(!meta) return;
meta.querySelector('.fail')?.remove();
const stat=meta.querySelector('.stat'); if(!stat) return;
// An OPML's header has a stat line of its own.
if(!S.feeds.some(c=>c.group===f.id)) stat.outerHTML=statHTML(f);
meta.querySelector('.stat')!.insertAdjacentHTML('afterend',failBannerHTML(f));
wireFailBanner(meta,f);
}
function renderFeed(){
paintScanning(); // another feed's page may be the one open now
const box=$('#content');
@@ -20,9 +37,7 @@ function renderFeed(){
${artHTML(f.image,name)}
<div class="meta">
<h2>${esc(name)}</h2>
<div class="sub stat" title="Checked every ${everyText(f.every_mins)}${f.next_check?`, next ${due(f.next_check)}`:''}">${
plural(f.entries,'item')}, ${f.downloaded} downloaded · checked ${ago(f.last_checked)}${
f.subscribers>1?` · shared with ${f.subscribers-1} other ${f.subscribers===2?'person':'people'}`:''}</div>
${statHTML(f)}
${failBannerHTML(f)}
${f.orphaned?`<div class="sub" style="color:var(--warn)">This feed is no longer listed in its
OPML subscription. It was kept rather than removed because it has downloaded items.</div>`:''}

View File

@@ -5,6 +5,7 @@ let rowsQueued=0;
function patchFeed(row: Feed){
const i=S.feeds.findIndex(f=>f.id===row.id);
if(i<0) S.feeds.push(row); else S.feeds[i]=row;
if(row.id===S.feed) patchFeedHead(row);
if(!rowsQueued) rowsQueued=requestAnimationFrame(()=>{ rowsQueued=0; renderFeeds(); });
}
@@ -200,10 +201,10 @@ function selectFeed(id: string){
/// set (it has been failing for a day and is a kind worth naming -- see `explain_failure` in
/// src/feed.rs). Anything else still shows the raw error, as before.
function failBannerHTML(f: Feed){
if(f.failing) return `<div class="sub" style="color:var(--bad)">${esc(f.failing.reason)}
if(f.failing) return `<div class="sub fail" style="color:var(--bad)">${esc(f.failing.reason)}
<button type="button" class="btn tiny" data-ffail="unsub">Unsubscribe</button>${
f.failing.new_url?` <button type="button" class="btn tiny" data-ffail="newurl">Use the new address</button>`:''}</div>`;
if(f.last_error) return `<div class="sub" style="color:var(--bad)">${esc(f.last_error)}</div>`;
if(f.last_error) return `<div class="sub fail" style="color:var(--bad)">${esc(f.last_error)}</div>`;
return '';
}
function wireFailBanner(box: ParentNode,f: Feed){