Base class architecture improvements: - Centralize all panel layout, styling, and behavior in ControlBase - Implement consistent generateContent() pattern for subclasses - Add proper flexbox layout with fixed header and scrollable content - Standardize title styling, positioning, and scroll behavior Panel layout fixes: - Fix content positioning to appear inside panels instead of floating above - Implement proper height management (expands with content up to browser height) - Add correct scroll boundaries with only content area scrolling - Position resize handle outside scroll area to avoid scrollbar interference Visual improvements: - Fix rounded border appearance with proper overflow handling - Ensure header respects panel corner radius - Add proper content margins and padding - Improve resize handle positioning and visibility Architecture standardization: - All panels now follow same base class pattern - Individual panels only provide configuration and content generation - Eliminate duplicate styling and layout code across controls - Consistent behavior across all panel types 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
480 lines
18 KiB
JavaScript
480 lines
18 KiB
JavaScript
/**
|
|
* DebugControl - System Debug Information and Message Display Control
|
|
*
|
|
* Provides comprehensive debugging capabilities including system message display,
|
|
* error tracking, performance monitoring, and development tools. Essential for
|
|
* troubleshooting and development workflows within the TestDrive-JSUI environment.
|
|
*
|
|
* Features:
|
|
* - Real-time debug message display with categorization
|
|
* - Error tracking with stack trace information
|
|
* - Performance metrics and timing measurements
|
|
* - System information display (browser, viewport, etc.)
|
|
* - Message filtering and search capabilities
|
|
* - Export functionality for debug logs
|
|
* - Integration with MarkitectDebugSystem
|
|
*
|
|
* Dependencies:
|
|
* - ControlBase (base control functionality)
|
|
* - MarkitectDebugSystem (optional, for enhanced debugging)
|
|
*/
|
|
|
|
/**
|
|
* DebugControl - Development and debugging information control
|
|
*
|
|
* This control serves as a central hub for all debugging activities,
|
|
* providing developers with essential information for troubleshooting
|
|
* and performance optimization.
|
|
*/
|
|
class DebugControl extends ControlBase {
|
|
constructor() {
|
|
super();
|
|
|
|
// Configure for debug functionality
|
|
this.config = {
|
|
icon: '🐛',
|
|
title: 'Debug',
|
|
className: 'debug-control',
|
|
defaultContent: 'Debug information loading...',
|
|
ariaLabel: 'Debug Information Control',
|
|
position: 'w' // West positioning
|
|
};
|
|
|
|
// Debug control state
|
|
this.messages = [];
|
|
this.maxMessages = 100;
|
|
this.messageFilter = 'all'; // 'all', 'error', 'warn', 'info', 'debug'
|
|
this.autoScroll = true;
|
|
this.isRecording = true;
|
|
this.startTime = Date.now();
|
|
this.performanceMarks = new Map();
|
|
|
|
this.initializeDebugCapture();
|
|
}
|
|
|
|
/**
|
|
* Initialize debug message capture
|
|
*/
|
|
initializeDebugCapture() {
|
|
return this.safeOperation(() => {
|
|
// Capture console messages
|
|
this.originalConsole = {
|
|
log: console.log,
|
|
error: console.error,
|
|
warn: console.warn,
|
|
info: console.info,
|
|
debug: console.debug
|
|
};
|
|
|
|
// Override console methods to capture messages
|
|
console.log = (...args) => {
|
|
this.originalConsole.log(...args);
|
|
this.addDebugMessage('LOG', args.join(' '), 'info');
|
|
};
|
|
|
|
console.error = (...args) => {
|
|
this.originalConsole.error(...args);
|
|
this.addDebugMessage('ERROR', args.join(' '), 'error');
|
|
};
|
|
|
|
console.warn = (...args) => {
|
|
this.originalConsole.warn(...args);
|
|
this.addDebugMessage('WARN', args.join(' '), 'warn');
|
|
};
|
|
|
|
console.info = (...args) => {
|
|
this.originalConsole.info(...args);
|
|
this.addDebugMessage('INFO', args.join(' '), 'info');
|
|
};
|
|
|
|
console.debug = (...args) => {
|
|
this.originalConsole.debug(...args);
|
|
this.addDebugMessage('DEBUG', args.join(' '), 'debug');
|
|
};
|
|
|
|
// Capture global errors
|
|
window.addEventListener('error', (event) => {
|
|
this.addDebugMessage('ERROR', `${event.message} at ${event.filename}:${event.lineno}`, 'error');
|
|
});
|
|
|
|
// Capture unhandled promise rejections
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
this.addDebugMessage('PROMISE_REJECT', `Unhandled promise rejection: ${event.reason}`, 'error');
|
|
});
|
|
|
|
}, null, 'initializeDebugCapture');
|
|
}
|
|
|
|
/**
|
|
* Add a debug message to the log
|
|
*/
|
|
addDebugMessage(category, message, level = 'info') {
|
|
return this.safeOperation(() => {
|
|
if (!this.isRecording) return;
|
|
|
|
const debugMessage = {
|
|
id: Date.now() + Math.random(),
|
|
timestamp: Date.now(),
|
|
category,
|
|
message,
|
|
level,
|
|
displayTime: new Date().toLocaleTimeString(),
|
|
relativeTime: Date.now() - this.startTime
|
|
};
|
|
|
|
this.messages.push(debugMessage);
|
|
|
|
// Limit message history
|
|
if (this.messages.length > this.maxMessages) {
|
|
this.messages.shift();
|
|
}
|
|
|
|
// Update display if visible
|
|
if (this.element && this.isExpanded) {
|
|
this.updateMessageDisplay();
|
|
}
|
|
|
|
}, null, 'addDebugMessage');
|
|
}
|
|
|
|
/**
|
|
* Get messages filtered by current filter setting
|
|
*/
|
|
getFilteredMessages() {
|
|
if (this.messageFilter === 'all') {
|
|
return this.messages;
|
|
}
|
|
return this.messages.filter(msg => msg.level === this.messageFilter);
|
|
}
|
|
|
|
/**
|
|
* Generate system information HTML
|
|
*/
|
|
generateSystemInfoHTML() {
|
|
return this.safeOperation(() => {
|
|
const systemInfo = {
|
|
userAgent: navigator.userAgent,
|
|
viewport: `${window.innerWidth}x${window.innerHeight}`,
|
|
screen: `${screen.width}x${screen.height}`,
|
|
colorDepth: screen.colorDepth,
|
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
language: navigator.language,
|
|
cookieEnabled: navigator.cookieEnabled,
|
|
onlineStatus: navigator.onLine ? 'Online' : 'Offline',
|
|
protocol: window.location.protocol,
|
|
memory: performance.memory ?
|
|
`Used: ${Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)}MB` :
|
|
'Not available'
|
|
};
|
|
|
|
return `
|
|
<div class="system-info" style="margin-bottom: 1rem; padding: 0.5rem; background: #f8f9fa; border-radius: 3px; font-size: 0.7rem;">
|
|
<strong>System Information:</strong><br>
|
|
<div style="margin-top: 0.3rem; line-height: 1.3;">
|
|
<div><strong>Viewport:</strong> ${systemInfo.viewport}</div>
|
|
<div><strong>Screen:</strong> ${systemInfo.screen}</div>
|
|
<div><strong>Memory:</strong> ${systemInfo.memory}</div>
|
|
<div><strong>Language:</strong> ${systemInfo.language}</div>
|
|
<div><strong>Status:</strong> ${systemInfo.onlineStatus}</div>
|
|
<div><strong>Protocol:</strong> ${systemInfo.protocol}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
}, '', 'generateSystemInfoHTML');
|
|
}
|
|
|
|
/**
|
|
* Generate performance metrics HTML
|
|
*/
|
|
generatePerformanceHTML() {
|
|
return this.safeOperation(() => {
|
|
const timing = performance.timing;
|
|
const navigation = performance.getEntriesByType('navigation')[0];
|
|
|
|
const metrics = {
|
|
pageLoad: timing.loadEventEnd - timing.navigationStart,
|
|
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
|
|
firstByte: timing.responseStart - timing.navigationStart,
|
|
uptime: Date.now() - this.startTime,
|
|
messagesCount: this.messages.length
|
|
};
|
|
|
|
return `
|
|
<div class="performance-info" style="margin-bottom: 1rem; padding: 0.5rem; background: #e7f3ff; border-radius: 3px; font-size: 0.7rem;">
|
|
<strong>Performance Metrics:</strong><br>
|
|
<div style="margin-top: 0.3rem; line-height: 1.3;">
|
|
<div><strong>Page Load:</strong> ${metrics.pageLoad}ms</div>
|
|
<div><strong>DOM Ready:</strong> ${metrics.domReady}ms</div>
|
|
<div><strong>First Byte:</strong> ${metrics.firstByte}ms</div>
|
|
<div><strong>Session Time:</strong> ${Math.round(metrics.uptime / 1000)}s</div>
|
|
<div><strong>Debug Messages:</strong> ${metrics.messagesCount}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
}, '', 'generatePerformanceHTML');
|
|
}
|
|
|
|
/**
|
|
* Generate debug messages HTML
|
|
*/
|
|
generateMessagesHTML() {
|
|
return this.safeOperation(() => {
|
|
const filteredMessages = this.getFilteredMessages();
|
|
|
|
if (filteredMessages.length === 0) {
|
|
return `
|
|
<div style="text-align: center; padding: 1rem; color: #666; font-style: italic;">
|
|
No ${this.messageFilter === 'all' ? '' : this.messageFilter + ' '}messages yet
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
const messagesHTML = filteredMessages.slice(-20).map(msg => {
|
|
const levelColors = {
|
|
error: '#dc3545',
|
|
warn: '#ffc107',
|
|
info: '#17a2b8',
|
|
debug: '#6c757d'
|
|
};
|
|
|
|
const backgroundColor = levelColors[msg.level] || '#6c757d';
|
|
const textColor = msg.level === 'warn' ? '#000' : '#fff';
|
|
|
|
return `
|
|
<div class="debug-message" style="margin-bottom: 0.5rem; padding: 0.3rem; background: #f8f9fa; border-left: 3px solid ${backgroundColor}; font-size: 0.7rem; border-radius: 0 3px 3px 0;">
|
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.2rem;">
|
|
<span style="background: ${backgroundColor}; color: ${textColor}; padding: 0.1rem 0.3rem; border-radius: 2px; font-size: 0.6rem; font-weight: bold;">
|
|
${msg.category}
|
|
</span>
|
|
<span style="color: #666; font-size: 0.6rem;">
|
|
${msg.displayTime}
|
|
</span>
|
|
</div>
|
|
<div style="word-break: break-word; line-height: 1.2;">
|
|
${msg.message}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
return `
|
|
<div class="messages-container" style="max-height: 200px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 3px; padding: 0.5rem; background: white;">
|
|
${messagesHTML}
|
|
</div>
|
|
`;
|
|
|
|
}, '<p>Error displaying messages</p>', 'generateMessagesHTML');
|
|
}
|
|
|
|
/**
|
|
* Generate control buttons HTML
|
|
*/
|
|
generateControlButtonsHTML() {
|
|
return `
|
|
<div class="debug-controls" style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem; margin: 0.5rem 0;">
|
|
<button onclick="this.closest('.debug-control').debugControl.clearMessages()"
|
|
style="padding: 0.3rem; font-size: 0.7rem; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
|
🗑️ Clear
|
|
</button>
|
|
|
|
<button onclick="this.closest('.debug-control').debugControl.exportMessages()"
|
|
style="padding: 0.3rem; font-size: 0.7rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
|
💾 Export
|
|
</button>
|
|
|
|
<button onclick="this.closest('.debug-control').debugControl.toggleRecording()"
|
|
style="padding: 0.3rem; font-size: 0.7rem; background: ${this.isRecording ? '#ffc107' : '#6c757d'}; color: ${this.isRecording ? '#000' : '#fff'}; border: none; border-radius: 3px; cursor: pointer;">
|
|
${this.isRecording ? '⏸️ Pause' : '▶️ Record'}
|
|
</button>
|
|
|
|
<button onclick="this.closest('.debug-control').debugControl.addTestMessage()"
|
|
style="padding: 0.3rem; font-size: 0.7rem; background: #17a2b8; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
|
🧪 Test
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Generate filter controls HTML
|
|
*/
|
|
generateFilterControlsHTML() {
|
|
const filters = ['all', 'error', 'warn', 'info', 'debug'];
|
|
|
|
const filterButtons = filters.map(filter => {
|
|
const isActive = this.messageFilter === filter;
|
|
return `
|
|
<button onclick="this.closest('.debug-control').debugControl.setMessageFilter('${filter}')"
|
|
style="padding: 0.2rem 0.4rem; margin-right: 0.2rem; font-size: 0.6rem; background: ${isActive ? '#007bff' : '#e9ecef'}; color: ${isActive ? 'white' : '#495057'}; border: none; border-radius: 2px; cursor: pointer;">
|
|
${filter.toUpperCase()}
|
|
</button>
|
|
`;
|
|
}).join('');
|
|
|
|
return `
|
|
<div style="margin-bottom: 0.5rem; padding: 0.3rem; background: #f1f3f4; border-radius: 3px;">
|
|
<div style="font-size: 0.7rem; margin-bottom: 0.3rem; color: #666;">Filter:</div>
|
|
${filterButtons}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Update the message display
|
|
*/
|
|
updateMessageDisplay() {
|
|
return this.safeOperation(() => {
|
|
const messagesContainer = this.element?.querySelector('.messages-container');
|
|
if (messagesContainer) {
|
|
const parent = messagesContainer.parentElement;
|
|
parent.innerHTML = this.generateMessagesHTML();
|
|
|
|
// Auto-scroll to bottom if enabled
|
|
if (this.autoScroll) {
|
|
const newContainer = parent.querySelector('.messages-container');
|
|
if (newContainer) {
|
|
newContainer.scrollTop = newContainer.scrollHeight;
|
|
}
|
|
}
|
|
}
|
|
}, null, 'updateMessageDisplay');
|
|
}
|
|
|
|
/**
|
|
* Clear all debug messages
|
|
*/
|
|
clearMessages() {
|
|
this.messages = [];
|
|
if (window.MarkitectDebugSystem) {
|
|
window.MarkitectDebugSystem.clearMessages();
|
|
}
|
|
this.buildContent();
|
|
}
|
|
|
|
/**
|
|
* Export debug messages to file
|
|
*/
|
|
exportMessages() {
|
|
return this.safeOperation(() => {
|
|
const exportData = {
|
|
timestamp: new Date().toISOString(),
|
|
session: {
|
|
startTime: new Date(this.startTime).toISOString(),
|
|
duration: Date.now() - this.startTime,
|
|
messageCount: this.messages.length
|
|
},
|
|
system: {
|
|
userAgent: navigator.userAgent,
|
|
viewport: `${window.innerWidth}x${window.innerHeight}`,
|
|
url: window.location.href
|
|
},
|
|
messages: this.messages
|
|
};
|
|
|
|
const dataStr = JSON.stringify(exportData, null, 2);
|
|
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
const url = URL.createObjectURL(dataBlob);
|
|
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `debug-log-${new Date().toISOString().split('T')[0]}.json`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
URL.revokeObjectURL(url);
|
|
this.addDebugMessage('EXPORT', 'Debug log exported successfully', 'info');
|
|
|
|
}, null, 'exportMessages');
|
|
}
|
|
|
|
/**
|
|
* Toggle message recording
|
|
*/
|
|
toggleRecording() {
|
|
this.isRecording = !this.isRecording;
|
|
this.buildContent();
|
|
this.addDebugMessage('CONTROL', `Recording ${this.isRecording ? 'started' : 'paused'}`, 'info');
|
|
}
|
|
|
|
/**
|
|
* Add a test message
|
|
*/
|
|
addTestMessage() {
|
|
const testMessages = [
|
|
{ category: 'TEST', message: 'This is a test info message', level: 'info' },
|
|
{ category: 'TEST', message: 'This is a test warning message', level: 'warn' },
|
|
{ category: 'TEST', message: 'This is a test error message', level: 'error' },
|
|
{ category: 'TEST', message: 'This is a test debug message', level: 'debug' }
|
|
];
|
|
|
|
const randomMessage = testMessages[Math.floor(Math.random() * testMessages.length)];
|
|
this.addDebugMessage(randomMessage.category, randomMessage.message, randomMessage.level);
|
|
}
|
|
|
|
/**
|
|
* Set message filter
|
|
*/
|
|
setMessageFilter(filter) {
|
|
this.messageFilter = filter;
|
|
this.buildContent();
|
|
}
|
|
|
|
/**
|
|
* Generate debug control content (called by base class buildContent)
|
|
*/
|
|
generateContent() {
|
|
return this.safeOperation(() => {
|
|
return `
|
|
${this.generateSystemInfoHTML()}
|
|
${this.generatePerformanceHTML()}
|
|
${this.generateFilterControlsHTML()}
|
|
${this.generateMessagesHTML()}
|
|
${this.generateControlButtonsHTML()}
|
|
|
|
<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #eee; font-size: 0.7rem; color: #666; text-align: center;">
|
|
Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} |
|
|
Filter: ${this.messageFilter.toUpperCase()} |
|
|
Messages: ${this.getFilteredMessages().length}/${this.messages.length}
|
|
</div>
|
|
`;
|
|
}, 'Error generating debug content', 'generateContent');
|
|
}
|
|
|
|
/**
|
|
* Override buildContent to add control reference
|
|
*/
|
|
buildContent() {
|
|
super.buildContent();
|
|
|
|
// Store reference to this control for onclick handlers
|
|
if (this.element) {
|
|
this.element.debugControl = this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean up resources when control is destroyed
|
|
*/
|
|
destroy() {
|
|
// Restore original console methods
|
|
if (this.originalConsole) {
|
|
console.log = this.originalConsole.log;
|
|
console.error = this.originalConsole.error;
|
|
console.warn = this.originalConsole.warn;
|
|
console.info = this.originalConsole.info;
|
|
console.debug = this.originalConsole.debug;
|
|
}
|
|
|
|
super.destroy();
|
|
}
|
|
}
|
|
|
|
// Export for module systems or attach to global for direct usage
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = DebugControl;
|
|
} else {
|
|
window.DebugControl = DebugControl;
|
|
} |