diff --git a/web.py b/web.py
index 2963a0a..6524d9b 100644
--- a/web.py
+++ b/web.py
@@ -704,6 +704,11 @@ button.chip:hover{background:#6c1f08;border-color:#9a3412}
button.chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.quiet{color:var(--muted);font-size:12px;margin-top:6px}
.spin{color:var(--muted);font-style:italic;font-size:12px;padding:6px 0}
+.prog-bar{height:3px;background:var(--brd);border-radius:2px;margin:6px 0 4px;overflow:hidden}
+.prog-fill{height:100%;background:var(--accent);border-radius:2px;transition:width 0.15s ease}
+.prog-file{font-size:12px;color:var(--muted);font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
+.prog-tally{font-size:11px;color:var(--muted);margin-top:3px}
+.cached-badge{font-size:10px;color:var(--muted);background:var(--surf);border:1px solid var(--brd);border-radius:3px;padding:1px 5px;margin-left:6px;vertical-align:middle;font-style:normal}
.empty{text-align:center;padding:60px;color:var(--muted)}
/* player row */
.player-row td{padding:0 10px 10px;background:var(--bg);border-bottom:1px solid var(--brd)}
@@ -955,6 +960,12 @@ async function analyse(idx, filename, cell, btn) {
}
const box = document.createElement('div'); box.className='wbox';
box.appendChild(drawWave(d.rms_display||[], d.sections||[], d.duration||0, filename));
+ if (d.cached) {
+ const badge = document.createElement('span');
+ badge.className = 'cached-badge'; badge.textContent = 'cached';
+ badge.title = 'Result loaded from cache — change threshold/gap and re-analyse to recompute';
+ box.firstChild.after(badge);
+ }
const chips = document.createElement('div');
chips.className='chips';
@@ -1323,22 +1334,43 @@ async function dayHighlights(dayId, analyzableFiles) {
hlRow.hidden = false;
const n = analyzableFiles.length;
- contentEl.innerHTML = `
Analysing ${n} file${n!==1?'s':''}…
`;
if (btn) btn.disabled = true;
+ // Build progress UI
+ const progWrap = document.createElement('div');
+ progWrap.setAttribute('aria-live', 'polite'); progWrap.setAttribute('aria-busy', 'true');
+ const progBar = document.createElement('div'); progBar.className = 'prog-bar';
+ const progFill = document.createElement('div'); progFill.className = 'prog-fill'; progFill.style.width = '0%';
+ progBar.appendChild(progFill);
+ const progFile = document.createElement('div'); progFile.className = 'prog-file';
+ const progTally = document.createElement('div'); progTally.className = 'prog-tally';
+ progWrap.appendChild(progBar); progWrap.appendChild(progFile); progWrap.appendChild(progTally);
+ contentEl.innerHTML = ''; contentEl.appendChild(progWrap);
+
const threshold = document.getElementById('threshold-input').value || '0.05';
const minGap = document.getElementById('min-gap-input').value || '2';
const results = [];
- for (const f of analyzableFiles) {
+ let nCached = 0, nLive = 0;
+ for (let i = 0; i < analyzableFiles.length; i++) {
+ const f = analyzableFiles[i];
+ progFile.textContent = `${i + 1} / ${n} — ${f.name}`;
+ progFill.style.width = `${(i / n) * 100}%`;
+ const tallyParts = [];
+ if (nCached) tallyParts.push(`${nCached} cached`);
+ if (nLive) tallyParts.push(`${nLive} analysed`);
+ progTally.textContent = tallyParts.join(' · ');
try {
const r = await fetch('/api/analyze?file=' + encodeURIComponent(f.name)
+ '&threshold=' + encodeURIComponent(threshold)
+ '&min_gap=' + encodeURIComponent(minGap));
const d = await r.json();
- if (!d.error) results.push({ f, data: d });
+ if (!d.error) { results.push({ f, data: d }); d.cached ? nCached++ : nLive++; }
} catch(e) {}
}
+ progFill.style.width = '100%';
+ progFile.textContent = `Done — ${n} file${n!==1?'s':''} (${nCached} cached, ${nLive} analysed)`;
+ progTally.textContent = '';
if (!results.length) {
contentEl.innerHTML = 'No analysable results.
';