fix #2: header pill refreshes itself after swipes, undo and new batches
loadStats() only ran once at boot, so the live/fresh/new pill (and the list sub-line) went stale until a full reload. Refresh it, debounced and fire-and-forget, after any decision change, when a new batch lands, and on app foreground. Adds two fe_walk_v11 regression checks + a UI-SPEC note.
This commit is contained in:
@@ -544,6 +544,33 @@ def walk_rich():
|
|||||||
and ('%d fresh' % stats['fresh']) in pill_txt,
|
and ('%d fresh' % stats['fresh']) in pill_txt,
|
||||||
'item 7: pill reads live + fresh (%s)' % pill_txt.replace('\n', ' '))
|
'item 7: pill reads live + fresh (%s)' % pill_txt.replace('\n', ' '))
|
||||||
|
|
||||||
|
# --- v1.1.1 (issue #2): the header pill keeps itself live --------------
|
||||||
|
pl0 = api('/api/stats')
|
||||||
|
key('ArrowRight') # probe swipe, undone right after
|
||||||
|
flick(300)
|
||||||
|
pl1 = api('/api/stats')
|
||||||
|
dl = time.time() + 5
|
||||||
|
ptx = ''
|
||||||
|
while time.time() < dl:
|
||||||
|
ptx = page.evaluate("() => document.getElementById('pill').textContent")
|
||||||
|
if ('%d fresh' % pl1['fresh']) in ptx:
|
||||||
|
break
|
||||||
|
time.sleep(0.2)
|
||||||
|
check(('%d fresh' % pl1['fresh']) in ptx and ('%d live' % pl1['live']) in ptx,
|
||||||
|
'issue #2: pill refreshes itself after a swipe, no reload (%s -> %s)'
|
||||||
|
% (pl0['fresh'], ptx))
|
||||||
|
key('ArrowDown') # undo the probe swipe (net-zero)
|
||||||
|
flick(300)
|
||||||
|
pl2 = api('/api/stats')
|
||||||
|
dl = time.time() + 5
|
||||||
|
while time.time() < dl:
|
||||||
|
ptx = page.evaluate("() => document.getElementById('pill').textContent")
|
||||||
|
if ('%d fresh' % pl2['fresh']) in ptx:
|
||||||
|
break
|
||||||
|
time.sleep(0.2)
|
||||||
|
check(('%d fresh' % pl2['fresh']) in ptx,
|
||||||
|
'issue #2: pill refreshes itself after an undo, no reload (%s)' % ptx)
|
||||||
|
|
||||||
# --- B1 no-lock: rapid commit storm (no sheet, no lock) ----------------
|
# --- B1 no-lock: rapid commit storm (no sheet, no lock) ----------------
|
||||||
before = sect_pill()['txt']
|
before = sect_pill()['txt']
|
||||||
taps(2, 40)
|
taps(2, 40)
|
||||||
|
|||||||
@@ -415,6 +415,7 @@ function setDecision(id, act, secId=null, photoIdx=null){
|
|||||||
localDecisions.set(id, act);
|
localDecisions.set(id, act);
|
||||||
updateRowDecision(id, act); // optimistic paint
|
updateRowDecision(id, act); // optimistic paint
|
||||||
enqueue(id, act, secId, photoIdx);
|
enqueue(id, act, secId, photoIdx);
|
||||||
|
refreshStats(); // v1.1.1 (#2): every decision change nudges the header counters
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- payload -> display helpers ---------- */
|
/* ---------- payload -> display helpers ---------- */
|
||||||
@@ -660,6 +661,7 @@ async function loadSection(){
|
|||||||
buffer.push(...fresh);
|
buffer.push(...fresh);
|
||||||
fillPile();
|
fillPile();
|
||||||
updateSectionUI(body);
|
updateSectionUI(body);
|
||||||
|
refreshStats(); // v1.1.1 (#2): a new batch landed — freshen the counters
|
||||||
}catch(e){ setTimeout(()=>loadSection(),15000); }
|
}catch(e){ setTimeout(()=>loadSection(),15000); }
|
||||||
finally{ deckLoading=false; }
|
finally{ deckLoading=false; }
|
||||||
maybeRefill();
|
maybeRefill();
|
||||||
@@ -1206,6 +1208,19 @@ async function loadStats(){
|
|||||||
fetch('/api/badge/seen',{method:'POST'}).catch(()=>{});
|
fetch('/api/badge/seen',{method:'POST'}).catch(()=>{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* v1.1.1 (issue #2): loadStats only ran at boot, so the header pill and the
|
||||||
|
list sub-line went stale after every swipe until a full reload. Refresh
|
||||||
|
shortly after any decision change (swipes, undo, dossier/list/matches
|
||||||
|
edits) and whenever a new batch lands. Coalesced + fire-and-forget: a
|
||||||
|
swipe never waits on it. */
|
||||||
|
let statsTimer=null;
|
||||||
|
function refreshStats(){
|
||||||
|
if(statsTimer) return; // bursts collapse into one read
|
||||||
|
statsTimer=setTimeout(()=>{ statsTimer=null; loadStats(); },600);
|
||||||
|
}
|
||||||
|
/* foreground/resume: catch anything that changed while the app slept
|
||||||
|
(e.g. the 08:30 import) without waiting for the next swipe */
|
||||||
|
document.addEventListener('visibilitychange',()=>{ if(!document.hidden) refreshStats(); });
|
||||||
|
|
||||||
/* ---------- P3 section UI: progress pill + report + forge + modes ---------- */
|
/* ---------- P3 section UI: progress pill + report + forge + modes ---------- */
|
||||||
let sectDecided=0, sectTotal=0;
|
let sectDecided=0, sectTotal=0;
|
||||||
@@ -1297,6 +1312,7 @@ $('btnForge').onclick=async()=>{
|
|||||||
buffer.push(...fresh);
|
buffer.push(...fresh);
|
||||||
fillPile();
|
fillPile();
|
||||||
updateSectionUI(body);
|
updateSectionUI(body);
|
||||||
|
refreshStats(); // v1.1.1 (#2): forged batch — freshen the counters
|
||||||
if(pile.length){ $('report').classList.remove('on'); }
|
if(pile.length){ $('report').classList.remove('on'); }
|
||||||
// item 8: a zero-item forge on a fully drained catalog -> empty2,
|
// item 8: a zero-item forge on a fully drained catalog -> empty2,
|
||||||
// no lone Forge button inviting a wasted agent run
|
// no lone Forge button inviting a wasted agent run
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ Opened from deck ⓘ (or tap) AND list row tap. iOS-sheet geometry: top gap 26px
|
|||||||
|
|
||||||
1. **Data wiring.** Deck buffer from `GET /api/deck` (limit 40, opaque cursor); refill when buffer <5 and a cursor exists. List from `GET /api/list`. Dossier from the row/card payload. Header from `GET /api/stats`.
|
1. **Data wiring.** Deck buffer from `GET /api/deck` (limit 40, opaque cursor); refill when buffer <5 and a cursor exists. List from `GET /api/list`. Dossier from the row/card payload. Header from `GET /api/stats`.
|
||||||
2. **Swipe persistence.** Commits fire `POST /api/swipe` optimistically — the UI never waits. Failures go to a retry queue (attempts at ~2s/5s/10s, then every 15s while the tab lives); toast "Couldn't save — retrying" on first failure. Undo fires `DELETE /api/swipe/{id}` through the same queue.
|
2. **Swipe persistence.** Commits fire `POST /api/swipe` optimistically — the UI never waits. Failures go to a retry queue (attempts at ~2s/5s/10s, then every 15s while the tab lives); toast "Couldn't save — retrying" on first failure. Undo fires `DELETE /api/swipe/{id}` through the same queue.
|
||||||
3. **Badge.** `POST /api/badge/seen` once per session when the deck first renders. Header pill shows `{live} live · {fresh} fresh` always, and `· {new} new` only when new > 0. `fresh` = active listings with no swipe (the deck's new-pool predicate) — v1.1.
|
3. **Badge.** `POST /api/badge/seen` once per session when the deck first renders. Header pill shows `{live} live · {fresh} fresh` always, and `· {new} new` only when new > 0. `fresh` = active listings with no swipe (the deck's new-pool predicate) — v1.1. The pill and the list sub-line refresh themselves shortly after any decision change (swipes, undo, dossier/list/matches edits) and whenever a new batch lands — no reload needed (v1.1.1, issue #2).
|
||||||
4. **List filters + search** per §4 (mockup had placeholder chips only).
|
4. **List filters + search** per §4 (mockup had placeholder chips only).
|
||||||
5. **Matches real (§5); Trends stays stub.**
|
5. **Matches real (§5); Trends stays stub.**
|
||||||
6. **Images.** Every card/thumb references `/img/{id}` (server: cached file → 302 CDN → 404 → placeholder). The client never hotlinks directly.
|
6. **Images.** Every card/thumb references `/img/{id}` (server: cached file → 302 CDN → 404 → placeholder). The client never hotlinks directly.
|
||||||
|
|||||||
Reference in New Issue
Block a user