Catch the feed list up when the page comes back into view (#168)

All Subscriptions said 1 unread when the server had none. The badge is the page's own sum of its
feed list, loaded once and patched only by events the server sends when a scan or a download
changes a feed. Reading an item, marking everything read or unsubscribing sends none, so another
device's page kept its old counts, and an iPhone stops the event stream while the app is in the
background, missing everything meanwhile; the reconnect did not catch up.

The page now takes the feed list and the open list again when it comes back into view and when the
stream reconnects after a drop. A browser test reads an item behind the page's back and checks the
badge follows once the page is shown again; it fails without this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 12:13:16 +00:00
parent 19f77a21d9
commit 4e417389fe
3 changed files with 32 additions and 1 deletions

View File

@@ -1,7 +1,19 @@
/* ---------------- live events ---------------- */
let sse;
/// The feed list and the open list, taken again from the server. The events keep both current,
/// and one missed is a count wrong until a reload: reading an item sends none to your other
/// pages, so one read on another device stayed unread here, and an iPhone stops the stream while
/// the app is in the background, when everything is missed (#168).
const catchUp=(()=>{ let t; return ()=>{ clearTimeout(t); t=setTimeout(()=>{
loadFeeds(true).catch(()=>{});
if(S.feed) loadEntries().catch(()=>{});
},300); }; })();
document.addEventListener('visibilitychange',()=>{ if(!document.hidden) catchUp(); });
let dropped=false;
function connect(){
sse=new EventSource('/api/events');
// Back after a drop: whatever was sent meanwhile went to nobody.
sse.onopen=()=>{ if(dropped){ dropped=false; catchUp(); } };
const soon=(fn,ms=500)=>{ let t; return ()=>{ clearTimeout(t); t=setTimeout(fn,ms); }; };
const refreshEntries=soon(()=>{ if(S.feed) loadEntries(); });
// Every scan's events reach everyone; only this person's feeds are theirs to show or refresh.
@@ -48,7 +60,7 @@ function connect(){
// page reloads; reload the list here when a scan synced an OPML if that ever matters.
else if(ev.ev==='scan_done'){ scanning.clear(); paintScanning(); if(ev.feeds) refreshEntries(); }
};
sse.onerror=()=>{ sse.close(); setTimeout(connect,4000); };
sse.onerror=()=>{ sse.close(); dropped=true; setTimeout(connect,4000); };
}
connect();
loadFeeds();