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 {