fix: update deployment source with enhanced ControlBase files
Copy enhanced ControlBase and control files to deployment source directory. This resolves the deployment cache issue where md-render --edit was using old control files instead of the new enhanced ControlBase architecture. Now all controls properly use the enhanced ControlBase with 5 behaviors: - Icon-only collapsed state - Expand/drag functionality - Bottom-left resize handle - Collapse button returns to original position - Header toggle for content visibility The enhanced control system is now fully deployed and functional.
This commit is contained in:
@@ -1,63 +1,479 @@
|
||||
/**
|
||||
* Debug Control - Displays debug information and system messages
|
||||
* Implements the Robustness Principle with Fail Fast mode support
|
||||
* 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)
|
||||
*/
|
||||
|
||||
class DebugControl {
|
||||
/**
|
||||
* 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() {
|
||||
this.control = Object.create(Control);
|
||||
this.control.config = {
|
||||
icon: '🪲',
|
||||
super();
|
||||
|
||||
// Configure for debug functionality
|
||||
this.config = {
|
||||
icon: '🐛',
|
||||
title: 'Debug',
|
||||
className: 'debug-control',
|
||||
defaultContent: 'Click to view debug information',
|
||||
ariaLabel: 'Debug Control',
|
||||
position: 'w'
|
||||
defaultContent: 'Debug information loading...',
|
||||
ariaLabel: 'Debug Information Control',
|
||||
position: 'w' // West positioning
|
||||
};
|
||||
|
||||
// Bind methods to control
|
||||
this.control.buildContent = () => {
|
||||
const content = this.control.element.querySelector('.control-content');
|
||||
const messages = window.MarkitectDebugSystem ?
|
||||
window.MarkitectDebugSystem.getMessages() : [];
|
||||
// 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();
|
||||
|
||||
content.innerHTML = `
|
||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
||||
<h4 style="margin-top: 0;">Debug Messages</h4>
|
||||
<div style="max-height: 200px; overflow-y: auto;">
|
||||
${messages.length > 0 ?
|
||||
messages.slice(-10).map(msg =>
|
||||
`<div style="margin-bottom: 0.5rem; padding: 0.3rem; background: #f8f9fa; border-radius: 3px;">
|
||||
<strong>[${msg.category}]</strong> ${msg.component}: ${msg.message}
|
||||
<div style="font-size: 0.7rem; color: #666;">${msg.displayTime}</div>
|
||||
</div>`
|
||||
).join('') :
|
||||
'<p>No debug messages yet</p>'
|
||||
}
|
||||
</div>
|
||||
<button onclick="if(window.MarkitectDebugSystem) window.MarkitectDebugSystem.clearMessages(); this.closest('.control-panel').querySelector('.control-content').innerHTML = '<div style="padding: 1rem; font-size: 0.8rem;"><h4 style="margin-top: 0;">Debug Messages</h4><p>Messages cleared</p></div>'"
|
||||
style="margin-top: 0.5rem; padding: 0.3rem 0.6rem; font-size: 0.7rem; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Clear Messages
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
this.control.isExpanded = true;
|
||||
};
|
||||
|
||||
this.control.toggle = () => {
|
||||
if (this.control.isExpanded) {
|
||||
this.control.element.querySelector('.control-content').style.display = 'none';
|
||||
this.control.isExpanded = false;
|
||||
} else {
|
||||
this.control.buildContent();
|
||||
this.control.element.querySelector('.control-content').style.display = 'block';
|
||||
}
|
||||
};
|
||||
this.initializeDebugCapture();
|
||||
}
|
||||
|
||||
createControl() {
|
||||
return this.control.createControl();
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the control content
|
||||
* Override of base class method to provide debug-specific functionality
|
||||
*/
|
||||
buildContent() {
|
||||
return this.safeOperation(() => {
|
||||
const content = this.element?.querySelector('.control-content');
|
||||
if (content) {
|
||||
content.innerHTML = `
|
||||
<div style="padding: 1rem; font-size: 0.8rem;">
|
||||
<h4 style="margin-top: 0; margin-bottom: 1rem;">Debug Information</h4>
|
||||
|
||||
${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>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Store reference to this control for onclick handlers
|
||||
this.element.debugControl = this;
|
||||
}
|
||||
}, null, 'buildContent');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
window.DebugControl = DebugControl;
|
||||
// Export for module systems or attach to global for direct usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = DebugControl;
|
||||
} else {
|
||||
window.DebugControl = DebugControl;
|
||||
}
|
||||
Reference in New Issue
Block a user