/** * Independent Debug System for Markitect * Uses IndexedDB for persistence and provides selection-based filtering */ class MarkitectDebugSystem { constructor() { this.db = null; this.messages = []; this.maxMessages = 1000; this.isEnabled = true; this.subscribers = []; // Selection and filtering system this.selectionCriteria = { includeDocumentEvents: true, includeSystemEvents: false, includeControlEvents: true, includeEditingEvents: true, includeNavigationEvents: false, includedHeadings: new Set(), // Track which document headings to monitor excludedSources: new Set(['ContentsControl', 'DocumentNavigator']) }; this.init(); } // Initialize IndexedDB for persistence async init() { return new Promise((resolve, reject) => { const request = indexedDB.open('MarkitectDebugDB', 1); request.onerror = () => reject(request.error); request.onsuccess = () => { this.db = request.result; this.loadMessages().then(resolve); }; request.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains('messages')) { const store = db.createObjectStore('messages', { keyPath: 'id', autoIncrement: true }); store.createIndex('timestamp', 'timestamp', { unique: false }); store.createIndex('category', 'category', { unique: false }); } }; }); } // Add a debug message with selection filtering async addMessage(message, category = 'INFO', source = 'System', context = {}) { // Check if this message should be included based on selection criteria if (!this.shouldIncludeMessage(message, category, source, context)) { return null; } const messageObj = { timestamp: new Date().toISOString(), message: String(message), category: category.toUpperCase(), source: String(source), context: context || {}, id: null // Will be set by IndexedDB }; // Store in IndexedDB if available if (this.db) { try { await this.saveMessage(messageObj); } catch (error) { console.warn('Failed to save debug message to IndexedDB:', error); } } // Store in memory this.messages.unshift(messageObj); // Limit memory storage if (this.messages.length > this.maxMessages) { this.messages = this.messages.slice(0, this.maxMessages); } // Notify subscribers this.notifySubscribers(messageObj); // Console output for development const consoleMethod = category.toLowerCase() === 'error' ? 'error' : category.toLowerCase() === 'warning' ? 'warn' : 'log'; console[consoleMethod](`[${source}] ${message}`, context); return messageObj; } // Selection filtering logic shouldIncludeMessage(message, category, source, context) { if (!this.isEnabled) return false; const eventType = context.eventType || 'UNKNOWN'; const criteria = this.selectionCriteria; // Check event type filters switch (eventType.toUpperCase()) { case 'DOCUMENT': if (!criteria.includeDocumentEvents) return false; break; case 'SYSTEM': if (!criteria.includeSystemEvents) return false; break; case 'CONTROL': if (!criteria.includeControlEvents) return false; break; case 'EDITING': if (!criteria.includeEditingEvents) return false; break; case 'NAVIGATION': if (!criteria.includeNavigationEvents) return false; break; } // Check excluded sources if (criteria.excludedSources.has(source)) { return false; } // Check heading-specific filtering if (context.sectionId && criteria.includedHeadings.size > 0) { const sectionElement = document.getElementById(context.sectionId); if (sectionElement) { const heading = sectionElement.querySelector('h1, h2, h3, h4, h5, h6'); if (heading && !criteria.includedHeadings.has(heading.textContent.trim())) { return false; } } } return true; } // Save message to IndexedDB async saveMessage(messageObj) { return new Promise((resolve, reject) => { const transaction = this.db.transaction(['messages'], 'readwrite'); const store = transaction.objectStore('messages'); const request = store.add(messageObj); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } // Load messages from IndexedDB async loadMessages() { if (!this.db) return []; return new Promise((resolve, reject) => { const transaction = this.db.transaction(['messages'], 'readonly'); const store = transaction.objectStore('messages'); const request = store.getAll(); request.onsuccess = () => { this.messages = request.result.reverse(); // Most recent first resolve(this.messages); }; request.onerror = () => reject(request.error); }); } // Clear all messages async clearMessages() { this.messages = []; if (this.db) { return new Promise((resolve, reject) => { const transaction = this.db.transaction(['messages'], 'readwrite'); const store = transaction.objectStore('messages'); const request = store.clear(); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } } // Get filtered messages getMessages(filter = {}) { let filteredMessages = [...this.messages]; if (filter.category) { filteredMessages = filteredMessages.filter(msg => msg.category.toLowerCase() === filter.category.toLowerCase() ); } if (filter.source) { filteredMessages = filteredMessages.filter(msg => msg.source.toLowerCase().includes(filter.source.toLowerCase()) ); } if (filter.since) { const sinceDate = new Date(filter.since); filteredMessages = filteredMessages.filter(msg => new Date(msg.timestamp) >= sinceDate ); } if (filter.limit) { filteredMessages = filteredMessages.slice(0, filter.limit); } return filteredMessages; } // Update selection criteria updateSelectionCriteria(updates) { Object.assign(this.selectionCriteria, updates); this.notifySubscribers({ type: 'criteria-updated', criteria: this.selectionCriteria }); } // Add heading to monitoring addHeadingToMonitoring(headingText) { this.selectionCriteria.includedHeadings.add(headingText); } // Remove heading from monitoring removeHeadingFromMonitoring(headingText) { this.selectionCriteria.includedHeadings.delete(headingText); } // Scan document for available headings scanDocumentHeadings() { const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); return Array.from(headings) .map(h => h.textContent.trim()) .filter(text => text.length > 0 && !text.toLowerCase().includes('control')); } // Subscribe to debug messages subscribe(callback) { this.subscribers.push(callback); return () => { const index = this.subscribers.indexOf(callback); if (index > -1) { this.subscribers.splice(index, 1); } }; } // Notify all subscribers notifySubscribers(message) { this.subscribers.forEach(callback => { try { callback(message); } catch (error) { console.error('Debug subscriber error:', error); } }); } // Toggle debug system setEnabled(enabled) { this.isEnabled = enabled; this.addMessage( `Debug system ${enabled ? 'enabled' : 'disabled'}`, 'INFO', 'DebugSystem', { eventType: 'SYSTEM' } ); } // Get statistics getStats() { const stats = { total: this.messages.length, byCategory: {}, bySource: {}, enabled: this.isEnabled, criteria: { ...this.selectionCriteria } }; this.messages.forEach(msg => { stats.byCategory[msg.category] = (stats.byCategory[msg.category] || 0) + 1; stats.bySource[msg.source] = (stats.bySource[msg.source] || 0) + 1; }); return stats; } } // Initialize and expose globally window.MarkitectDebugSystem = new MarkitectDebugSystem();