diff --git a/PROGRESS.md b/PROGRESS.md index 6fc97bf..c5b5e8c 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -56,6 +56,35 @@ and until now nothing set them. --- +## 2026-09-10 — Fixed: pressing play made an episode vanish from the list + +Reported as "where did Session Zero go, and why do parts share a number?". + +Two separate things, one of them a real bug. + +**The bug.** `play()` marked an episode read the moment playback started. The default view is the +Unread tab, so pressing play removed the episode from the list you were looking at — indistinguishable +from it going missing. Confirmed from the data: all four read entries had `position = 0`, meaning +playback never passed the 10-second save threshold. They had been started, never listened to. + +Marking read now happens when an episode is actually consumed — on `ended`, or once playback passes +90% — never on play. Two earlier PROGRESS entries described mark-on-play as a deliberate feature; +it was a design error, and this supersedes them. + +Also restored the four affected rows to unread, since their read flag was purely an artifact of the +bug (`UPDATE entries SET read=0 WHERE read=1 AND position=0` — no genuinely-played episode could +match, since playing one writes a position). + +**Not a bug: repeated episode numbers.** Each story part ships as two items — the main episode and a +shorter `Junk in the Trunk` companion — with distinct GUIDs, durations and files, and the publisher +gives both the same `itunes:episode`. Zero duplicated titles across all 131 entries. `Session Zero` +additionally carries a season with no episode number and drops the "Part N" naming, so it does not +match the pattern its siblings follow. + +Verified: the arc shows 11/11 items under the default Unread view again. + +--- + ## 2026-09-10 — Titles: RSS `` is the only source Confirmed against the live feed rather than assumed: 131 feed items, 131 stored, **0 mismatches** — diff --git a/web/index.html b/web/index.html index 49f1168..5c1d875 100644 --- a/web/index.html +++ b/web/index.html @@ -495,7 +495,20 @@ async function epAction(a,e,el){ /* ---------------- player ---------------- */ const audio=$('#audio'); -const player={guid:null,feed:null,entry:null,saveAt:0}; +const player={guid:null,feed:null,entry:null,saveAt:0,marked:false}; + +// Marked read when an episode has actually been listened to -- at the end, or past 90%. +// NOT on play: doing that made the episode vanish from the default Unread list the instant +// you pressed play, which looks exactly like it went missing. +function markPlayed(){ + if(!player.guid||player.marked) return; + player.marked=true; + const e=player.entry; + if(!e||e.read) return; + e.read=true; + api(`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}/flags`, + {method:'POST',body:JSON.stringify({read:true})}).then(()=>loadFeeds(true)).catch(()=>{}); +} function play(e){ const enc=e.enclosures.find(x=>x.path); @@ -512,10 +525,7 @@ function play(e){ $('#pfeed').textContent=f?(f.title||f.id):''; $('#player').classList.add('on'); mediaSession(e,f); - // Playing it is the clearest signal it has been listened to; retention deletes - // read episodes before unread ones. - if(!e.read){ e.read=true; api(`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}/flags`, - {method:'POST',body:JSON.stringify({read:true})}).then(()=>loadFeeds(true)); } + player.marked=false; } audio.play().catch(err=>toast('Playback failed: '+err.message,true)); renderEntries(); @@ -537,6 +547,7 @@ audio.addEventListener('timeupdate',()=>{ if(d) $('#seek').value=String(Math.round(audio.currentTime/d*1000)); // Persist roughly every 10s so a reload resumes where you were. if(player.guid && audio.currentTime-player.saveAt>10){ savePos(); } + if(d && audio.currentTime/d >= 0.9) markPlayed(); }); function savePos(){ if(!player.guid) return; @@ -547,7 +558,7 @@ function savePos(){ new Blob([JSON.stringify({secs:Math.floor(audio.currentTime)})],{type:'application/json'})); } audio.addEventListener('pause',savePos); -audio.addEventListener('ended',()=>{savePos();$('#pplay').textContent='▶'}); +audio.addEventListener('ended',()=>{savePos();markPlayed();$('#pplay').textContent='▶'}); audio.addEventListener('play',()=>$('#pplay').textContent='❚❚'); audio.addEventListener('pause',()=>$('#pplay').textContent='▶'); window.addEventListener('beforeunload',savePos);