waddling-errors 0.7.3

Structured, secure-by-default diagnostic codes for distributed systems with no_std and role-based documentation
Documentation
/* ============================================
   SIDEBAR MANAGEMENT
   Overview Stats, Browse List, and Detail Panel
   ============================================ */

function initSidebar() {
    // Tab switching
    document.querySelectorAll('.browse-tab').forEach(tab => {
        tab.addEventListener('click', () => {
            const tabId = tab.dataset.tab;
            switchBrowseTab(tabId);
        });
    });
    
    // Update stats
    updateStats();
}

function updateStats() {
    const statErrors = document.getElementById('statErrors');
    const statComponents = document.getElementById('statComponents');
    const statDeprecated = document.getElementById('statDeprecated');
    
    if (statErrors) {
        statErrors.textContent = AppState.errors.length;
    }
    if (statComponents) {
        statComponents.textContent = Object.keys(AppState.components || {}).length;
    }
    if (statDeprecated) {
        const deprecatedCount = AppState.errors.filter(e => e.deprecated).length;
        statDeprecated.textContent = deprecatedCount;
    }
}

function switchBrowseTab(tabId) {
    // Update tab active states
    document.querySelectorAll('.browse-tab').forEach(tab => {
        tab.classList.toggle('active', tab.dataset.tab === tabId);
    });
    
    // Clear selection and hide detail panel when switching tabs
    AppState.activeCategory = null;
    AppState.activeCategoryType = null;
    hideDetail();
    
    // Re-filter to show all errors (no category filter)
    filterErrors();
    renderResults();
    
    // Build the appropriate list based on tab
    buildBrowseList(tabId);
}

function updateSidebar() {
    const activeTab = document.querySelector('.browse-tab.active');
    buildBrowseList(activeTab?.dataset.tab || 'components');
    updateStats();
}

function buildBrowseList(tabType = 'components') {
    if (!DOM.browseList) return;
    
    let items = [];
    
    switch (tabType) {
        case 'components':
            items = buildComponentList();
            break;
        case 'primaries':
            items = buildPrimaryList();
            break;
        case 'sequences':
            items = buildSequenceList();
            break;
        default:
            items = buildComponentList();
    }
    
    if (items.length === 0) {
        DOM.browseList.innerHTML = '<div class="browse-empty">No items to display</div>';
        return;
    }
    
    DOM.browseList.innerHTML = items.map(item => `
        <div class="browse-item ${AppState.activeCategory === item.id ? 'active' : ''}" 
             data-type="${tabType}" data-name="${escapeHtml(item.id)}">
            <span class="browse-item-name">${escapeHtml(item.displayName || item.name)}</span>
            <span class="browse-item-count">${item.count}</span>
        </div>
    `).join('');
    
    // Click handlers
    DOM.browseList.querySelectorAll('.browse-item').forEach(item => {
        item.addEventListener('click', () => {
            const id = item.dataset.name;
            const type = item.dataset.type;
            
            // Toggle active state
            if (AppState.activeCategory === id && AppState.activeCategoryType === type) {
                AppState.activeCategory = null;
                AppState.activeCategoryType = null;
                hideDetail();
            } else {
                AppState.activeCategory = id;
                AppState.activeCategoryType = type; // Track which type of category is selected
                showComponentDetail(id, type);
            }
            
            // Re-filter errors based on new category selection
            filterErrors();
            renderResults();
            
            // Update visual state
            DOM.browseList.querySelectorAll('.browse-item').forEach(i => {
                i.classList.toggle('active', i.dataset.name === AppState.activeCategory);
            });
        });
    });
}

function buildComponentList() {
    const components = new Map();
    
    AppState.filteredErrors.forEach(error => {
        const comp = error.component || getCategoryFromCode(error.code);
        if (!components.has(comp)) {
            components.set(comp, { id: comp, name: comp, count: 0 });
        }
        components.get(comp).count++;
    });
    
    // Merge with metadata if available
    Object.entries(AppState.components || {}).forEach(([id, meta]) => {
        if (components.has(id)) {
            components.get(id).name = meta.name || id;
        }
    });
    
    return Array.from(components.values()).sort((a, b) => b.count - a.count);
}

function buildPrimaryList() {
    const primaries = new Map();
    
    AppState.filteredErrors.forEach(error => {
        const prim = error.primary;
        if (!prim) return;
        
        if (!primaries.has(prim)) {
            primaries.set(prim, { id: prim, name: prim, count: 0 });
        }
        primaries.get(prim).count++;
    });
    
    // Merge with metadata if available
    Object.entries(AppState.primaries || {}).forEach(([id, meta]) => {
        if (primaries.has(id)) {
            primaries.get(id).name = meta.name || id;
        }
    });
    
    return Array.from(primaries.values()).sort((a, b) => b.count - a.count);
}

function buildSequenceList() {
    const sequences = new Map();
    
    AppState.filteredErrors.forEach(error => {
        const seq = error.sequence;
        if (seq === undefined || seq === null) return;
        
        // Use string version of sequence number as key
        const seqKey = String(seq);
        
        if (!sequences.has(seqKey)) {
            // Format: "001" padded number
            const paddedNum = String(seq).padStart(3, '0');
            sequences.set(seqKey, { id: seqKey, num: paddedNum, name: null, count: 0 });
        }
        sequences.get(seqKey).count++;
    });
    
    // Merge with metadata to get sequence names
    Object.entries(AppState.sequences || {}).forEach(([id, meta]) => {
        if (sequences.has(id)) {
            sequences.get(id).name = meta.name || null;
        }
    });
    
    // Build display name: "001 - MISSING" or just "001" if no name
    const result = Array.from(sequences.values()).map(item => ({
        ...item,
        displayName: item.name ? `${item.num} - ${item.name}` : item.num
    }));
    
    return result.sort((a, b) => b.count - a.count);
}

// Severity metadata with descriptions from WDP spec
const SEVERITY_INFO = {
    'E': { name: 'Error', emoji: '', priority: 8, blocking: true, tone: 'Negative', description: 'Invalid input, logic errors, or failures that prevent an operation from completing.' },
    'B': { name: 'Blocked', emoji: '🚫', priority: 7, blocking: true, tone: 'Negative', description: 'Operation blocked by an external condition (deadlock, I/O wait, resource unavailable).' },
    'C': { name: 'Critical', emoji: '🔥', priority: 6, blocking: false, tone: 'Negative', description: 'Severe conditions requiring immediate attention (data corruption, resource exhaustion). Execution may continue in degraded mode.' },
    'W': { name: 'Warning', emoji: '⚠️', priority: 5, blocking: false, tone: 'Negative', description: 'Potentially problematic situations that do not prevent operation (deprecated API, edge cases).' },
    'H': { name: 'Help', emoji: '💡', priority: 4, blocking: false, tone: 'Neutral', description: 'Helpful suggestions, tips, or guidance for users or developers.' },
    'S': { name: 'Success', emoji: '', priority: 3, blocking: false, tone: 'Positive', description: 'Operation completed successfully. Explicit positive feedback.' },
    'K': { name: 'Completed', emoji: '✔️', priority: 2, blocking: false, tone: 'Positive', description: 'Long-running task or phase has finished (builds, migrations, batch jobs).' },
    'I': { name: 'Info', emoji: 'ℹ️', priority: 1, blocking: false, tone: 'Neutral', description: 'Informational events, milestones, or status updates.' },
    'T': { name: 'Trace', emoji: '🔍', priority: 0, blocking: false, tone: 'Neutral', description: 'Detailed execution traces, debugging information, profiling, and performance measurements.' }
};

function hideDetail() {
    const detailSection = document.getElementById('detailSection');
    if (detailSection) {
        detailSection.style.display = 'none';
    }
}

function showSeverityDetail(severityChar) {
    const detailSection = document.getElementById('detailSection');
    const detailPanel = document.getElementById('detailPanel');
    if (!detailSection || !detailPanel) return;
    
    const info = SEVERITY_INFO[severityChar] || { 
        name: severityChar, 
        emoji: '',
        priority: '?',
        blocking: false,
        tone: 'Unknown',
        description: 'Unknown severity level.' 
    };
    
    // Find all errors with this severity
    const errors = AppState.filteredErrors.filter(e => {
        const sev = e.code.split('.')[0];
        return sev === severityChar;
    });
    
    // Clear active category since this is a special view
    AppState.activeCategory = null;
    document.querySelectorAll('.browse-item').forEach(i => i.classList.remove('active'));
    
    // Tone color class
    const toneClass = info.tone === 'Negative' ? 'tone-negative' : 
                      info.tone === 'Positive' ? 'tone-positive' : 'tone-neutral';
    
    detailPanel.innerHTML = `
        <div class="detail-back" onclick="hideDetail();"> Back</div>
        <div class="detail-type">Severity</div>
        <div class="detail-title severity">
            <span class="severity-emoji">${info.emoji}</span>
            <span>${severityChar} - ${escapeHtml(info.name)}</span>
        </div>
        <div class="detail-description">${escapeHtml(info.description)}</div>
        
        <div class="severity-meta">
            <span class="severity-meta-item">
                <span class="meta-label">Priority:</span>
                <span class="meta-value">${info.priority}</span>
            </span>
            <span class="severity-meta-item">
                <span class="meta-label">Blocking:</span>
                <span class="meta-value ${info.blocking ? 'blocking-yes' : 'blocking-no'}">${info.blocking ? 'Yes' : 'No'}</span>
            </span>
            <span class="severity-meta-item">
                <span class="meta-label">Tone:</span>
                <span class="meta-value ${toneClass}">${info.tone}</span>
            </span>
        </div>
        
        <div class="detail-section">
            <div class="detail-section-title">Error Codes (${errors.length})</div>
            <div class="detail-errors">
                ${errors.slice(0, 20).map(e => `
                    <span class="detail-error-link" onclick="searchFor('${e.code}')">${escapeHtml(e.code)}</span>
                `).join('')}
                ${errors.length > 20 ? `<span class="detail-more">+${errors.length - 20} more</span>` : ''}
            </div>
        </div>
    `;
    
    detailSection.style.display = 'block';
}

function showComponentDetail(id, type) {
    const detailSection = document.getElementById('detailSection');
    const detailPanel = document.getElementById('detailPanel');
    if (!detailSection || !detailPanel) return;
    
    // Get metadata based on type
    let meta = {};
    let errors = [];
    let typeLabel = 'Component';
    let titleClass = 'component';
    let displayTitle = id; // Default title is just the id
    
    if (type === 'components') {
        meta = AppState.components?.[id] || {};
        typeLabel = 'Component';
        titleClass = 'component';
        errors = AppState.filteredErrors.filter(e => 
            (e.component || getCategoryFromCode(e.code)) === id
        );
    } else if (type === 'primaries') {
        meta = AppState.primaries?.[id] || {};
        typeLabel = 'Primary';
        titleClass = 'primary';
        errors = AppState.filteredErrors.filter(e => e.primary === id);
    } else if (type === 'sequences') {
        meta = AppState.sequences?.[id] || {};
        typeLabel = 'Sequence';
        titleClass = 'sequence';
        errors = AppState.filteredErrors.filter(e => e.sequence == id);
        // Format sequence title as "021 - NOTFOUND" or just "021" if no name
        const paddedNum = String(id).padStart(3, '0');
        const seqName = meta.name || null;
        displayTitle = seqName ? `${paddedNum} - ${seqName}` : paddedNum;
    }
    
    const description = meta.description || '';
    const examples = meta.examples || [];
    const locations = meta.locations || [];
    
    detailPanel.innerHTML = `
        <div class="detail-back" onclick="hideDetail(); AppState.activeCategory = null; updateSidebar();">\u2190 Back</div>
        <div class="detail-type">${typeLabel}</div>
        <div class="detail-title ${titleClass}">${escapeHtml(displayTitle)}</div>
        ${description ? `<div class="detail-description">${escapeHtml(description)}</div>` : ''}
        
        ${examples.length ? `
            <div class="detail-section">
                <div class="detail-section-title">Examples</div>
                <div class="detail-list">
                    ${examples.map(ex => `<div class="detail-list-item">${escapeHtml(ex)}</div>`).join('')}
                </div>
            </div>
        ` : ''}
        
        <div class="detail-section">
            <div class="detail-section-title">Error Codes</div>
            <div class="detail-errors">
                ${errors.map(e => `
                    <span class="detail-error-link" onclick="searchFor('${e.code}')">${escapeHtml(e.code)}</span>
                `).join('')}
            </div>
        </div>
        
        ${locations.length ? `
            <div class="detail-section">
                <div class="detail-section-title">Locations</div>
                <div class="detail-list">
                    ${locations.map(loc => {
                        // Handle both string and object formats
                        const path = typeof loc === 'string' ? loc : (loc.path || loc.file || '');
                        const role = typeof loc === 'object' && loc.role ? ` <span class="location-role">(${loc.role})</span>` : '';
                        return `<div class="detail-list-item location-item">${escapeHtml(path)}${role}</div>`;
                    }).join('')}
                </div>
            </div>
        ` : ''}
    `;
    
    detailSection.style.display = 'block';
}

function searchFor(code) {
    if (DOM.searchInput) {
        // Clear all filters so search works across all errors
        AppState.activeCategory = null;
        AppState.activeTag = null;
        hideDetail();
        
        // Set search query
        DOM.searchInput.value = code;
        AppState.searchQuery = code;
        
        // Update UI
        filterErrors();
        renderResults();
        updateSidebar();
        renderActiveFilters();
    }
}

function selectError(code) {
    AppState.selectedErrorCode = code;
    
    // Scroll to and expand the card
    const card = document.querySelector(`.error-card[data-code="${code}"]`);
    if (card) {
        if (!AppState.expandedCards.has(code)) {
            toggleCard(code);
        }
        scrollToElement(card);
    }
}

function scrollToElement(element) {
    element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}