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||{};