waddling-errors 0.7.3

Structured, secure-by-default diagnostic codes for distributed systems with no_std and role-based documentation
Documentation
/* ============================================
   VISIBILITY MANAGEMENT
   Role-based visibility switching
   ============================================ */

function initVisibility() {
    // Get document role from class or text content
    const roleEl = document.querySelector('.role-badge');
    if (!roleEl) {
        console.log('[Visibility] No role badge found');
        return;
    }
    
    // Try to detect role from class first (more reliable)
    let docRole = 'public';
    if (roleEl.classList.contains('role-internal')) {
        docRole = 'internal';
    } else if (roleEl.classList.contains('role-developer')) {
        docRole = 'developer';
    } else if (roleEl.classList.contains('role-public')) {
        docRole = 'public';
    } else {
        // Fallback: check text content
        const text = roleEl.textContent?.toLowerCase() || '';
        if (text.includes('internal')) {
            docRole = 'internal';
        } else if (text.includes('developer') || text.includes('dev')) {
            docRole = 'developer';
        }
    }
    
    AppState.documentRole = docRole;
    AppState.viewingAs = null;
    
    console.log('[Visibility] Document role:', docRole);
    
    // Don't show visibility row for public docs
    if (AppState.documentRole === 'public') {
        if (DOM.visibilityRow) {
            DOM.visibilityRow.style.display = 'none';
        }
        return;
    }
    
    // Build visibility switcher
    buildVisibilitySwitcher();
    updateVisibilityDisplay();
}

function buildVisibilitySwitcher() {
    if (!DOM.visibilityRow) return;
    
    const docRole = AppState.documentRole;
    const options = getAvailableVisibilityOptions(docRole);
    
    // Role icons
    const roleIcons = {
        'internal': '🔒',
        'developer': '👨‍💻',
        'public': '📘'
    };
    
    DOM.visibilityRow.innerHTML = `
        <span class="visibility-label">👁 View as</span>
        <div class="visibility-switcher" id="visibilitySwitcher">
            <button class="visibility-current ${docRole}" aria-haspopup="listbox" aria-expanded="false">
                <span class="vis-icon">${roleIcons[docRole] || '📘'}</span>
                <span class="vis-label">${capitalize(docRole)}</span>
            </button>
            <div class="visibility-dropdown" role="listbox">
                ${options.map(opt => `
                    <button class="visibility-option ${opt}${opt === docRole ? ' selected' : ''}" 
                            data-visibility="${opt}" role="option">
                        <span class="vis-icon">${roleIcons[opt] || '📘'}</span>
                        <span>${capitalize(opt)}</span>
                    </button>
                `).join('')}
            </div>
        </div>
        <span class="viewing-counter" id="viewingCounter"></span>
        <button class="visibility-reset" id="visibilityReset" style="display: none;">
            <span></span> Reset to ${capitalize(docRole)}
        </button>
    `;
    
    // Store references
    const switcher = DOM.visibilityRow.querySelector('.visibility-switcher');
    const currentBtn = switcher.querySelector('.visibility-current');
    
    // Toggle dropdown
    currentBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        switcher.classList.toggle('open');
        currentBtn.setAttribute('aria-expanded', switcher.classList.contains('open'));
    });
    
    // Option clicks
    switcher.querySelectorAll('.visibility-option').forEach(opt => {
        opt.addEventListener('click', () => selectVisibility(opt.dataset.visibility));
    });
    
    // Reset button
    const resetBtn = DOM.visibilityRow.querySelector('.visibility-reset');
    if (resetBtn) {
        resetBtn.addEventListener('click', resetVisibility);
    }
    
    // Close dropdown on outside click
    document.addEventListener('click', (e) => {
        if (!switcher.contains(e.target)) {
            switcher.classList.remove('open');
            currentBtn.setAttribute('aria-expanded', 'false');
        }
    });
}

function getAvailableVisibilityOptions(role) {
    switch (role) {
        case 'internal':
            return ['internal', 'developer', 'public'];
        case 'developer':
            return ['developer', 'public'];
        case 'public':
            return ['public'];
        default:
            return ['public'];
    }
}

function selectVisibility(visibility) {
    const docRole = AppState.documentRole;
    
    // Set viewingAs (null if same as native role)
    AppState.viewingAs = (visibility === docRole) ? null : visibility;
    
    // Clear any active category/filter since the data set is changing
    AppState.activeCategory = null;
    AppState.activeTag = null;
    hideDetail();
    
    // Close dropdown
    const switcher = DOM.visibilityRow?.querySelector('.visibility-switcher');
    if (switcher) {
        switcher.classList.remove('open');
    }
    
    // Update display
    updateVisibilityDisplay();
    
    // Re-filter and render
    filterErrors();
    renderResults();
    updateSidebar();
}

function updateVisibilityDisplay() {
    if (!DOM.visibilityRow) return;
    
    const switcher = DOM.visibilityRow.querySelector('.visibility-switcher');
    if (!switcher) return;
    
    const currentBtn = switcher.querySelector('.visibility-current');
    const effective = getEffectiveVisibility();
    
    // Role icons
    const roleIcons = {
        'internal': '🔒',
        'developer': '👨‍💻',
        'public': '📘'
    };
    
    // Update button class and label
    currentBtn.className = `visibility-current ${effective}`;
    currentBtn.innerHTML = `
        <span class="vis-icon">${roleIcons[effective] || '📘'}</span>
        <span class="vis-label">${capitalize(effective)}</span>
    `;
    
    // Update selected option
    switcher.querySelectorAll('.visibility-option').forEach(opt => {
        opt.classList.toggle('selected', opt.dataset.visibility === effective);
    });
    
    // Update viewing counter
    updateViewingCounter();
    
    // Show/hide reset button
    const resetBtn = DOM.visibilityRow.querySelector('.visibility-reset');
    if (resetBtn) {
        resetBtn.style.display = AppState.viewingAs ? 'inline-flex' : 'none';
    }
}

function updateViewingCounter() {
    const counter = document.getElementById('viewingCounter');
    if (!counter) return;
    
    const visible = AppState.filteredErrors.length;
    const total = AppState.errors.length;
    
    counter.innerHTML = `Viewing <strong>${visible}</strong> of <strong>${total}</strong> ${plural(total, 'error')}`;
}

function resetVisibility() {
    AppState.viewingAs = null;
    
    // Clear any active category/filter since the data set is changing
    AppState.activeCategory = null;
    AppState.activeTag = null;
    hideDetail();
    
    updateVisibilityDisplay();
    filterErrors();
    renderResults();
    updateSidebar();
}

function capitalize(str) {
    if (!str) return '';
    return str.charAt(0).toUpperCase() + str.slice(1);
}