ui(downloads): Unverified review rows get the Quarantine-style card design

The Quarantine sub-view was reworked into rich cards (artwork, source line,
row-click to expand an inline detail panel, consistent action cluster) but the
Unverified sub-view was left on the generic download-row layout that opened a
modal on click. Bring it to parity:

- dedicated _verifUnverifiedRowHtml / _verifUnverifiedRows renderer, used via a
  sibling branch in _adlRender (mirrors the quarantine sub-view branch).
- row click toggles an inline details panel (why flagged, download source,
  quality, file, downloaded-at), open-state keyed by stable id so it survives
  the 2 s poll re-render — same pattern as verifQuarInspect.
- reuses the existing verif-quar-* / verif-actions / adl-row CSS (no new styles)
  and the existing play/compare/audit/approve/delete handlers.
- NO grouping: each unverified import is its own track (grouping only makes
  sense for the quarantine alternates), per design intent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWJk7EuM7YktQeNyqQwTZY
This commit is contained in:
dev 2026-06-27 19:27:28 +02:00
parent a42cf3b865
commit 027cf74d47

View file

@ -2760,6 +2760,84 @@ async function verifDelete(hid, btn) {
} catch (e) { showToast && showToast('Delete failed', 'error'); }
if (btn) btn.disabled = false;
}
// ---- Unverified review rows (Quarantine-style cards) ----
// The Unverified sub-view used to piggyback on the generic download row and
// open a modal on click. These give it the same card design the Quarantine
// sub-view got — row click expands an inline detail panel in place — minus the
// grouping (each unverified import is its own track, nothing to group).
let _verifUnvData = [];
const _verifUnvOpenDetails = new Set();
function _verifUnvKey(dl) {
return verifHistoryId(dl) || dl.task_id || '';
}
function _verifUnvDetailRows(dl) {
const reason = dl.verification_status === 'force_imported'
? 'Accepted as the best available candidate after the retry budget was exhausted (version-mismatch fallback). A library AcoustID scan reports these as informational.'
: 'AcoustID could not hard-confirm this file (ambiguous / cross-script metadata / no fingerprint match). Imported, but not verified.';
const source = dl.batch_source || dl.batch_name || '';
return [
`<div><span class="verif-detail-label">Why flagged:</span> ${_adlEsc(reason)}</div>`,
source ? `<div><span class="verif-detail-label">Download source:</span> ${_adlEsc(source)}</div>` : '',
dl.quality ? `<div><span class="verif-detail-label">Quality:</span> ${_adlEsc(dl.quality)}</div>` : '',
dl.file_path ? `<div><span class="verif-detail-label">File:</span> ${_adlEsc(dl.file_path)}</div>` : '',
dl.created_at ? `<div><span class="verif-detail-label">Downloaded:</span> ${_adlEsc(dl.created_at)}</div>` : '',
].filter(Boolean).join('');
}
function _verifUnverifiedRowHtml(dl, idx) {
const hid = verifHistoryId(dl);
const title = _adlEsc(dl.title || 'Unknown Track');
const meta = [_adlEsc(dl.artist || ''), _adlEsc(dl.album || '')].filter(Boolean).join(' · ');
const source = _adlEsc(dl.batch_source || dl.batch_name || '');
const timeAgo = _verifTimeAgo(dl.created_at);
const artHtml = dl.artwork
? `<img class="adl-row-art" src="${_adlEsc(dl.artwork)}" alt="" onerror="this.style.display='none'">`
: '<div class="adl-row-art adl-row-art-empty"></div>';
const detailsOpen = _verifUnvOpenDetails.has(_verifUnvKey(dl));
const actions = hid ? `
<button class="verif-act verif-act-play" onclick="verifPlay('${hid}')" title="Play the downloaded file in the media player"></button>
<button class="verif-act" onclick="verifCompare('${hid}', this)" title="Find this track on Soulseek/streaming sources and play it in the media player — compare against your file"></button>
<button class="verif-act" onclick="verifAudit('${hid}')" title="Open the full audit trail for this download (lifecycle, embedded tags, lyrics)">🔍</button>
<button class="verif-act verif-act-ok" onclick="verifApprove('${hid}', this)" title="Approve: mark as human-verified (tag + DB). The AcoustID scanner will skip it."></button>
<button class="verif-act verif-act-del" onclick="verifDelete('${hid}', this)" title="Wrong file: delete it from disk and remove this entry">🗑</button>` : '';
return `<div class="adl-row adl-row-completed verif-quar-row" data-task-id="${_adlEsc(dl.task_id || '')}" onclick="verifUnvInspect(${idx})" title="Click to show/hide details (why it was flagged, source, quality, file)">
${artHtml}
<div class="adl-row-info">
<div class="adl-row-title">${title}</div>
${meta ? `<div class="adl-row-meta">${meta}</div>` : ''}
${source ? `<div class="adl-row-batch">${source}</div>` : ''}
<div class="verif-quar-details" id="verif-unv-details-${idx}" style="display:${detailsOpen ? '' : 'none'}">${_verifUnvDetailRows(dl)}</div>
</div>
<div class="verif-actions" onclick="event.stopPropagation()">
${_verifReasonBadge(dl)}
${dl.quality ? `<span class="adl-quality-chip" title="Audio quality of the downloaded file (read from the file itself)">${_adlEsc(dl.quality)}</span>` : ''}
${timeAgo ? `<span class="verif-time">${timeAgo}</span>` : ''}
${actions}
</div>
</div>`;
}
function _verifUnverifiedRows(items) {
_verifUnvData = items;
if (!items.length) return '';
return items.map((dl, idx) => _verifUnverifiedRowHtml(dl, idx)).join('');
}
function verifUnvInspect(idx) {
// Open-state lives in a Set keyed by the row's stable id (not the DOM) so it
// survives the 2 s polling re-render — same pattern as verifQuarInspect.
const dl = _verifUnvData[idx];
if (!dl) return;
const key = _verifUnvKey(dl);
if (_verifUnvOpenDetails.has(key)) _verifUnvOpenDetails.delete(key);
else _verifUnvOpenDetails.add(key);
const el = document.getElementById(`verif-unv-details-${idx}`);
if (el) el.style.display = _verifUnvOpenDetails.has(key) ? '' : 'none';
}
// ---- Quarantine sub-view inside the ⚠ filter ----
// The review queue covers BOTH kinds of suspect files: imported-but-
// unconfirmed (unverified/force_imported) and not-imported-at-all
@ -3293,6 +3371,18 @@ function _adlRender() {
return;
}
// Unverified review sub-view: render the Quarantine-style cards (inline
// expandable details), not the generic download rows.
if (_adlFilter === 'unverified' && _verifSubView === 'unverified') {
const uhtml = _verifUnverifiedRows(filtered);
const uEmptyEl = document.getElementById('adl-empty');
const uEmptyHtml = uEmptyEl ? uEmptyEl.outerHTML : '';
list.innerHTML = uEmptyHtml + uhtml;
const uNewEmpty = document.getElementById('adl-empty');
if (uNewEmpty) uNewEmpty.style.display = uhtml ? 'none' : '';
return;
}
if (filtered.length === 0) {
if (empty) empty.style.display = '';
// Clear any existing rows but keep the empty message