From b6e1851b11eb273666bd307e1cb9d2501a5f696d Mon Sep 17 00:00:00 2001 From: Torres Date: Tue, 10 Nov 2026 23:28:06 -0400 Subject: [PATCH] =?UTF-8?q?frontend:=20report-screen=20exhaustion=20fix=20?= =?UTF-8?q?=E2=80=94=20local=20decision=20map=20drives=20section=20progres?= =?UTF-8?q?s;=20show=20report=20on=20local=20exhaust=20with=20rate-limited?= =?UTF-8?q?=20resync=20(verified=20headless:=20full=2025-card=20section=20?= =?UTF-8?q?loop,=20forge,=20trends,=20tanks)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/index.html | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) 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||{};