feat(v3): show "N added / M removed" after a library scan (#686)

Completes the Refresh feature: the scanner now reports a delta so the toast can
say what changed instead of a generic confirmation.

Server: delete_missing returns both deltas from its one query -- rows pruned
(removed) and current files not yet in the DB (added) -- and the scan retains
added/removed on the terminal scan-status (previously wiped to 0). Client: the
completion toast shows "N songs added / M removed" (or "up to date"), and a scan
we merely attached to (background / Settings) toasts only when it actually
changed something, so a periodic no-op pass stays silent. library:changed now
carries the delta too.

Verified end to end: empty rescan -> added 0; add a song -> added 1; remove it
-> removed 1.


Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ChrisBeWithYou
2026-07-02 13:57:50 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 11c0f0483f
commit 7ff9261000
2 changed files with 30 additions and 14 deletions
+17 -6
View File
@@ -2795,13 +2795,20 @@
}
}
// Show a completion toast (reuses the shared fbNotify surface), suppressed
// while in a song. Honest + never-punishing copy; the precise "N added" count
// arrives with the background-scan delta work — until then this is a generic,
// truthful confirmation.
// while in a song. Honest + never-punishing copy: shows the "N added / M
// removed" delta from the scan when there is one, else "up to date".
function _scanCompleteToast(sd) {
if (document.querySelector('.screen.active') && document.querySelector('.screen.active').id === 'player') return;
if (!window.fbNotify) return;
const msg = (sd && sd.error) ? 'Scan finished with an error' : 'Your library is up to date';
const added = (sd && sd.added) || 0, removed = (sd && sd.removed) || 0;
let msg;
if (sd && sd.error) msg = 'Scan finished with an error';
else if (added || removed) {
const parts = [];
if (added) parts.push(added + ' song' + (added === 1 ? '' : 's') + ' added');
if (removed) parts.push(removed + ' removed');
msg = parts.join(' · ');
} else msg = 'Your library is up to date';
try { window.fbNotify.show({ title: 'Library scan complete', message: msg, icon: '🔄', accent: '#22C55E' }); } catch (e) { /* */ }
}
// Poll scan-status until the scan finishes, driving the button state. On
@@ -2824,8 +2831,12 @@
if ((sawRunning && sd && !sd.running) || noopDone || ticks >= 180) {
clearInterval(_refreshPoll); _refreshPoll = null;
_setRefreshState(null);
if (sawRunning && window.feedBack) { try { window.feedBack.emit('library:changed', { reason: 'rescan' }); } catch (e) { /* */ } }
if (announce) _scanCompleteToast(sd);
const hasDelta = sd && (((sd.added || 0) > 0) || ((sd.removed || 0) > 0));
if (sawRunning && window.feedBack) { try { window.feedBack.emit('library:changed', { reason: 'rescan', added: (sd && sd.added) || 0, removed: (sd && sd.removed) || 0 }); } catch (e) { /* */ } }
// A user-initiated refresh always confirms; a scan we only attached
// to (background / Settings) toasts just when it changed something,
// so a periodic no-op pass stays silent.
if (announce || hasDelta) _scanCompleteToast(sd);
}
}, 1000);
}