Make auto-sync run history readable

Render playlist pipeline history as visible run cards with fallback summaries, preview chips, metadata, Details controls, and an explicit empty result message for sparse payloads.
This commit is contained in:
Broque Thomas 2026-05-25 07:55:52 -07:00
parent 9eb7a49c24
commit 06c76bbcaf
2 changed files with 91 additions and 22 deletions

View file

@ -456,18 +456,32 @@ function autoSyncHistoryEntryHtml(entry) {
const wishlistDelta = autoSyncDelta(after.wishlisted_count, before.wishlisted_count);
const libraryDelta = autoSyncDelta(after.in_library_count, before.in_library_count);
const entryId = `auto-sync-history-${entry.id}`;
const playlistName = entry.playlist_name || after.name || before.name || `Playlist #${entry.playlist_id || 'unknown'}`;
const summary = entry.summary || autoSyncHistoryFallbackSummary(before, after, status);
const resultHtml = [
autoSyncHistoryResultPill('Refreshed', result.playlists_refreshed),
autoSyncHistoryResultPill('Synced', result.tracks_synced),
autoSyncHistoryResultPill('Skipped', result.sync_skipped),
autoSyncHistoryResultPill('Queued', result.wishlist_queued),
result.error ? `<span class="error">${_esc(result.error)}</span>` : '',
].filter(Boolean).join('');
return `
<article class="auto-sync-history-entry">
<div class="auto-sync-history-row" onclick="autoSyncToggleHistoryEntry('${entryId}')">
<span class="auto-sync-history-status ${_escAttr(status)}">${_esc(autoSyncHistoryStatusLabel(status))}</span>
<div class="auto-sync-history-main">
<strong>${_esc(entry.playlist_name || 'Mirrored playlist')}</strong>
<small>${_esc(entry.summary || '')}</small>
<strong>${_esc(playlistName)}</strong>
<small>${_esc(summary)}</small>
<div class="auto-sync-history-preview">
${autoSyncHistoryPreviewPill('Tracks', before.track_count, after.track_count, trackDelta)}
${autoSyncHistoryPreviewPill('Discovered', before.discovered_count, after.discovered_count, discoveredDelta)}
</div>
</div>
<div class="auto-sync-history-meta">
${started ? `<span>${_esc(started)}</span>` : ''}
${duration ? `<span>${_esc(duration)}</span>` : ''}
<span>${_esc(entry.trigger_source || 'pipeline')}</span>
<button type="button" onclick="event.stopPropagation(); autoSyncToggleHistoryEntry('${entryId}')">Details</button>
</div>
</div>
<div id="${entryId}" class="auto-sync-history-detail">
@ -478,17 +492,19 @@ function autoSyncHistoryEntryHtml(entry) {
${autoSyncHistoryStatHtml('In library', before.in_library_count, after.in_library_count, libraryDelta)}
</div>
<div class="auto-sync-history-result">
${autoSyncHistoryResultPill('Refreshed', result.playlists_refreshed)}
${autoSyncHistoryResultPill('Synced', result.tracks_synced)}
${autoSyncHistoryResultPill('Skipped', result.sync_skipped)}
${autoSyncHistoryResultPill('Queued', result.wishlist_queued)}
${result.error ? `<span class="error">${_esc(result.error)}</span>` : ''}
${resultHtml || '<span class="muted">No detailed result payload recorded for this run.</span>'}
</div>
</div>
</article>
`;
}
function autoSyncHistoryFallbackSummary(before, after, status) {
const beforeTracks = parseInt(before.track_count, 10) || 0;
const afterTracks = parseInt(after.track_count, 10) || 0;
return `${autoSyncHistoryStatusLabel(status)} | ${beforeTracks} -> ${afterTracks} tracks`;
}
function autoSyncToggleHistoryEntry(entryId) {
const el = document.getElementById(entryId);
if (el) el.classList.toggle('expanded');
@ -527,6 +543,13 @@ function autoSyncHistoryStatHtml(label, before, after, delta) {
`;
}
function autoSyncHistoryPreviewPill(label, before, after, delta) {
const beforeValue = parseInt(before, 10) || 0;
const afterValue = parseInt(after, 10) || 0;
const deltaText = delta ? ` ${delta > 0 ? '+' : ''}${delta}` : '';
return `<span>${_esc(label)} ${beforeValue}->${afterValue}${_esc(deltaText)}</span>`;
}
function autoSyncHistoryResultPill(label, value) {
if (value === undefined || value === null || value === '') return '';
return `<span>${_esc(label)}: ${_esc(String(value))}</span>`;

View file

@ -12016,29 +12016,36 @@ body.helper-mode-active #dashboard-activity-feed:hover {
.auto-sync-history-list {
min-height: 0;
overflow-y: auto;
padding: 18px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
gap: 12px;
}
.auto-sync-history-entry {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
background: rgba(255, 255, 255, 0.035);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.025)),
rgba(255, 255, 255, 0.035);
overflow: hidden;
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.14);
}
.auto-sync-history-entry:hover {
border-color: rgba(var(--accent-rgb), 0.22);
border-color: rgba(var(--accent-rgb), 0.3);
background:
linear-gradient(135deg, rgba(var(--accent-rgb), 0.075), rgba(255, 255, 255, 0.028)),
rgba(255, 255, 255, 0.04);
}
.auto-sync-history-row {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 12px;
padding: 13px 14px;
gap: 14px;
min-height: 84px;
padding: 15px 16px;
cursor: pointer;
}
@ -12077,18 +12084,38 @@ body.helper-mode-active #dashboard-activity-feed:hover {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.auto-sync-history-main strong {
color: rgba(255, 255, 255, 0.88);
font-size: 13px;
font-size: 14px;
line-height: 1.25;
white-space: nowrap;
}
.auto-sync-history-main small {
margin-top: 3px;
color: rgba(255, 255, 255, 0.42);
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
line-height: 1.3;
white-space: normal;
}
.auto-sync-history-preview {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 9px;
}
.auto-sync-history-preview span {
padding: 4px 7px;
border-radius: 6px;
background: rgba(var(--accent-rgb), 0.1);
border: 1px solid rgba(var(--accent-rgb), 0.16);
color: rgb(var(--accent-light-rgb));
font-size: 10px;
font-weight: 800;
}
.auto-sync-history-meta {
@ -12099,7 +12126,8 @@ body.helper-mode-active #dashboard-activity-feed:hover {
}
.auto-sync-history-meta span,
.auto-sync-history-result span {
.auto-sync-history-result span,
.auto-sync-history-meta button {
padding: 4px 7px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.055);
@ -12108,9 +12136,21 @@ body.helper-mode-active #dashboard-activity-feed:hover {
font-weight: 700;
}
.auto-sync-history-meta button {
border: 1px solid rgba(var(--accent-rgb), 0.18);
color: rgb(var(--accent-light-rgb));
cursor: pointer;
}
.auto-sync-history-meta button:hover {
background: rgba(var(--accent-rgb), 0.14);
border-color: rgba(var(--accent-rgb), 0.35);
}
.auto-sync-history-detail {
display: none;
padding: 0 14px 14px;
padding: 0 16px 16px;
border-top: 1px solid rgba(255, 255, 255, 0.07);
}
.auto-sync-history-detail.expanded {
@ -12121,7 +12161,7 @@ body.helper-mode-active #dashboard-activity-feed:hover {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
padding-top: 2px;
padding-top: 14px;
}
.auto-sync-history-stats div {
@ -12167,6 +12207,12 @@ body.helper-mode-active #dashboard-activity-feed:hover {
border-color: rgba(239, 68, 68, 0.18);
}
.auto-sync-history-result span.muted {
color: rgba(255, 255, 255, 0.42);
background: rgba(255, 255, 255, 0.045);
border-color: rgba(255, 255, 255, 0.08);
}
.auto-sync-history-empty {
margin: 24px;
padding: 44px;