v0.6.0 5c50dd3

debug-console.ts RawHistoryFolder

1export interface DebugLogEntry {
2  timestamp: string;
3  level: 'info' | 'warn' | 'error' | 'debug';
4  source: 'frontend' | 'backend';
5  category: string;
6  message: string;
7  data?: any;
8}
9
10export class DebugConsoleService {
11  private logs: DebugLogEntry[] = [];
12  private maxLogs = 1000;
13  private isVisible = false;
14  private debugElement: HTMLElement | null = null;
15  private logsContainer: HTMLElement | null = null;
16
17  constructor() {
18    this.createDebugUI();
19    this.setupKeyboardShortcut();
20    this.interceptConsole();
21  }
22
23  private createDebugUI(): void {
24    // Create debug panel
25    this.debugElement = document.createElement('div');
26    this.debugElement.id = 'debug-console';
27    this.debugElement.style.cssText = `
28      position: fixed;
29      top: 0;
30      right: 0;
31      width: 50%;
32      height: 100%;
33      background: rgba(0, 0, 0, 0.95);
34      color: #00ff00;
35      font-family: 'Courier New', monospace;
36      font-size: 12px;
37      z-index: 10000;
38      display: none;
39      border-left: 2px solid #00ff00;
40    `;
41
42    // Create header
43    const header = document.createElement('div');
44    header.style.cssText = `
45      padding: 10px;
46      background: rgba(0, 255, 0, 0.1);
47      border-bottom: 1px solid #00ff00;
48      font-weight: bold;
49      display: flex;
50      justify-content: space-between;
51      align-items: center;
52    `;
53    header.innerHTML = `
54      <span>🐛 DUMBER DEBUG CONSOLE</span>
55      <div style="font-size: 10px;">
56        <span style="color: #888; margin-right: 10px;">F11: Inspect Services | F12: Toggle</span>
57        <button id="clear-logs" style="background: none; border: 1px solid #00ff00; color: #00ff00; padding: 2px 8px; margin-right: 5px; cursor: pointer;">Clear</button>
58        <button id="close-debug" style="background: none; border: 1px solid #00ff00; color: #00ff00; padding: 2px 8px; cursor: pointer;">Close</button>
59      </div>
60    `;
61
62    // Create logs container
63    this.logsContainer = document.createElement('div');
64    this.logsContainer.style.cssText = `
65      height: calc(100% - 50px);
66      overflow-y: auto;
67      padding: 10px;
68    `;
69
70    this.debugElement.appendChild(header);
71    this.debugElement.appendChild(this.logsContainer);
72    document.body.appendChild(this.debugElement);
73
74    // Setup button handlers
75    header.querySelector('#clear-logs')?.addEventListener('click', () => this.clearLogs());
76    header.querySelector('#close-debug')?.addEventListener('click', () => this.hide());
77  }
78
79  private setupKeyboardShortcut(): void {
80    document.addEventListener('keydown', (event) => {
81      // F12 to toggle debug console
82      if (event.key === 'F12') {
83        event.preventDefault();
84        this.toggle();
85      }
86    });
87  }
88
89  private interceptConsole(): void {
90    const originalConsole = {
91      log: console.log,
92      warn: console.warn,
93      error: console.error,
94      info: console.info,
95      debug: console.debug
96    };
97
98    console.log = (...args) => {
99      this.addLog('info', 'frontend', 'console', args.join(' '));
100      originalConsole.log(...args);
101    };
102
103    console.warn = (...args) => {
104      this.addLog('warn', 'frontend', 'console', args.join(' '));
105      originalConsole.warn(...args);
106    };
107
108    console.error = (...args) => {
109      this.addLog('error', 'frontend', 'console', args.join(' '));
110      originalConsole.error(...args);
111    };
112
113    console.info = (...args) => {
114      this.addLog('info', 'frontend', 'console', args.join(' '));
115      originalConsole.info(...args);
116    };
117
118    console.debug = (...args) => {
119      this.addLog('debug', 'frontend', 'console', args.join(' '));
120      originalConsole.debug(...args);
121    };
122  }
123
124  public addLog(level: DebugLogEntry['level'], source: DebugLogEntry['source'], category: string, message: string, data?: any): void {
125    const entry: DebugLogEntry = {
126      timestamp: new Date().toISOString().split('T')[1].split('.')[0], // HH:MM:SS format
127      level,
128      source,
129      category,
130      message,
131      data
132    };
133
134    this.logs.push(entry);
135    
136    // Keep only the last maxLogs entries
137    if (this.logs.length > this.maxLogs) {
138      this.logs.shift();
139    }
140
141    this.updateDisplay();
142  }
143
144  private updateDisplay(): void {
145    if (!this.logsContainer) return;
146
147    const logHTML = this.logs.map(log => {
148      const levelColor = {
149        info: '#00ff00',
150        warn: '#ffff00', 
151        error: '#ff0000',
152        debug: '#00ffff'
153      }[log.level];
154
155      const sourceIcon = log.source === 'backend' ? '🖥️' : '🌐';
156
157      return `
158        <div style="margin-bottom: 2px; font-size: 11px;">
159          <span style="color: #888;">[${log.timestamp}]</span>
160          <span style="color: ${levelColor};">${sourceIcon} ${log.level.toUpperCase()}</span>
161          <span style="color: #aaa;">[${log.category}]</span>
162          <span style="color: #fff;">${log.message}</span>
163          ${log.data ? `<pre style="color: #ccc; font-size: 10px; margin: 2px 0;">${JSON.stringify(log.data, null, 2)}</pre>` : ''}
164        </div>
165      `;
166    }).join('');
167
168    this.logsContainer.innerHTML = logHTML;
169    
170    // Auto-scroll to bottom
171    this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
172  }
173
174  public logKeyboardEvent(event: KeyboardEvent): void {
175    const modifiers = [];
176    if (event.ctrlKey) modifiers.push('Ctrl');
177    if (event.shiftKey) modifiers.push('Shift');
178    if (event.altKey) modifiers.push('Alt');
179    if (event.metaKey) modifiers.push('Meta');
180
181    const shortcut = modifiers.length > 0 ? `${modifiers.join('+')}+${event.key}` : event.key;
182    
183    this.addLog('info', 'frontend', 'keyboard', `Key pressed: ${shortcut}`, {
184      key: event.key,
185      code: event.code,
186      modifiers: modifiers,
187      target: event.target?.constructor.name
188    });
189  }
190
191  public logMouseEvent(event: MouseEvent): void {
192    const buttonNames = {
193      0: 'Left',
194      1: 'Middle', 
195      2: 'Right',
196      3: 'Back',
197      4: 'Forward'
198    };
199
200    this.addLog('info', 'frontend', 'mouse', `Mouse ${event.type}: ${buttonNames[event.button] || `Button${event.button}`}`, {
201      button: event.button,
202      clientX: event.clientX,
203      clientY: event.clientY,
204      target: event.target?.constructor.name
205    });
206  }
207
208  public logServiceCall(serviceName: string, methodName: string, params: any[], result?: any, error?: Error): void {
209    if (error) {
210      this.addLog('error', 'frontend', 'service', `${serviceName}.${methodName}() failed`, {
211        params,
212        error: error.message
213      });
214    } else {
215      this.addLog('info', 'frontend', 'service', `${serviceName}.${methodName}() called`, {
216        params,
217        result
218      });
219    }
220  }
221
222  public logBackendMessage(message: string, data?: any): void {
223    this.addLog('info', 'backend', 'service', message, data);
224  }
225
226  public show(): void {
227    if (this.debugElement) {
228      this.debugElement.style.display = 'block';
229      this.isVisible = true;
230      this.addLog('info', 'frontend', 'debug', 'Debug console opened');
231    }
232  }
233
234  public hide(): void {
235    if (this.debugElement) {
236      this.debugElement.style.display = 'none';
237      this.isVisible = false;
238    }
239  }
240
241  public toggle(): void {
242    if (this.isVisible) {
243      this.hide();
244    } else {
245      this.show();
246    }
247  }
248
249  public clearLogs(): void {
250    this.logs = [];
251    this.updateDisplay();
252    this.addLog('info', 'frontend', 'debug', 'Logs cleared');
253  }
254
255  public isDebugVisible(): boolean {
256    return this.isVisible;
257  }
258}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close