fix: resolve Quick Prompt hotkey registration and startup resilience

- Fix accelerator: convert 'Super' to 'Meta' in toElectronAccelerator()
  since Electron uses 'Meta' for the Win/Cmd key, not 'Super'
- Defer quick prompt window creation until after workspace loads so a
  failure cannot block the main window from rendering
- Extract registerQuickPromptIpcHandlers into a separate function called
  after the quick prompt window is created
- Wrap quick prompt window creation in try/catch for resilience

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-13 11:38:26 +02:00
co-authored by Copilot
parent b038019954
commit 6714b93bed
3 changed files with 60 additions and 45 deletions
+13 -4
View File
@@ -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<void> {
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<void> {
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, () => {
+44 -38
View File
@@ -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<QuickPromptSettings>) => 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<QuickPromptSettings>) => 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);
}
});
}
+3 -3
View File
@@ -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');
}