feat: add debug information panel for data loading transparency

Added comprehensive debug panel that displays:
- Field mappings (CSV columns → item properties)
- Template placeholders (required template fields)
- CSV data preview (headers, sample data, validation warnings)

The panel appears below the timeline viewer and helps troubleshoot:
- Missing or mismatched CSV columns
- Template structure issues
- Data format problems

Shows warnings when no valid items are found with actionable
troubleshooting steps.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-27 23:09:35 +01:00
parent c22a47f1ea
commit dd3ba4df58
2 changed files with 162 additions and 0 deletions

139
engine.js
View File

@@ -190,6 +190,9 @@ window.timelineEngine = {
// Update project status
this.updateFileStatus('project', name, 'loaded');
// Show field mappings in debug panel
this.showFieldMappings();
// Track loading errors for user feedback
const loadingErrors = [];
@@ -235,6 +238,9 @@ window.timelineEngine = {
console.log("SVG template loaded, length:", this.template.length);
this.updateFileStatus('svg', cfg.svgTemplate, 'loaded');
// Show template fields in debug panel
this.showTemplateFields();
// Show template preview if no CSV data is loaded yet
if (!this.csvOverride && document.querySelector("#viewer").innerHTML.includes("Keine Timeline verfügbar")) {
this.showTemplatePreview();
@@ -321,6 +327,10 @@ window.timelineEngine = {
complete: (res) => {
console.log("Papa.parse complete, found", res.data.length, "rows");
const rows = res.data;
// Show CSV preview in debug panel
self.showCSVPreview(text, rows);
const items = rows.map((r) => {
const dueField = (m.due || []).find(f => r[f]);
const item = {
@@ -396,6 +406,132 @@ window.timelineEngine = {
// Fallback
d = new Date(str);
return isNaN(d.getTime()) ? null : d;
},
// --------- Debug Display Functions ---------
showFieldMappings() {
if (!this.config || !this.config.fieldMapping) return;
const debugInfo = document.getElementById("debugInfo");
const fieldMappingInfo = document.getElementById("fieldMappingInfo");
const fieldMappingDisplay = document.getElementById("fieldMappingDisplay");
if (debugInfo && fieldMappingInfo && fieldMappingDisplay) {
debugInfo.style.display = "block";
fieldMappingInfo.style.display = "block";
const mapping = this.config.fieldMapping;
let display = "CSV Column → Timeline Field\n";
display += "━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
display += `ID: ${JSON.stringify(mapping.id)}\n`;
display += `Title: ${JSON.stringify(mapping.title)}\n`;
display += `Lane: ${JSON.stringify(mapping.lane)}\n`;
display += `Due: ${JSON.stringify(mapping.due)}\n`;
if (mapping.epic) display += `Epic: ${JSON.stringify(mapping.epic)}\n`;
if (mapping.type) display += `Type: ${JSON.stringify(mapping.type)}\n`;
if (mapping.color) display += `Color: ${JSON.stringify(mapping.color)}\n`;
fieldMappingDisplay.textContent = display;
}
},
showTemplateFields() {
if (!this.template) return;
const debugInfo = document.getElementById("debugInfo");
const templateFieldsInfo = document.getElementById("templateFieldsInfo");
const templateFieldsDisplay = document.getElementById("templateFieldsDisplay");
if (debugInfo && templateFieldsInfo && templateFieldsDisplay) {
debugInfo.style.display = "block";
templateFieldsInfo.style.display = "block";
// Extract placeholders from template
const placeholders = new Set();
const placeholderRegex = /\{\{([A-Z_]+)\}\}/g;
let match;
while ((match = placeholderRegex.exec(this.template)) !== null) {
placeholders.add(match[1]);
}
let display = "Required Template Placeholders:\n";
display += "━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
// Group by category
const monthFields = Array.from(placeholders).filter(p => p.includes('MONTH'));
const laneFields = Array.from(placeholders).filter(p => p.includes('LANE'));
const taskFields = Array.from(placeholders).filter(p => p.includes('TASK') || p.includes('TEXT'));
const otherFields = Array.from(placeholders).filter(p =>
!monthFields.includes(p) && !laneFields.includes(p) && !taskFields.includes(p)
);
if (monthFields.length > 0) {
display += "\nMonth Fields:\n";
monthFields.forEach(f => display += `${f}\n`);
}
if (laneFields.length > 0) {
display += "\nLane Fields:\n";
laneFields.forEach(f => display += `${f}\n`);
}
if (taskFields.length > 0) {
display += "\nTask Fields:\n";
taskFields.forEach(f => display += `${f}\n`);
}
if (otherFields.length > 0) {
display += "\nOther Fields:\n";
otherFields.forEach(f => display += `${f}\n`);
}
templateFieldsDisplay.textContent = display;
}
},
showCSVPreview(csvText, parsedData) {
const debugInfo = document.getElementById("debugInfo");
const csvDataInfo = document.getElementById("csvDataInfo");
const csvDataDisplay = document.getElementById("csvDataDisplay");
if (debugInfo && csvDataInfo && csvDataDisplay) {
debugInfo.style.display = "block";
csvDataInfo.style.display = "block";
const lines = csvText.trim().split('\n');
const headers = lines[0] ? lines[0].split(',') : [];
const firstDataLine = lines[1] ? lines[1].split(',') : [];
let display = "CSV Structure Preview:\n";
display += "━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
display += `Headers: ${headers.join(', ')}\n\n`;
if (firstDataLine.length > 0) {
display += "First Data Row:\n";
headers.forEach((header, i) => {
display += ` ${header}: "${firstDataLine[i] || ''}"\n`;
});
}
if (parsedData) {
display += `\nParsed Rows: ${parsedData.length}\n`;
const validCount = parsedData.filter(r => {
const mapping = this.config?.fieldMapping || {};
const titleField = mapping.title;
const dueField = Array.isArray(mapping.due) ? mapping.due.find(f => r[f]) : mapping.due;
return r[titleField] && r[dueField];
}).length;
display += `Valid Items: ${validCount}\n`;
if (validCount === 0 && parsedData.length > 0) {
display += "\n⚠ WARNING: No valid items found!\n";
display += "Check that:\n";
display += " • CSV headers match field mappings\n";
display += " • Title and Due fields have values\n";
display += " • Date format is parseable (e.g., YYYY-MM-DD)\n";
}
}
csvDataDisplay.textContent = display;
}
}
};
@@ -610,6 +746,9 @@ window.setupEventHandlers = function() {
window.timelineEngine.template = await file.text();
window.timelineEngine.updateFileStatus('svg', file.name, 'loaded');
// Show template fields in debug panel
window.timelineEngine.showTemplateFields();
// Show template preview immediately when manually loaded
window.timelineEngine.showTemplatePreview();
});

View File

@@ -325,6 +325,29 @@
</div>
</div>
<!-- Debug Information Panel -->
<div id="debugInfo" style="margin-top:16px; padding:16px; background:#f8f9fa; border:1px solid #e9ecef; border-radius:8px; display:none;">
<h3 style="margin:0 0 12px 0; font-size:14px; font-weight:600; color:#495057;">🔍 Debug Information</h3>
<!-- Field Mappings -->
<div id="fieldMappingInfo" style="display:none; margin-bottom:12px;">
<h4 style="margin:0 0 8px 0; font-size:13px; color:#6c757d; font-weight:600;">📋 Configured Field Mappings</h4>
<pre id="fieldMappingDisplay" style="background:#fff; padding:12px; border-radius:4px; border:1px solid #dee2e6; font-size:12px; margin:0; overflow-x:auto; font-family:'Courier New', monospace;"></pre>
</div>
<!-- Template Fields -->
<div id="templateFieldsInfo" style="display:none; margin-bottom:12px;">
<h4 style="margin:0 0 8px 0; font-size:13px; color:#6c757d; font-weight:600;">🖼️ Template Placeholders</h4>
<pre id="templateFieldsDisplay" style="background:#fff; padding:12px; border-radius:4px; border:1px solid #dee2e6; font-size:12px; margin:0; overflow-x:auto; font-family:'Courier New', monospace;"></pre>
</div>
<!-- CSV Data Preview -->
<div id="csvDataInfo" style="display:none;">
<h4 style="margin:0 0 8px 0; font-size:13px; color:#6c757d; font-weight:600;">📊 CSV Data Preview</h4>
<pre id="csvDataDisplay" style="background:#fff; padding:12px; border-radius:4px; border:1px solid #dee2e6; font-size:12px; margin:0; overflow-x:auto; font-family:'Courier New', monospace;"></pre>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Setup event handlers first