generated from coulomb/repo-seed
Phase 1-3 Complete: - ✅ Installed Rollup with all required plugins - ✅ Created rollup.config.js for UMD, ESM, CJS builds - ✅ Created src/index.js entry point - ✅ Created src/styles.css for CSS bundling - ✅ Added ES6 exports to debug-system.js - ✅ Excluded Node.js-only html-generator.js from bundle Build outputs: - dist/testdrive-jsui.js (217KB UMD) - dist/testdrive-jsui.min.js (107KB minified!) - dist/testdrive-jsui.esm.js (199KB ES Module) - dist/testdrive-jsui.cjs.js (199KB CommonJS) - dist/testdrive-jsui.css (minified, all styles inlined) All builds include source maps for debugging.
293 lines
9.4 KiB
JavaScript
293 lines
9.4 KiB
JavaScript
/**
|
|
* 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();
|
|
|
|
// ES6 export for bundler
|
|
export { MarkitectDebugSystem }; |