v0.8.0 0fa6785

gui.ts RawHistoryFolder

1/**
2 * Unified GUI Bundle Entry Point
3 *
4 * Single entry point for all WebKit GUI functionality.
5 * Loads immediately and provides modular initialization system.
6 */
7
8// Import all GUI modules
9import { initializeToast, type ToastConfig } from './modules/toast';
10import { initializeOmnibox, type OmniboxInitConfig } from './modules/omnibox';
11import { keyboardService, type KeyboardService } from '../lib/keyboard';
12// Note: color-scheme module is loaded separately at document-start by WebKit
13
14// Global interface for the unified GUI system
15interface DumberGUI {
16  initializeToast: (config?: ToastConfig) => Promise<void>;
17  initializeOmnibox: (config?: OmniboxInitConfig) => Promise<void>;
18  keyboard: KeyboardService;
19  isReady: boolean;
20}
21
22// (legacy suggestions normalization removed; omnibox now fetches suggestions directly via API)
23
24// DOM readiness utility - handles all document ready states
25function whenDOMReady(callback: () => void) {
26  if (document.readyState === 'loading') {
27    document.addEventListener('DOMContentLoaded', callback, { once: true });
28  } else if (document.readyState === 'interactive' && !document.body) {
29    // Interactive but no body yet, wait a bit more
30    setTimeout(() => whenDOMReady(callback), 10);
31  } else {
32    // DOM ready and body exists
33    callback();
34  }
35}
36
37// Install a page-world bridge so Go can call window.__dumber_omnibox_suggestions()
38// Page-world bridge is injected by WebKit at document-start; no-op here to avoid duplicates.
39
40// Create global namespace
41declare global {
42  interface Window {
43    __dumber_gui?: DumberGUI;
44    __dumber_gui_ready?: boolean;
45    __dumber_showToast?: (message: string, duration?: number, type?: 'info' | 'success' | 'error') => number | void;
46    __dumber_showZoomToast?: (level: number) => void;
47    __dumber_keyboard?: KeyboardService;
48    // Legacy compatibility functions for Go bridge
49    __dumber_toggle?: () => void;
50    __dumber_find_open?: (query?: string) => void;
51    __dumber_find_close?: () => void;
52    __dumber_find_query?: (query: string) => void;
53  }
54}
55
56// Prevent multiple initialization with enhanced logging
57if (!window.__dumber_gui_ready) {
58  // Skip initialization in iframes - GUI only needed in main frame
59  if (window.self !== window.top) {
60    console.log('🚫 Dumber GUI skipped in iframe');
61  } else {
62    window.__dumber_gui_ready = true;
63
64  // Initialize Svelte GUI systems immediately
65  whenDOMReady(async () => {
66    try {
67  // Page-world bridge is injected by WebKit; avoid duplicate injection here
68
69      // Initialize toast system first
70      await initializeToast();
71
72      // Verify toast functions are available
73      if (typeof window.__dumber_showToast !== 'function') {
74        throw new Error('Toast functions not exposed after initialization');
75      }
76
77      // Initialize omnibox system
78      await initializeOmnibox();
79
80      console.log('✅ All GUI systems initialized successfully');
81    } catch (e) {
82      console.error('❌ Failed to initialize Svelte toast system:', e);
83
84      // Enhanced fallback with better error recovery
85      window.__dumber_showToast = (message: string, duration: number = 2500) => {
86        // Remove existing toasts to prevent overlap
87        document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
88
89        const toast = document.createElement('div');
90        toast.className = 'dumber-fallback-toast';
91        toast.textContent = message;
92        toast.style.cssText = `
93          position: fixed;
94          bottom: 20px;
95          right: 20px;
96          background: rgba(0,0,0,0.9);
97          color: white;
98          padding: 12px 16px;
99          border-radius: 6px;
100          z-index: 2147483647;
101          font-family: system-ui, -apple-system, sans-serif;
102          font-size: 14px;
103          opacity: 0;
104          transform: translateX(100%);
105          transition: all 0.3s ease;
106          max-width: 300px;
107          word-wrap: break-word;
108        `;
109
110        if (document.body) {
111          document.body.appendChild(toast);
112
113          // Animate in
114          requestAnimationFrame(() => {
115            toast.style.opacity = '1';
116            toast.style.transform = 'translateX(0)';
117          });
118
119          // Animate out and remove
120          setTimeout(() => {
121            toast.style.opacity = '0';
122            toast.style.transform = 'translateX(100%)';
123            setTimeout(() => toast.remove(), 300);
124          }, duration);
125
126        }
127      };
128
129      window.__dumber_showZoomToast = (level: number) => {
130        const percentage = Math.round(level * 100);
131        window.__dumber_showToast!(`Zoom: ${percentage}%`, 1500);
132      };
133
134      // Expose other required functions
135      window.__dumber_dismissToast = () => {
136        document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
137      };
138
139      window.__dumber_clearToasts = () => {
140        document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
141      };
142    }
143  });
144
145  // Expose keyboard service globally for Go bridge
146  window.__dumber_keyboard = keyboardService;
147
148  // Initialize global keyboard and mouse event listeners for the keyboard service
149  whenDOMReady(() => {
150    // Global keyboard event listener (capture phase for high priority)
151    document.addEventListener('keydown', (event) => {
152      keyboardService.handleKeyboardEvent(event);
153    }, true);
154
155    // Global mouse event listener for navigation buttons
156    document.addEventListener('mousedown', (event) => {
157      keyboardService.handleMouseEvent(event);
158    }, true);
159
160    console.log('✅ KeyboardService initialized with global listeners');
161
162    // Listen to bridge keyboard events from page-world and forward to KeyboardService
163    document.addEventListener('dumber:key', (e: Event) => {
164      const detail = (e as CustomEvent).detail || {};
165      if (detail && typeof detail.shortcut === 'string') {
166        keyboardService.handleNativeShortcut(detail.shortcut);
167      }
168    });
169
170    // Suggestions are now fetched via API directly by the omnibox component; no page-bridge handlers needed
171  });
172
173  // Legacy compatibility functions for existing Go code
174  window.__dumber_toggle = () => {
175    try {
176      console.log('🎯 __dumber_toggle called');
177      console.log('🔧 Omnibox API available:', !!window.__dumber_omnibox);
178      if (window.__dumber_omnibox?.toggle) {
179        console.log('✅ Using Svelte omnibox toggle');
180        window.__dumber_omnibox.toggle();
181        return;
182      }
183
184      throw new Error('Omnibox toggle requested but API is unavailable');
185    } catch (error) {
186      console.error('❌ Error in __dumber_toggle:', error);
187    }
188  };
189
190  window.__dumber_find_open = (query?: string) => {
191    try {
192      if (window.__dumber_omnibox?.open) {
193        window.__dumber_omnibox.open('find', query);
194        return;
195      }
196
197      throw new Error('Omnibox find requested but API is unavailable');
198    } catch (error) {
199      console.error('❌ Error in __dumber_find_open:', error);
200    }
201  };
202
203  window.__dumber_find_close = () => {
204    if (window.__dumber_omnibox?.close) {
205      window.__dumber_omnibox.close();
206      return;
207    }
208
209    const error = new Error('Omnibox close requested but API is unavailable');
210    console.error('❌ Error in __dumber_find_close:', error);
211  };
212
213  // Find query function that Go uses to set find query
214  window.__dumber_find_query = (query: string) => {
215    if (window.__dumber_omnibox?.findQuery) {
216      window.__dumber_omnibox.findQuery(query);
217    }
218  };
219
220  // Create the global GUI object
221  window.__dumber_gui = {
222    initializeToast: async (config?: ToastConfig) => {
223      return await initializeToast(config);
224    },
225
226    initializeOmnibox: async (config?: OmniboxInitConfig) => {
227      return await initializeOmnibox(config);
228    },
229
230    keyboard: keyboardService,
231
232    isReady: true
233  };
234
235  }
236}

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