From 7396a62c79444c7e0e2a93168a1a53a3ab9ff0d2 Mon Sep 17 00:00:00 2001 From: Zane Staggs Date: Fri, 19 Dec 2025 17:18:41 -0800 Subject: [PATCH 1/3] fix deeplink recipe launch cold start --- ui/desktop/src/main.ts | 64 +++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 6f442ccd969c..1ebaab8e0778 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -227,6 +227,8 @@ if (process.platform !== 'darwin') { let firstOpenWindow: BrowserWindow; let pendingDeepLink: string | null = null; +// Track if open-url already handled window creation during cold start +let openUrlHandledLaunch = false; async function handleProtocolUrl(url: string) { if (!url) return; @@ -305,19 +307,24 @@ let windowDeeplinkURL: string | null = null; app.on('open-url', async (_event, url) => { if (process.platform !== 'win32') { const parsedUrl = new URL(url); - const recentDirs = loadRecentDirs(); - const openDir = recentDirs.length > 0 ? recentDirs[0] : null; // Handle bot/recipe URLs by directly creating a new window console.log('[Main] Received open-url event:', url); if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { console.log('[Main] Detected bot/recipe URL, creating new chat window'); + openUrlHandledLaunch = true; const deeplinkData = parseRecipeDeeplink(url); if (deeplinkData) { windowDeeplinkURL = url; } const scheduledJobId = parsedUrl.searchParams.get('scheduledJob'); + // Wait for app to be ready before creating window (critical for cold start) + await app.whenReady(); + + const recentDirs = loadRecentDirs(); + const openDir = recentDirs.length > 0 ? recentDirs[0] : null; + // Create a new window directly await createChat( app, @@ -332,26 +339,38 @@ app.on('open-url', async (_event, url) => { deeplinkData?.parameters ); windowDeeplinkURL = null; - return; // Skip the rest of the handler + return; } - // For non-bot URLs, continue with normal handling + // For non bot/recipe URLs (extension, sessions), store the deep link and create window pendingDeepLink = url; + console.log('[Main] Stored pending deep link for processing after React ready:', url); + + // Wait for app to be ready before creating window (critical for cold start) + await app.whenReady(); + + const recentDirs = loadRecentDirs(); + const openDir = recentDirs.length > 0 ? recentDirs[0] : null; const existingWindows = BrowserWindow.getAllWindows(); if (existingWindows.length > 0) { firstOpenWindow = existingWindows[0]; if (firstOpenWindow.isMinimized()) firstOpenWindow.restore(); firstOpenWindow.focus(); + // For existing windows, send the message directly (React is already ready) + if (parsedUrl.hostname === 'extension') { + firstOpenWindow.webContents.send('add-extension', pendingDeepLink); + pendingDeepLink = null; + } else if (parsedUrl.hostname === 'sessions') { + firstOpenWindow.webContents.send('open-shared-session', pendingDeepLink); + pendingDeepLink = null; + } } else { + // Cold start: mark that we're handling the launch, create window + // The pending deep link will be processed when react-ready fires + openUrlHandledLaunch = true; firstOpenWindow = await createChat(app, undefined, openDir || undefined); } - - if (parsedUrl.hostname === 'extension') { - firstOpenWindow.webContents.send('add-extension', pendingDeepLink); - } else if (parsedUrl.hostname === 'sessions') { - firstOpenWindow.webContents.send('open-shared-session', pendingDeepLink); - } } }); @@ -1167,9 +1186,22 @@ ipcMain.on('react-ready', (event) => { pendingInitialMessages.delete(windowId); } - if (pendingDeepLink) { + if (pendingDeepLink && window) { log.info('Processing pending deep link:', pendingDeepLink); - handleProtocolUrl(pendingDeepLink); + try { + const parsedUrl = new URL(pendingDeepLink); + if (parsedUrl.hostname === 'extension') { + log.info('Sending add-extension IPC to ready window'); + window.webContents.send('add-extension', pendingDeepLink); + } else if (parsedUrl.hostname === 'sessions') { + log.info('Sending open-shared-session IPC to ready window'); + window.webContents.send('open-shared-session', pendingDeepLink); + } + pendingDeepLink = null; + } catch (error) { + log.error('Error processing pending deep link:', error); + pendingDeepLink = null; + } } else { log.info('No pending deep link to process'); } @@ -1891,7 +1923,13 @@ async function appMain() { const { dirPath } = parseArgs(); - await createNewWindow(app, dirPath); + // Only create a new window if open-url didn't already handle the launch + // This prevents duplicate windows when launching via deep link on cold start + if (!openUrlHandledLaunch) { + await createNewWindow(app, dirPath); + } else { + log.info('[Main] Skipping window creation in appMain - open-url already handled launch'); + } // Setup auto-updater AFTER window is created and displayed (with delay to avoid blocking) setTimeout(() => { From 0081220a011dda21c18ecf4d2e730e4b8ea0fa59 Mon Sep 17 00:00:00 2001 From: Zane Staggs Date: Mon, 12 Jan 2026 10:23:50 -0800 Subject: [PATCH 2/3] cleanup --- ui/desktop/src/main.ts | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 1ebaab8e0778..f96671606b45 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -308,10 +308,17 @@ app.on('open-url', async (_event, url) => { if (process.platform !== 'win32') { const parsedUrl = new URL(url); + log.info('[Main] Received open-url event:', url); + + // Wait for app to be ready before creating window (critical for cold start) + await app.whenReady(); + + const recentDirs = loadRecentDirs(); + const openDir = recentDirs.length > 0 ? recentDirs[0] : null; + // Handle bot/recipe URLs by directly creating a new window - console.log('[Main] Received open-url event:', url); if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { - console.log('[Main] Detected bot/recipe URL, creating new chat window'); + log.info('[Main] Detected bot/recipe URL, creating new chat window'); openUrlHandledLaunch = true; const deeplinkData = parseRecipeDeeplink(url); if (deeplinkData) { @@ -319,13 +326,6 @@ app.on('open-url', async (_event, url) => { } const scheduledJobId = parsedUrl.searchParams.get('scheduledJob'); - // Wait for app to be ready before creating window (critical for cold start) - await app.whenReady(); - - const recentDirs = loadRecentDirs(); - const openDir = recentDirs.length > 0 ? recentDirs[0] : null; - - // Create a new window directly await createChat( app, undefined, @@ -342,22 +342,15 @@ app.on('open-url', async (_event, url) => { return; } - // For non bot/recipe URLs (extension, sessions), store the deep link and create window + // For extension/session URLs, store the deep link for processing after React is ready pendingDeepLink = url; - console.log('[Main] Stored pending deep link for processing after React ready:', url); - - // Wait for app to be ready before creating window (critical for cold start) - await app.whenReady(); - - const recentDirs = loadRecentDirs(); - const openDir = recentDirs.length > 0 ? recentDirs[0] : null; + log.info('[Main] Stored pending deep link for processing after React ready:', url); const existingWindows = BrowserWindow.getAllWindows(); if (existingWindows.length > 0) { firstOpenWindow = existingWindows[0]; if (firstOpenWindow.isMinimized()) firstOpenWindow.restore(); firstOpenWindow.focus(); - // For existing windows, send the message directly (React is already ready) if (parsedUrl.hostname === 'extension') { firstOpenWindow.webContents.send('add-extension', pendingDeepLink); pendingDeepLink = null; @@ -366,8 +359,6 @@ app.on('open-url', async (_event, url) => { pendingDeepLink = null; } } else { - // Cold start: mark that we're handling the launch, create window - // The pending deep link will be processed when react-ready fires openUrlHandledLaunch = true; firstOpenWindow = await createChat(app, undefined, openDir || undefined); } From aa4bc097b5b1384fcd5f63aa1e6bcb78b7aab4fa Mon Sep 17 00:00:00 2001 From: Zane Staggs Date: Mon, 12 Jan 2026 10:25:13 -0800 Subject: [PATCH 3/3] cleanup --- ui/desktop/src/main.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index f96671606b45..ad16dedb7cdc 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -227,7 +227,6 @@ if (process.platform !== 'darwin') { let firstOpenWindow: BrowserWindow; let pendingDeepLink: string | null = null; -// Track if open-url already handled window creation during cold start let openUrlHandledLaunch = false; async function handleProtocolUrl(url: string) { @@ -310,7 +309,6 @@ app.on('open-url', async (_event, url) => { log.info('[Main] Received open-url event:', url); - // Wait for app to be ready before creating window (critical for cold start) await app.whenReady(); const recentDirs = loadRecentDirs(); @@ -1914,8 +1912,6 @@ async function appMain() { const { dirPath } = parseArgs(); - // Only create a new window if open-url didn't already handle the launch - // This prevents duplicate windows when launching via deep link on cold start if (!openUrlHandledLaunch) { await createNewWindow(app, dirPath); } else {