diff --git a/webui/static/script.js b/webui/static/script.js
index 39353a17..e40bcb7a 100644
--- a/webui/static/script.js
+++ b/webui/static/script.js
@@ -50438,8 +50438,17 @@ async function loadRepairFindings() {
container.innerHTML = items.map(f => {
const icon = severityIcons[f.severity] || 'ℹ️';
const age = formatCacheAge(f.created_at);
- const statusBadge = f.status !== 'pending' ?
- `${f.status}` : '';
+ const actionLabels = {
+ removed_db_entry: 'Entry Removed', deleted_file: 'File Deleted',
+ already_gone: 'Already Gone', fixed_track_number: 'Track # Fixed',
+ applied_cover_art: 'Art Applied', applied_metadata: 'Metadata Applied',
+ removed_duplicates: 'Duplicates Removed',
+ };
+ let statusBadge = '';
+ if (f.status !== 'pending') {
+ const actionText = actionLabels[f.user_action] || f.status;
+ statusBadge = `${actionText}`;
+ }
const typeLabel = typeLabels[f.finding_type] || f.finding_type.replace(/_/g, ' ');
const d = f.details || {};
const filePath = f.file_path || d.original_path || d.file_path || '';
@@ -50555,41 +50564,76 @@ function _renderFindingDetail(f) {
if (f.file_path) rows.push(['File', f.file_path, 'path']);
return _gridRows(rows);
- case 'fake_lossless':
+ case 'fake_lossless': {
+ const cutoff = d.detected_cutoff_khz || 0;
+ const expectedMin = d.expected_min_khz || 0;
+ const nyquist = d.nyquist_khz || (d.sample_rate ? d.sample_rate / 2000 : 22.05);
+ let flHtml = '';
+ if (cutoff && expectedMin) {
+ const cutoffPct = Math.min(100, Math.round((cutoff / nyquist) * 100));
+ const expectedPct = Math.min(100, Math.round((expectedMin / nyquist) * 100));
+ flHtml += `
${d.missing_tracks.map(t => `
+ incHtml += `
${d.missing_tracks.map(t => `
#${t.track_number || '?'} ${_escFinding(t.name || t.title || 'Unknown')}
${t.duration_ms ? `Duration: ${Math.round(t.duration_ms / 1000)}s` : ''}
`).join('')}
`;
}
- return _gridRows(rows);
+ return incHtml;
case 'path_mismatch':
if (d.from) rows.push(['Current Path', d.from, 'path']);
@@ -50612,8 +50656,15 @@ function _renderFindingDetail(f) {
if (d.artist) rows.push(['Artist', d.artist]);
if (d.album_title) rows.push(['Album', d.album_title]);
if (d.spotify_album_id) rows.push(['Spotify ID', d.spotify_album_id]);
- if (d.found_artwork_url) rows.push(['Artwork URL', d.found_artwork_url, 'success']);
- return _gridRows(rows);
+ let artHtml = _gridRows(rows);
+ if (d.found_artwork_url) {
+ artHtml += `
+
})
+
Found artwork
+
`;
+ }
+ return artHtml;
case 'track_number_mismatch':
if (d.matched_title) rows.push(['Matched To', d.matched_title]);
diff --git a/webui/static/style.css b/webui/static/style.css
index 705826c9..20c4e23c 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -40580,6 +40580,124 @@ tr.tag-diff-same {
color: rgba(255, 255, 255, 0.35);
}
+/* Duplicate keep/remove badges */
+.repair-detail-subitem.best {
+ border-color: rgba(34, 197, 94, 0.2);
+ background: rgba(34, 197, 94, 0.04);
+}
+.repair-detail-subitem.removable {
+ opacity: 0.65;
+}
+.repair-keep-badge {
+ display: inline-block;
+ font-size: 9px;
+ font-weight: 700;
+ padding: 1px 5px;
+ border-radius: 3px;
+ background: rgba(34, 197, 94, 0.15);
+ color: #22c55e;
+ margin-right: 4px;
+ letter-spacing: 0.04em;
+}
+.repair-remove-badge {
+ display: inline-block;
+ font-size: 9px;
+ font-weight: 700;
+ padding: 1px 5px;
+ border-radius: 3px;
+ background: rgba(239, 68, 68, 0.12);
+ color: #ef4444;
+ margin-right: 4px;
+ letter-spacing: 0.04em;
+}
+
+/* Artwork preview (for missing cover art) */
+.repair-artwork-preview {
+ margin-top: 8px;
+ display: flex;
+ align-items: flex-end;
+ gap: 10px;
+}
+.repair-artwork-preview img {
+ width: 80px;
+ height: 80px;
+ border-radius: 6px;
+ object-fit: cover;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+.repair-artwork-label {
+ font-size: 10px;
+ color: rgba(255, 255, 255, 0.35);
+}
+
+/* Completion bar (for incomplete albums) */
+.repair-completion-bar {
+ margin: 8px 0 4px;
+}
+.repair-completion-label {
+ font-size: 11px;
+ color: rgba(255, 255, 255, 0.5);
+ margin-bottom: 4px;
+}
+.repair-completion-track {
+ height: 6px;
+ background: rgba(255, 255, 255, 0.06);
+ border-radius: 3px;
+ overflow: hidden;
+}
+.repair-completion-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #f59e0b, #f97316);
+ border-radius: 3px;
+ transition: width 0.3s ease;
+}
+
+/* Spectrum bar (for fake lossless) */
+.repair-spectrum-bar {
+ margin-bottom: 10px;
+}
+.repair-spectrum-label {
+ font-size: 11px;
+ color: rgba(255, 255, 255, 0.5);
+ margin-bottom: 4px;
+}
+.repair-spectrum-track {
+ position: relative;
+ height: 10px;
+ background: rgba(255, 255, 255, 0.06);
+ border-radius: 5px;
+ overflow: visible;
+}
+.repair-spectrum-detected {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ background: linear-gradient(90deg, #ef4444, #f97316);
+ border-radius: 5px;
+ opacity: 0.7;
+}
+.repair-spectrum-expected {
+ position: absolute;
+ top: -2px;
+ width: 2px;
+ height: 14px;
+ background: #22c55e;
+ border-radius: 1px;
+}
+.repair-spectrum-legend {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 4px;
+ font-size: 10px;
+}
+.repair-spectrum-legend-detected {
+ color: #f97316;
+}
+.repair-spectrum-legend-expected {
+ color: #22c55e;
+}
+
/* Score bars (for AcoustID) */
.repair-score-bar {
display: flex;