Files
SuperCharged-Claude-Code-Up…/public/claude-ide/bug-tracker.js
uroma a45b71e1e4 Implement terminal approval UI system
Phase 1: Backend approval tracking
- Add PendingApprovalsManager class to track pending approvals
- Add approval-request, approval-response, approval-expired WebSocket handlers
- Add requestApproval() method to ClaudeCodeService
- Add event forwarding for approval requests

Phase 2: Frontend approval card component
- Create approval-card.js with interactive UI
- Create approval-card.css with styled component
- Add Approve, Custom Instructions, Reject buttons
- Add expandable custom command input

Phase 3: Wire up approval flow end-to-end
- Add handleApprovalRequest, handleApprovalConfirmed, handleApprovalExpired handlers
- Add detectApprovalRequest() to parse AI approval request patterns
- Integrate approval card into WebSocket message flow
- Route approval responses based on source (server vs AI conversational)

This allows the AI agent to request command approval through a clean
UI instead of confusing conversational text responses.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-21 14:24:13 +00:00

696 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Real-Time Bug Tracker Dashboard
* Shows all auto-detected errors and fix progress
*/
(function() {
'use strict';
// Error state storage
window.bugTracker = {
errors: [],
fixesInProgress: new Map(),
fixesCompleted: new Map(),
activityLog: [], // New: stores AI activity stream
addError(error) {
// Skip 'info' type errors - they're for learning, not bugs
if (error.type === 'info') {
console.log('[BugTracker] Skipping info-type error:', error.message);
return null;
}
const errorId = this.generateErrorId(error);
const existingError = this.errors.find(e => e.id === errorId);
if (!existingError) {
const errorWithMeta = {
id: errorId,
...error,
detectedAt: new Date().toISOString(),
status: 'detected',
count: 1,
activity: [] // Activity for this error
};
this.errors.push(errorWithMeta);
this.updateDashboard();
// Trigger auto-fix notification
if (typeof showErrorNotification === 'function') {
showErrorNotification(errorWithMeta);
}
} else {
existingError.count++;
existingError.lastSeen = new Date().toISOString();
}
return errorId;
},
startFix(errorId) {
const error = this.errors.find(e => e.id === errorId);
if (error) {
error.status = 'fixing';
error.fixStartedAt = new Date().toISOString();
this.fixesInProgress.set(errorId, true);
this.addActivity(errorId, '🤖', 'AI agent started analyzing error...');
this.updateDashboard();
}
},
// Add activity to error's activity log
addActivity(errorId, icon, message, type = 'info') {
// Add to global activity log
this.activityLog.unshift({
errorId,
icon,
message,
type,
timestamp: new Date().toISOString()
});
// Keep only last 50 global activities
if (this.activityLog.length > 50) {
this.activityLog = this.activityLog.slice(0, 50);
}
// Add to specific error's activity
const error = this.errors.find(e => e.id === errorId);
if (error) {
if (!error.activity) error.activity = [];
error.activity.unshift({
icon,
message,
type,
timestamp: new Date().toISOString()
});
if (error.activity.length > 20) {
error.activity = error.activity.slice(0, 20);
}
}
this.updateActivityStream();
},
// Update the activity stream display
updateActivityStream() {
const stream = document.getElementById('activity-stream');
if (!stream) return;
// Show last 10 activities globally
const recentActivities = this.activityLog.slice(0, 10);
stream.innerHTML = recentActivities.map(activity => {
const timeAgo = this.getTimeAgo(activity.timestamp);
return `
<div class="activity-item activity-${activity.type}">
<span class="activity-icon">${activity.icon}</span>
<span class="activity-message">${this.escapeHtml(activity.message)}</span>
<span class="activity-time">${timeAgo}</span>
</div>
`;
}).join('');
},
completeFix(errorId, fixDetails) {
const error = this.errors.find(e => e.id === errorId);
if (error) {
error.status = 'fixed';
error.fixedAt = new Date().toISOString();
error.fixDetails = fixDetails;
this.fixesInProgress.delete(errorId);
this.fixesCompleted.set(errorId, true);
this.updateDashboard();
}
},
generateErrorId(error) {
const parts = [
error.type,
error.message.substring(0, 50),
error.filename || 'unknown'
];
return btoa(parts.join('::')).substring(0, 20);
},
updateDashboard() {
const dashboard = document.getElementById('bug-tracker-dashboard');
if (!dashboard) return;
const content = dashboard.querySelector('#bug-tracker-content');
const stats = dashboard.querySelector('#bug-tracker-stats');
if (!content) return;
// Update stats
if (stats) {
const totalErrors = this.errors.length;
const activeErrors = this.errors.filter(e => e.status === 'detected').length;
const fixingErrors = this.errors.filter(e => e.status === 'fixing').length;
const fixedErrors = this.errors.filter(e => e.status === 'fixed').length;
stats.innerHTML = `
<div class="stat-item">
<span>Total:</span>
<span class="stat-value">${totalErrors}</span>
</div>
<div class="stat-item" style="color: #ff6b6b;">
<span>🔴 Active:</span>
<span class="stat-value">${activeErrors}</span>
</div>
<div class="stat-item" style="color: #ffa94d;">
<span>🔧 Fixing:</span>
<span class="stat-value">${fixingErrors}</span>
</div>
<div class="stat-item" style="color: #51cf66;">
<span>✅ Fixed:</span>
<span class="stat-value">${fixedErrors}</span>
</div>
`;
}
// Sort errors: fixing first, then detected, then fixed
const sortedErrors = [...this.errors].sort((a, b) => {
const statusOrder = { 'fixing': 0, 'detected': 1, 'fixed': 2 };
return statusOrder[a.status] - statusOrder[b.status];
});
content.innerHTML = this.renderErrors(sortedErrors);
},
renderErrors(errors) {
if (errors.length === 0) {
return `
<div class="bug-tracker-empty">
<div class="empty-icon">✨</div>
<div class="empty-title">No bugs detected!</div>
<div class="empty-subtitle">The code is running smoothly</div>
</div>
`;
}
return errors.map(error => this.renderError(error)).join('');
},
renderError(error) {
const statusIcons = {
'detected': '🔴',
'fixing': '🔧',
'fixed': '✅'
};
const statusClasses = {
'detected': 'status-detected',
'fixing': 'status-fixing',
'fixed': 'status-fixed'
};
const timeAgo = this.getTimeAgo(error.detectedAt || error.timestamp);
return `
<div class="bug-item ${statusClasses[error.status]}" data-error-id="${error.id}">
<div class="bug-header">
<span class="bug-status">${statusIcons[error.status]} ${error.status}</span>
<span class="bug-time">${timeAgo}</span>
${error.count > 1 ? `<span class="bug-count">×${error.count}</span>` : ''}
</div>
<div class="bug-message">${this.escapeHtml(error.message.substring(0, 100))}${error.message.length > 100 ? '...' : ''}</div>
${error.filename ? `<div class="bug-location">📄 ${error.filename.split('/').pop()}:${error.line || ''}</div>` : ''}
${error.fixDetails ? `<div class="bug-fix-details">✨ ${error.fixDetails}</div>` : ''}
${error.status === 'detected' ? `
<button class="bug-fix-btn" onclick="window.bugTracker.triggerManualFix('${error.id}')">
🤖 Fix Now
</button>
` : ''}
</div>
`;
},
triggerManualFix(errorId) {
const error = this.errors.find(e => e.id === errorId);
if (!error) {
console.error('[BugTracker] Error not found:', errorId);
return;
}
// Report to server to trigger fix
fetch('/claude/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...error,
manualTrigger: true
})
})
.then(res => {
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
})
.then(data => {
console.log('[BugTracker] Manual fix triggered:', data);
// Update error status to 'fixing'
this.startFix(errorId);
// Show success feedback
if (typeof showToast === 'function') {
showToast('Auto-fix agent triggered', 'success');
}
})
.catch(err => {
console.error('[BugTracker] Failed to trigger fix:', err);
if (typeof showToast === 'function') {
showToast('Failed to trigger auto-fix', 'error');
}
});
},
getTimeAgo(timestamp) {
const now = new Date();
const then = new Date(timestamp);
const diffMs = now - then;
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
if (diffSecs < 60) return `${diffSecs}s ago`;
if (diffMins < 60) return `${diffMins}m ago`;
return `${Math.floor(diffMins / 60)}h ago`;
},
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
},
toggle() {
const dashboard = document.getElementById('bug-tracker-dashboard');
if (dashboard) {
dashboard.classList.toggle('visible');
dashboard.classList.toggle('hidden');
}
}
};
// Create dashboard UI
function createDashboard() {
// Create toggle button
const toggleBtn = document.createElement('button');
toggleBtn.id = 'bug-tracker-toggle';
toggleBtn.className = 'bug-tracker-toggle';
toggleBtn.innerHTML = `
<span class="toggle-icon">🐛</span>
<span class="toggle-badge" id="bug-count-badge">0</span>
`;
toggleBtn.onclick = () => window.bugTracker.toggle();
// Create dashboard
const dashboard = document.createElement('div');
dashboard.id = 'bug-tracker-dashboard';
dashboard.className = 'bug-tracker-dashboard hidden';
dashboard.innerHTML = `
<div class="bug-tracker-header">
<div class="bug-tracker-title">
<span class="title-icon">🤖</span>
<span>AI Auto-Fix Tracker</span>
</div>
<button class="bug-tracker-close" onclick="window.bugTracker.toggle()">×</button>
</div>
<div class="activity-stream-header">
<span class="activity-title">🔴 Live Activity Feed</span>
</div>
<div id="activity-stream" class="activity-stream"></div>
<div class="bug-tracker-stats" id="bug-tracker-stats"></div>
<div id="bug-tracker-content" class="bug-tracker-content"></div>
`;
// Add styles
const style = document.createElement('style');
style.id = 'bug-tracker-styles';
style.textContent = `
.bug-tracker-toggle {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
cursor: pointer;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.bug-tracker-toggle:hover {
transform: scale(1.1);
box-shadow: 0 6px 30px rgba(102, 126, 234, 0.6);
}
.toggle-icon {
font-size: 24px;
}
.toggle-badge {
position: absolute;
top: -5px;
right: -5px;
background: #ff6b6b;
color: white;
font-size: 12px;
font-weight: bold;
padding: 2px 8px;
border-radius: 10px;
min-width: 20px;
text-align: center;
}
.bug-tracker-dashboard {
position: fixed;
top: 50%;
right: 20px;
transform: translateY(-50%);
width: 400px;
max-height: 80vh;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 12px;
box-shadow: 0 10px 60px rgba(0, 0, 0, 0.5);
z-index: 9998;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
.bug-tracker-dashboard.hidden {
display: none;
}
.bug-tracker-dashboard.visible {
display: flex;
}
.bug-tracker-header {
padding: 16px 20px;
border-bottom: 1px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
}
.bug-tracker-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: 600;
color: #e0e0e0;
}
.bug-tracker-close {
background: none;
border: none;
color: #888;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.bug-tracker-close:hover {
color: #e0e0e0;
}
.bug-tracker-content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.bug-item {
background: #252525;
border: 1px solid #333;
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
transition: all 0.2s ease;
}
.bug-item:hover {
border-color: #4a9eff;
}
.bug-item.status-fixing {
border-color: #ffa94d;
background: #2a2520;
}
.bug-item.status-fixed {
border-color: #51cf66;
opacity: 0.7;
}
.bug-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.bug-status {
font-size: 12px;
font-weight: 600;
padding: 4px 8px;
border-radius: 4px;
}
.status-detected {
background: rgba(255, 107, 107, 0.2);
color: #ff6b6b;
}
.status-fixing {
background: rgba(255, 169, 77, 0.2);
color: #ffa94d;
}
.status-fixed {
background: rgba(81, 207, 102, 0.2);
color: #51cf66;
}
.bug-time {
font-size: 11px;
color: #888;
}
.bug-count {
background: #ff6b6b;
color: white;
font-size: 10px;
font-weight: bold;
padding: 2px 6px;
border-radius: 10px;
}
.bug-message {
color: #e0e0e0;
font-size: 13px;
margin-bottom: 8px;
}
.bug-location {
color: #888;
font-size: 11px;
font-family: monospace;
}
.bug-fix-details {
color: #51cf66;
font-size: 12px;
margin-top: 8px;
padding: 8px;
background: rgba(81, 207, 102, 0.1);
border-radius: 4px;
}
.bug-fix-btn {
width: 100%;
padding: 8px;
background: linear-gradient(135deg, #4a9eff 0%, #a78bfa 100%);
border: none;
border-radius: 6px;
color: white;
font-size: 13px;
font-weight: 600;
cursor: pointer;
margin-top: 8px;
}
.bug-fix-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(74, 158, 255, 0.4);
}
.bug-tracker-empty {
text-align: center;
padding: 40px 20px;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
}
.empty-title {
font-size: 18px;
font-weight: 600;
color: #e0e0e0;
margin-bottom: 8px;
}
.empty-subtitle {
font-size: 14px;
color: #888;
}
.activity-stream-header {
padding: 12px 20px;
border-bottom: 1px solid #333;
background: rgba(255, 107, 107, 0.05);
}
.activity-title {
font-size: 13px;
font-weight: 600;
color: #ff6b6b;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.activity-stream {
max-height: 200px;
overflow-y: auto;
padding: 12px;
background: #0d0d0d;
border-bottom: 1px solid #333;
}
.activity-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
margin-bottom: 6px;
background: #1a1a1a;
border-radius: 6px;
border-left: 3px solid #4a9eff;
transition: all 0.2s ease;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.activity-item:hover {
background: #252525;
border-left-color: #a78bfa;
}
.activity-item.activity-error {
border-left-color: #ff6b6b;
background: rgba(255, 107, 107, 0.05);
}
.activity-item.activity-success {
border-left-color: #51cf66;
background: rgba(81, 207, 102, 0.05);
}
.activity-item.activity-warning {
border-left-color: #ffa94d;
background: rgba(255, 169, 77, 0.05);
}
.activity-icon {
font-size: 16px;
flex-shrink: 0;
}
.activity-message {
flex: 1;
font-size: 12px;
color: #e0e0e0;
line-height: 1.4;
}
.activity-time {
font-size: 10px;
color: #888;
flex-shrink: 0;
}
.bug-tracker-stats {
padding: 12px 20px;
border-bottom: 1px solid #333;
display: flex;
gap: 20px;
font-size: 12px;
background: #151515;
}
.stat-item {
display: flex;
align-items: center;
gap: 6px;
color: #888;
}
.stat-value {
font-weight: 600;
color: #e0e0e0;
}
`;
document.head.appendChild(style);
document.body.appendChild(toggleBtn);
document.body.appendChild(dashboard);
// Auto-update error count badge
setInterval(() => {
const badge = document.getElementById('bug-count-badge');
if (badge) {
const activeErrors = window.bugTracker.errors.filter(e => e.status !== 'fixed').length;
badge.textContent = activeErrors;
badge.style.display = activeErrors > 0 ? 'block' : 'none';
}
}, 1000);
}
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createDashboard);
} else {
createDashboard();
}
console.log('[BugTracker] Real-time bug tracker initialized');
})();