From dd3ba4df585ab3a7f6c92d4ba1e25c4b5bab848e Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 27 Nov 2025 23:09:35 +0100 Subject: [PATCH] feat: add debug information panel for data loading transparency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- engine.js | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 23 +++++++++ 2 files changed, 162 insertions(+) diff --git a/engine.js b/engine.js index d7712d6..f2d92be 100644 --- a/engine.js +++ b/engine.js @@ -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(); }); diff --git a/index.html b/index.html index 6a58eb9..3cfcbdf 100644 --- a/index.html +++ b/index.html @@ -325,6 +325,29 @@ + + +