From cf8ba70af80ff915ef83139a2132a500b33651f7 Mon Sep 17 00:00:00 2001 From: Honey Date: Sat, 2 May 2026 11:56:17 +0200 Subject: [PATCH] fix(ui): playlist search with artist+title, artist in missing list, no bounce on job cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Playlist track search: use 'Artist Title' query + songCount=10 (was title-only + 5) fixes songs like 'Dead' by Geoxor being missed due to common-word collision - Missing tracks now show 'Artist – Title' instead of just title - renderJobs: update existing cards instead of full innerHTML wipe every 1.5s eliminates slideUp animation re-triggering on every poll tick Co-Authored-By: Claude Sonnet 4.6 --- src/ui.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/ui.js b/src/ui.js index ba80fbd..ce6a5d6 100644 --- a/src/ui.js +++ b/src/ui.js @@ -355,16 +355,19 @@ async function _createNdPlaylist(tracks, name) { const norm = s => s?.toLowerCase().trim() || ''; for (const t of tracks) { if (!t.trackName) continue; + const missingLabel = t.artistName ? `${t.artistName} – ${t.trackName}` : t.trackName; try { - const r = await api('search3', `&query=${encodeURIComponent(t.trackName)}&songCount=5&albumCount=0&artistCount=0`); + // Search with artist+title first for precision, fall back to title-only with more results + const artistQuery = t.artistName ? `${t.artistName} ${t.trackName}` : t.trackName; + const r = await api('search3', `&query=${encodeURIComponent(artistQuery)}&songCount=10&albumCount=0&artistCount=0`); const songs = r.searchResult3?.song || []; const match = songs.find(s => norm(s.title) === norm(t.trackName) && norm(s.artist).split(' ')[0] === norm(t.artistName).split(' ')[0] ) || songs.find(s => norm(s.title) === norm(t.trackName)); if (match) songIds.push(match.id); - else missing.push(t.trackName); - } catch { missing.push(t.trackName); } + else missing.push(missingLabel); + } catch { missing.push(missingLabel); } } if (!songIds.length) { toast('⚠ Keine Tracks in Navidrome gefunden — zuerst Library scannen, dann Playlist manuell anlegen'); @@ -521,10 +524,22 @@ function updateJob(id, status, msg, progress) { } function renderJobs() { const panel = document.getElementById('jobs-panel'); - panel.innerHTML = ''; - Object.entries(S.jobs).forEach(([id,j]) => { - const c = document.createElement('div'); c.className = 'job-card'; - const isDone=j.status==='done', isErr=j.status==='error'; + const activeIds = new Set(Object.keys(S.jobs)); + + // Remove cards whose jobs are gone + panel.querySelectorAll('[data-job-id]').forEach(el => { + if (!activeIds.has(el.dataset.jobId)) el.remove(); + }); + + Object.entries(S.jobs).forEach(([id, j]) => { + let c = panel.querySelector(`[data-job-id="${id}"]`); + if (!c) { + c = document.createElement('div'); + c.className = 'job-card'; + c.dataset.jobId = id; + panel.appendChild(c); + } + const isDone = j.status === 'done', isErr = j.status === 'error'; const statusText = isErr ? 'Fehler' : isDone ? '✓ Fertig! Bibliothek wird gescannt…' : j.msg || 'Wird heruntergeladen…'; c.innerHTML = `
${esc(j.title)}
@@ -532,7 +547,6 @@ function renderJobs() {
${statusText}
`; - panel.appendChild(c); }); } async function pollJob(jobId) {