From a8e6432e86deeff16e30c846b0fa6625efaef540 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Tue, 26 May 2026 20:02:50 -0700 Subject: [PATCH] SoulSync Discovery tab: open mirror detail modal after refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1c.3 left the click flow at "card shows 'mirrored' + toast", which felt incomplete — Tidal / LB / Last.fm all open a follow-up modal after their discovery flow so the user can act on the results (sync to server playlist, queue downloads, etc.). SoulSync Discovery skips the discovery phase (tracks pre-matched), so the natural analog is the mirrored-playlist detail modal — same one the Mirrored tab opens when you click a row. - Inline ``fetch('/api/mirror-playlist', ...)`` in place of the fire-and-forget ``mirrorPlaylist`` helper so we can capture the returned ``playlist_id`` from the response. - After successful mirror creation, call ``openMirroredPlaylistModal(playlist_id)`` (exposed by stats-automations.js) to surface the tracks view. The card itself keeps the ``♪ N / ✓ N / mirrored`` progress text so a quick second click can re-refresh without re-opening the modal each time (just re-runs the generator + re-upserts the mirror). --- webui/static/sync-soulsync-discovery.js | 53 +++++++++++++++++++------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/webui/static/sync-soulsync-discovery.js b/webui/static/sync-soulsync-discovery.js index f806acbb..6a707daa 100644 --- a/webui/static/sync-soulsync-discovery.js +++ b/webui/static/sync-soulsync-discovery.js @@ -181,19 +181,37 @@ async function handleSoulsyncDiscoverySyncCardClick(kind, variant, name, cardEl) }; }); - if (typeof mirrorPlaylist === 'function') { - mirrorPlaylist( - 'soulsync_discovery', - syntheticId, - finalName, - mirrorTracks, - { - owner: 'SoulSync', - description: `Personalized ${kind}${variant ? ' · ' + variant : ''} — regenerates on Auto-Sync refresh.`, - image_url: '', - }, - ); + // POST inline so we can capture the returned mirrored_playlists + // row id and open the detail modal afterward. ``mirrorPlaylist`` + // (in stats-automations.js) is fire-and-forget and doesn't + // surface the id, which the next step needs. + const normalizedTracks = mirrorTracks.map(t => ({ + track_name: t.track_name || '', + artist_name: t.artist_name || '', + album_name: t.album_name || '', + duration_ms: t.duration_ms || 0, + image_url: t.image_url || null, + source_track_id: t.source_track_id || '', + extra_data: t.extra_data || null, + })); + const mirrorResp = await fetch('/api/mirror-playlist', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + source: 'soulsync_discovery', + source_playlist_id: syntheticId, + name: finalName, + tracks: normalizedTracks, + description: `Personalized ${kind}${variant ? ' · ' + variant : ''} — regenerates on Auto-Sync refresh.`, + owner: 'SoulSync', + image_url: '', + }), + }); + const mirrorData = await mirrorResp.json(); + if (!mirrorData.success) { + throw new Error(mirrorData.error || 'Mirror creation failed'); } + const mirroredId = mirrorData.playlist_id; if (progEl) { progEl.textContent = `♪ ${tracks.length} / ✓ ${mirrorTracks.length} / mirrored`; @@ -219,6 +237,17 @@ async function handleSoulsyncDiscoverySyncCardClick(kind, variant, name, cardEl) if (typeof showToast === 'function') { showToast(`Mirrored '${finalName}' with ${mirrorTracks.length} tracks`, 'success'); } + + // Open the mirrored-playlist detail modal so the user lands on + // the tracks view + can trigger sync / download from there. + // Same flow the Mirrored tab uses when clicking a row. + if (mirroredId && typeof openMirroredPlaylistModal === 'function') { + try { + await openMirroredPlaylistModal(mirroredId); + } catch (e) { + console.warn('Could not open mirrored playlist detail:', e); + } + } } catch (err) { if (btn) { btn.disabled = false;