diff --git a/src/main/index.ts b/src/main/index.ts index bff94db..cd7511d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,7 +1,7 @@ import electron from 'electron'; import type { BrowserWindow as BrowserWindowType } from 'electron'; -import { registerIpcHandlers } from '@main/ipc/registerIpcHandlers'; +import { registerIpcHandlers, registerQuickPromptIpcHandlers } from '@main/ipc/registerIpcHandlers'; import { AryxAppService } from '@main/AryxAppService'; import { AutoUpdateService } from '@main/services/autoUpdater'; import { GlobalHotkeyService } from '@main/services/globalHotkey'; @@ -29,8 +29,10 @@ async function bootstrap(): Promise { autoUpdateService = new AutoUpdateService({ isPackaged: app.isPackaged }); mainWindow = createMainWindow(); - quickPromptWindow = createQuickPromptWindow(); - registerIpcHandlers(mainWindow, appService, autoUpdateService, quickPromptWindow); + + // Defer quick prompt window creation — create it lazily when workspace is ready + // so a failure here cannot block the main window from loading. + registerIpcHandlers(mainWindow, appService, autoUpdateService); // Start workspace loading in parallel — don't block window from showing. // The renderer fetches the workspace via its own IPC call after mount. @@ -57,7 +59,14 @@ async function bootstrap(): Promise { systemTray?.updateRunningCount(updatedWorkspace); }); - // Register global hotkey for Quick Prompt + // Create quick prompt window and register global hotkey + try { + quickPromptWindow = createQuickPromptWindow(); + registerQuickPromptIpcHandlers(mainWindow!, appService!, quickPromptWindow); + } catch (err) { + console.error('[aryx] Failed to create quick prompt window:', err); + } + globalHotkeyService = new GlobalHotkeyService(); const hotkeySettings = workspace.settings.quickPrompt ?? createDefaultQuickPromptSettings(); globalHotkeyService.register(hotkeySettings, () => { diff --git a/src/main/ipc/registerIpcHandlers.ts b/src/main/ipc/registerIpcHandlers.ts index dbac5f9..406bcbf 100644 --- a/src/main/ipc/registerIpcHandlers.ts +++ b/src/main/ipc/registerIpcHandlers.ts @@ -71,7 +71,6 @@ export function registerIpcHandlers( window: BrowserWindow, service: AryxAppService, autoUpdateService: AutoUpdateService, - quickPromptWindow?: BrowserWindow, ): void { window.on('focus', () => { if (service.isGitAutoRefreshEnabled()) { @@ -354,9 +353,39 @@ export function registerIpcHandlers( window.webContents.send(ipcChannels.terminalExit, info); }); - // --- Quick Prompt IPC --- + // --- Quick Prompt IPC (window-independent) --- - // Track the active quick prompt session so events can be routed + ipcMain.handle(ipcChannels.quickPromptGetCapabilities, async () => { + const capabilities = await service.describeSidecarCapabilities(); + const settings = service.getQuickPromptSettings(); + const models = buildAvailableModelCatalog(capabilities.models); + return { + models, + defaultModel: settings.defaultModel, + defaultReasoningEffort: settings.defaultReasoningEffort, + }; + }); + + ipcMain.handle( + ipcChannels.quickPromptSetSettings, + (_event, settings: Partial) => service.setQuickPromptSettings(settings), + ); + + ipcMain.handle( + ipcChannels.quickPromptGetSettings, + () => service.getQuickPromptSettings(), + ); +} + +/** + * Register IPC handlers that depend on the quick prompt BrowserWindow. + * Called separately after the quick prompt window is created (deferred from bootstrap). + */ +export function registerQuickPromptIpcHandlers( + mainWindow: BrowserWindow, + service: AryxAppService, + quickPromptWindow: BrowserWindow, +): void { let quickPromptSessionId: string | undefined; ipcMain.handle(ipcChannels.quickPromptSend, async (_event, input: QuickPromptSendInput) => { @@ -392,12 +421,12 @@ export function registerIpcHandlers( await service.deleteSession(quickPromptSessionId); quickPromptSessionId = undefined; } - if (quickPromptWindow) hideQuickPromptWindow(quickPromptWindow); + hideQuickPromptWindow(quickPromptWindow); }); ipcMain.handle(ipcChannels.quickPromptClose, async () => { quickPromptSessionId = undefined; - if (quickPromptWindow) hideQuickPromptWindow(quickPromptWindow); + hideQuickPromptWindow(quickPromptWindow); }); ipcMain.handle(ipcChannels.quickPromptContinueInAryx, async () => { @@ -405,42 +434,19 @@ export function registerIpcHandlers( await service.selectSession(quickPromptSessionId); quickPromptSessionId = undefined; } - if (quickPromptWindow) hideQuickPromptWindow(quickPromptWindow); + hideQuickPromptWindow(quickPromptWindow); // Show and focus the main window - if (!window.isDestroyed()) { - if (window.isMinimized()) window.restore(); - window.show(); - window.focus(); + if (!mainWindow.isDestroyed()) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.show(); + mainWindow.focus(); } }); - ipcMain.handle(ipcChannels.quickPromptGetCapabilities, async () => { - const capabilities = await service.describeSidecarCapabilities(); - const settings = service.getQuickPromptSettings(); - const models = buildAvailableModelCatalog(capabilities.models); - return { - models, - defaultModel: settings.defaultModel, - defaultReasoningEffort: settings.defaultReasoningEffort, - }; - }); - - ipcMain.handle( - ipcChannels.quickPromptSetSettings, - (_event, settings: Partial) => service.setQuickPromptSettings(settings), - ); - - ipcMain.handle( - ipcChannels.quickPromptGetSettings, - () => service.getQuickPromptSettings(), - ); - // Route session events to the quick prompt window - if (quickPromptWindow) { - service.on('session-event', (event) => { - if (event.sessionId === quickPromptSessionId && !quickPromptWindow.isDestroyed()) { - quickPromptWindow.webContents.send(ipcChannels.quickPromptSessionEvent, event); - } - }); - } + service.on('session-event', (event) => { + if (event.sessionId === quickPromptSessionId && !quickPromptWindow.isDestroyed()) { + quickPromptWindow.webContents.send(ipcChannels.quickPromptSessionEvent, event); + } + }); } diff --git a/src/main/services/globalHotkey.ts b/src/main/services/globalHotkey.ts index 1da525e..f00379e 100644 --- a/src/main/services/globalHotkey.ts +++ b/src/main/services/globalHotkey.ts @@ -51,9 +51,9 @@ export class GlobalHotkeyService { /** * Convert our portable hotkey string (e.g. "Super+Shift+A") to an Electron - * accelerator string. Electron uses "Super" on all platforms, which maps to - * Cmd on macOS and Win on Windows/Linux. + * accelerator string. Electron uses "Meta" which maps to Cmd on macOS and + * Win key on Windows/Linux. */ function toElectronAccelerator(hotkey: string): string { - return hotkey; + return hotkey.replace(/Super/gi, 'Meta'); }