diff --git a/app/static/index.html b/app/static/index.html index 7de7785..731cffd 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -366,8 +366,13 @@ async function runQueue(){ }finally{ qRunning=false; } if(pend.size){ qTimer=setTimeout(runQueue,Math.max(minDelay||15000,1000)); } } -/* optimistic decision set for list/matches + deck commits share this */ +/* optimistic decision set for list/matches + deck commits share this. + localDecisions remembers the latest intent per listing so section + progress stays correct while the persistence queue is mid-flight. */ +const localDecisions=new Map(); // listing_id -> action|null +let lastResyncAt=0; function setDecision(id, act, secId=null){ + localDecisions.set(id, act); updateRowDecision(id, act); // optimistic paint enqueue(id, act, secId); } @@ -553,9 +558,17 @@ function maybeRefill(){ return; } if(!pile.length&&!buffer.length&&!deckLoading){ - if(section&&(section.exhausted||(section.progress&§ion.progress.undecided===0))){ + if(section&§Decided>=sectTotal){ + // locally exhausted (last POSTs may still be flushing — the report + // shows from localDecisions; a quiet resync freshens counts and the + // server's report flip, rate-limited so a failing last POST can't + // cause a resync loop) $('report').classList.add('on'); $('empty').classList.remove('on'); + if(!section.exhausted&&Date.now()-lastResyncAt>2000){ + lastResyncAt=Date.now(); + loadSection(); + } }else if(section){ $('empty').classList.remove('on'); $('report').classList.remove('on'); } @@ -921,7 +934,13 @@ function paintMode(){ else if(!forgeBusy) btn.disabled=false; } function updateSectionUI(body){ - sectDecided=body.progress.decided; sectTotal=body.progress.total; + sectTotal=body.progress.total; + let decided=0; + for(const it of (body.items||[])){ + const local=localDecisions.get(it.id); + if(local===undefined? it.decision!=null : local!=null) decided++; + } + sectDecided=decided; paintSect(); paintMode(); const c=body.counts||{};