v0.12.1 1453d21

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?: unknown;
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
76      .querySelector("#clear-logs")
77      ?.addEventListener("click", () => this.clearLogs());
78    header
79      .querySelector("#close-debug")
80      ?.addEventListener("click", () => this.hide());
81  }
82
83  private setupKeyboardShortcut(): void {
84    document.addEventListener("keydown", (event) => {
85      // F12 to toggle debug console
86      if (event.key === "F12") {
87        event.preventDefault();
88        this.toggle();
89      }
90    });
91  }
92
93  private interceptConsole(): void {
94    const originalConsole = {
95      log: console.log,
96      warn: console.warn,
97      error: console.error,
98      info: console.info,
99      debug: console.debug,
100    };
101
102    console.log = (...args) => {
103      this.addLog("info", "frontend", "console", args.join(" "));
104      originalConsole.log(...args);
105    };
106
107    console.warn = (...args) => {
108      this.addLog("warn", "frontend", "console", args.join(" "));
109      originalConsole.warn(...args);
110    };
111
112    console.error = (...args) => {
113      this.addLog("error", "frontend", "console", args.join(" "));
114      originalConsole.error(...args);
115    };
116
117    console.info = (...args) => {
118      this.addLog("info", "frontend", "console", args.join(" "));
119      originalConsole.info(...args);
120    };
121
122    console.debug = (...args) => {
123      this.addLog("debug", "frontend", "console", args.join(" "));
124      originalConsole.debug(...args);
125    };
126  }
127
128  public addLog(
129    level: DebugLogEntry["level"],
130    source: DebugLogEntry["source"],
131    category: string,
132    message: string,
133    data?: unknown,
134  ): void {
135    const entry: DebugLogEntry = {
136      timestamp: new Date().toISOString().split("T")[1]?.split(".")[0] || "", // HH:MM:SS format
137      level,
138      source,
139      category,
140      message,
141      data,
142    };
143
144    this.logs.push(entry);
145
146    // Keep only the last maxLogs entries
147    if (this.logs.length > this.maxLogs) {
148      this.logs.shift();
149    }
150
151    this.updateDisplay();
152  }
153
154  private updateDisplay(): void {
155    if (!this.logsContainer) return;
156
157    const logHTML = this.logs
158      .map((log) => {
159        const levelColor = {
160          info: "#00ff00",
161          warn: "#ffff00",
162          error: "#ff0000",
163          debug: "#00ffff",
164        }[log.level];
165
166        const sourceIcon = log.source === "backend" ? "🖥️" : "🌐";
167
168        return `
169        <div style="margin-bottom: 2px; font-size: 11px;">
170          <span style="color: #888;">[${log.timestamp}]</span>
171          <span style="color: ${levelColor};">${sourceIcon} ${log.level.toUpperCase()}</span>
172          <span style="color: #aaa;">[${log.category}]</span>
173          <span style="color: #fff;">${log.message}</span>
174          ${log.data ? `<pre style="color: #ccc; font-size: 10px; margin: 2px 0;">${JSON.stringify(log.data, null, 2)}</pre>` : ""}
175        </div>
176      `;
177      })
178      .join("");
179
180    this.logsContainer.innerHTML = logHTML;
181
182    // Auto-scroll to bottom
183    this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
184  }
185
186  public logKeyboardEvent(event: KeyboardEvent): void {
187    const modifiers = [];
188    if (event.ctrlKey) modifiers.push("Ctrl");
189    if (event.shiftKey) modifiers.push("Shift");
190    if (event.altKey) modifiers.push("Alt");
191    if (event.metaKey) modifiers.push("Meta");
192
193    const shortcut =
194      modifiers.length > 0 ? `${modifiers.join("+")}+${event.key}` : event.key;
195
196    this.addLog("info", "frontend", "keyboard", `Key pressed: ${shortcut}`, {
197      key: event.key,
198      code: event.code,
199      modifiers,
200      target: event.target?.constructor.name,
201    });
202  }
203
204  public logMouseEvent(event: MouseEvent): void {
205    const buttonNames: Record<number, string> = {
206      0: "Left",
207      1: "Middle",
208      2: "Right",
209      3: "Back",
210      4: "Forward",
211    };
212
213    this.addLog(
214      "info",
215      "frontend",
216      "mouse",
217      `Mouse ${event.type}: ${buttonNames[event.button] || `Button${event.button}`}`,
218      {
219        button: event.button,
220        clientX: event.clientX,
221        clientY: event.clientY,
222        target: event.target?.constructor.name,
223      },
224    );
225  }
226
227  public logServiceCall(
228    serviceName: string,
229    methodName: string,
230    params: unknown[],
231    result?: unknown,
232    error?: Error,
233  ): void {
234    if (error) {
235      this.addLog(
236        "error",
237        "frontend",
238        "service",
239        `${serviceName}.${methodName}() failed`,
240        {
241          params,
242          error: error.message,
243        },
244      );
245    } else {
246      this.addLog(
247        "info",
248        "frontend",
249        "service",
250        `${serviceName}.${methodName}() called`,
251        {
252          params,
253          result,
254        },
255      );
256    }
257  }
258
259  public logBackendMessage(message: string, data?: unknown): void {
260    this.addLog("info", "backend", "service", message, data);
261  }
262
263  public show(): void {
264    if (this.debugElement) {
265      this.debugElement.style.display = "block";
266      this.isVisible = true;
267      this.addLog("info", "frontend", "debug", "Debug console opened");
268    }
269  }
270
271  public hide(): void {
272    if (this.debugElement) {
273      this.debugElement.style.display = "none";
274      this.isVisible = false;
275    }
276  }
277
278  public toggle(): void {
279    if (this.isVisible) {
280      this.hide();
281    } else {
282      this.show();
283    }
284  }
285
286  public clearLogs(): void {
287    this.logs = [];
288    this.updateDisplay();
289    this.addLog("info", "frontend", "debug", "Logs cleared");
290  }
291
292  public isDebugVisible(): boolean {
293    return this.isVisible;
294  }
295}

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