From 119e8f1599a8ed7e35763811da2743c3d186b491 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Mon, 25 May 2026 07:59:27 -0700 Subject: [PATCH] Harden auto-sync history row rendering Normalize sparse playlist pipeline history rows before rendering and add a visible fallback for empty entries so the Run History tab cannot collapse into unreadable divider lines. --- webui/static/auto-sync.js | 39 +++++++++++++++++++++++++++++++++++++-- webui/static/style.css | 16 ++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/webui/static/auto-sync.js b/webui/static/auto-sync.js index 586d8ad6..b9899fce 100644 --- a/webui/static/auto-sync.js +++ b/webui/static/auto-sync.js @@ -438,13 +438,14 @@ function renderAutoSyncHistoryPanel(history, total) {
- ${history.map(autoSyncHistoryEntryHtml).join('')} + ${history.map((entry, index) => autoSyncHistoryEntryHtml(entry, index)).join('')} ${total > history.length ? `
Showing ${history.length} of ${total} runs
` : ''}
`; } -function autoSyncHistoryEntryHtml(entry) { +function autoSyncHistoryEntryHtml(entry, index = 0) { + entry = autoSyncNormalizeHistoryEntry(entry, index); const status = entry.status || 'completed'; const before = entry.before_json || {}; const after = entry.after_json || {}; @@ -499,6 +500,40 @@ function autoSyncHistoryEntryHtml(entry) { `; } +function autoSyncNormalizeHistoryEntry(entry, index) { + if (!entry || typeof entry !== 'object') { + return { + id: `unknown-${index}`, + status: 'completed', + playlist_name: 'Playlist pipeline run', + trigger_source: 'pipeline', + summary: 'Run history entry did not include detailed metadata.', + before_json: {}, + after_json: {}, + result_json: {}, + }; + } + return { + ...entry, + id: entry.id ?? `history-${index}`, + before_json: autoSyncParseHistoryObject(entry.before_json), + after_json: autoSyncParseHistoryObject(entry.after_json), + result_json: autoSyncParseHistoryObject(entry.result_json), + }; +} + +function autoSyncParseHistoryObject(value) { + if (!value) return {}; + if (typeof value === 'object') return value; + if (typeof value !== 'string') return {}; + try { + const parsed = JSON.parse(value); + return parsed && typeof parsed === 'object' ? parsed : {}; + } catch (_err) { + return {}; + } +} + function autoSyncHistoryFallbackSummary(before, after, status) { const beforeTracks = parseInt(before.track_count, 10) || 0; const afterTracks = parseInt(after.track_count, 10) || 0; diff --git a/webui/static/style.css b/webui/static/style.css index 6b211b5e..b9a75954 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -12023,6 +12023,8 @@ body.helper-mode-active #dashboard-activity-feed:hover { } .auto-sync-history-entry { + display: block; + min-height: 84px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; background: @@ -12032,6 +12034,19 @@ body.helper-mode-active #dashboard-activity-feed:hover { box-shadow: 0 10px 26px rgba(0, 0, 0, 0.14); } +.auto-sync-history-entry:empty { + display: flex; + align-items: center; + padding: 16px; +} + +.auto-sync-history-entry:empty::before { + content: 'Run history entry unavailable. Refresh after the next playlist pipeline run.'; + color: rgba(255, 255, 255, 0.58); + font-size: 12px; + font-weight: 700; +} + .auto-sync-history-entry:hover { border-color: rgba(var(--accent-rgb), 0.3); background: @@ -12047,6 +12062,7 @@ body.helper-mode-active #dashboard-activity-feed:hover { min-height: 84px; padding: 15px 16px; cursor: pointer; + background: rgba(255, 255, 255, 0.018); } .auto-sync-history-status {