feat(hibp): show a progress pill while the breach scan runs

Bottom-left, out of the way of the toasts, with a live count. Deliberately not the busy overlay: the scan is network-bound and can take a while, and there is no reason to lock the vault while it runs.
This commit is contained in:
r-zakarya
2026-08-01 19:58:16 +01:00
parent e80703d2c4
commit c9068a6732
2 changed files with 46 additions and 0 deletions
+23
View File
@@ -960,11 +960,31 @@ async function hibpCheckPassword(plaintext) {
// parallel but with a small concurrency cap so we don't hammer HIBP
// or trip browser connection limits. Mutates state.hibpResults and
// re-renders to show the new badges.
// Progress pill for the breach scan. Created on demand, removed when done —
// pass null to dismiss. Non-blocking by design: the app stays usable.
function hibpProgress(done, total) {
let pill = document.getElementById('hibpProgress');
if (done === null) { if (pill) pill.remove(); return; }
if (!pill) {
pill = el('div', { class: 'hibp-progress', id: 'hibpProgress' });
pill.appendChild(el('div', { class: 'hibp-progress-spinner' }));
pill.appendChild(el('span', { class: 'hibp-progress-text' }));
document.body.appendChild(pill);
}
const t = pill.querySelector('.hibp-progress-text');
if (t) t.textContent = 'Checking breaches… ' + done + '/' + total;
}
async function hibpCheckAllEntries() {
if (!state.hibpEnabled || !state.entries.length) return;
const CONCURRENCY = 6;
const queue = state.entries.slice();
const workers = [];
const total = queue.length;
let done = 0;
// A discreet pill, not the busy overlay: the scan is network-bound and can
// run for a while, and there is no reason to lock the vault meanwhile.
hibpProgress(0, total);
for (let w = 0; w < CONCURRENCY; w++) {
workers.push((async () => {
@@ -978,11 +998,14 @@ async function hibpCheckAllEntries() {
} catch (e) {
// Network or decrypt failure: skip silently. Will retry
// next time the user opens the vault.
} finally {
hibpProgress(++done, total);
}
}
})());
}
await Promise.all(workers);
hibpProgress(null);
// The health dashboard reads its "pwned" column from state.hibpResults, and
// its cache may well have been computed before these results landed — that
// is exactly what happens when HIBP is switched on from the health view