Library re-tag: render the per-track old->new diff in the finding card

Wire library_retag into the repair findings UI: a 'Re-tag' type badge, an
'Apply Tags' fix button, and an expandable detail that shows, per track, every
tag that would change as old -> new (plus source/mode/cover-action summary and
any unmatched tracks). So the dry-run finding is actually reviewable before you
apply it — the rich details_json the job stores now surfaces in the card.
This commit is contained in:
BoulderBadgeDad 2026-06-04 09:01:29 -07:00
parent f5c905be86
commit 50b6876d6b

View file

@ -2725,7 +2725,7 @@ async function loadRepairFindings() {
duplicate_tracks: 'Duplicate', incomplete_album: 'Incomplete',
path_mismatch: 'Path Mismatch', metadata_gap: 'Missing Metadata',
missing_cover_art: 'Missing Art', track_number_mismatch: 'Track Number',
missing_lossy_copy: 'No Lossy Copy'
missing_lossy_copy: 'No Lossy Copy', library_retag: 'Re-tag'
};
// Finding types that have an automated fix action
@ -2740,6 +2740,7 @@ async function loadRepairFindings() {
missing_lossy_copy: 'Convert',
acoustid_mismatch: 'Fix',
missing_discography_track: 'Add to Wishlist',
library_retag: 'Apply Tags',
};
container.innerHTML = items.map(f => {
@ -2905,6 +2906,40 @@ function _renderFindingDetail(f) {
if (f.file_path) rows.push(['Full Path', f.file_path, 'path']);
return _gridRows(rows) + _renderPlayButton(f);
case 'library_retag': {
const tracks = Array.isArray(d.tracks) ? d.tracks : [];
const changed = tracks.filter(t => t.changes && Object.keys(t.changes).length);
const meta = [];
if (d.source) meta.push(`Source: ${d.source}`);
if (d.mode) meta.push(`Mode: ${d.mode}`);
if (d.cover_action) meta.push(`Cover: ${d.cover_action}`);
let html = '';
if (meta.length) {
html += `<div class="repair-finding-meta" style="margin-bottom:8px">${_escFinding(meta.join(' · '))}</div>`;
}
if (!changed.length && d.cover_action) {
html += `<div class="repair-finding-desc">Tags already correct — this would refresh cover art only.</div>`;
}
// Per-track old → new diff (cap the rendered list so huge albums stay sane).
changed.slice(0, 40).forEach(t => {
const label = t.title || (t.file_path || '').split(/[\\/]/).pop();
const rows = Object.entries(t.changes).map(([field, c]) => [
field.replace(/_/g, ' '),
`${(c.old === '' || c.old == null) ? '∅' : c.old}${c.new}`,
'highlight',
]);
html += `<div style="margin:8px 0 2px;font-weight:600">${_escFinding(label)}</div>`;
html += _gridRows(rows);
});
if (changed.length > 40) {
html += `<div class="repair-finding-meta" style="margin-top:6px">…and ${changed.length - 40} more track(s)</div>`;
}
if (Array.isArray(d.unmatched) && d.unmatched.length) {
html += `<div class="repair-finding-meta" style="margin-top:8px">Unmatched (left untouched): ${_escFinding(d.unmatched.join(', '))}</div>`;
}
return html || '<div class="repair-finding-desc">No changes.</div>';
}
case 'acoustid_mismatch': {
let html = media + '<div style="margin-bottom:8px">';
html += _renderScoreBar(d.fingerprint_score, 'Fingerprint');