From 6be36fdc9efbfbff66a9785ce8302709df33ce37 Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 19 Aug 2026 13:23:09 +0100 Subject: [PATCH 1/3] fix: remember last visited directory in Desktop file dialogs Native showOpenDialog/showSaveDialog calls no longer reliably remember the last used folder across invocations (regression after bumping Electron 42 -> 43 in 9.17), so every file dialog in Desktop mode - Open SQL file, Backup, Restore, Import/Export Data - fell back to Chromium's hardcoded default location (Downloads) each time. Track the last visited directory ourselves in the persistent config store and pass it as defaultPath whenever the caller hasn't already asked for a specific location, restoring the pre-9.17 behaviour. Closes #10226 --- runtime/src/js/pgadmin.js | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/runtime/src/js/pgadmin.js b/runtime/src/js/pgadmin.js index 022cb4247ab..4a80d0308dd 100644 --- a/runtime/src/js/pgadmin.js +++ b/runtime/src/js/pgadmin.js @@ -478,9 +478,44 @@ function notifyUpdateInstalled() { } } +// Native file dialogs don't reliably remember the last used directory across +// calls, so we track it ourselves and use it as the default when the caller +// hasn't asked for a specific location. +function withLastVisitedDirectory(options) { + if (options.defaultPath) { + return options; + } + const lastVisitedDirectory = configStore.get('lastVisitedDirectory'); + if (lastVisitedDirectory && fs.existsSync(lastVisitedDirectory)) { + return { ...options, defaultPath: lastVisitedDirectory }; + } + return options; +} + +function rememberVisitedDirectory(options, result) { + const chosenPath = result.filePath || result.filePaths?.[0]; + if (!chosenPath) { + return; + } + const isDirectory = options.properties?.includes('openDirectory'); + configStore.set('lastVisitedDirectory', isDirectory ? chosenPath : path.dirname(chosenPath)); +} + // setup preload events. -ipcMain.handle('showOpenDialog', (e, options) => dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender), options)); -ipcMain.handle('showSaveDialog', (e, options) => dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender), options)); +ipcMain.handle('showOpenDialog', async (e, options) => { + const result = await dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender), withLastVisitedDirectory(options)); + if (!result.canceled) { + rememberVisitedDirectory(options, result); + } + return result; +}); +ipcMain.handle('showSaveDialog', async (e, options) => { + const result = await dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender), withLastVisitedDirectory(options)); + if (!result.canceled) { + rememberVisitedDirectory(options, result); + } + return result; +}); ipcMain.handle('showMessageBox', (e, options) => dialog.showMessageBox(BrowserWindow.fromWebContents(e.sender), options)); ipcMain.handle('getStoreData', (_e, key) => key ? configStore.get(key) : configStore.store); ipcMain.handle('setStoreData', (_e, newValues) => { From e5a465bc0c17c0571ad2657060f4827a8cfafbdd Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 19 Aug 2026 14:25:05 +0100 Subject: [PATCH 2/3] review: address CodeRabbit findings on last-visited-directory fix - withLastVisitedDirectory now stats asynchronously (fs.promises.stat) instead of fs.existsSync, so a stale/network path can't block the Electron main-process event loop. - rememberVisitedDirectory catches configStore.set failures instead of letting them reject the dialog handler's promise, so a transient config-write error doesn't turn a successful file selection into an error for the caller. --- runtime/src/js/pgadmin.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/runtime/src/js/pgadmin.js b/runtime/src/js/pgadmin.js index 4a80d0308dd..5c9b43c68f1 100644 --- a/runtime/src/js/pgadmin.js +++ b/runtime/src/js/pgadmin.js @@ -481,13 +481,21 @@ function notifyUpdateInstalled() { // Native file dialogs don't reliably remember the last used directory across // calls, so we track it ourselves and use it as the default when the caller // hasn't asked for a specific location. -function withLastVisitedDirectory(options) { +async function withLastVisitedDirectory(options) { if (options.defaultPath) { return options; } const lastVisitedDirectory = configStore.get('lastVisitedDirectory'); - if (lastVisitedDirectory && fs.existsSync(lastVisitedDirectory)) { - return { ...options, defaultPath: lastVisitedDirectory }; + if (!lastVisitedDirectory) { + return options; + } + try { + const stats = await fs.promises.stat(lastVisitedDirectory); + if (stats.isDirectory()) { + return { ...options, defaultPath: lastVisitedDirectory }; + } + } catch { + // Remembered directory no longer exists (e.g. removable/network drive) - fall through. } return options; } @@ -498,19 +506,23 @@ function rememberVisitedDirectory(options, result) { return; } const isDirectory = options.properties?.includes('openDirectory'); - configStore.set('lastVisitedDirectory', isDirectory ? chosenPath : path.dirname(chosenPath)); + try { + configStore.set('lastVisitedDirectory', isDirectory ? chosenPath : path.dirname(chosenPath)); + } catch (error) { + misc.writeServerLog(`Error remembering last visited directory: ${error}`); + } } // setup preload events. ipcMain.handle('showOpenDialog', async (e, options) => { - const result = await dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender), withLastVisitedDirectory(options)); + const result = await dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender), await withLastVisitedDirectory(options)); if (!result.canceled) { rememberVisitedDirectory(options, result); } return result; }); ipcMain.handle('showSaveDialog', async (e, options) => { - const result = await dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender), withLastVisitedDirectory(options)); + const result = await dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender), await withLastVisitedDirectory(options)); if (!result.canceled) { rememberVisitedDirectory(options, result); } From 3438c39e231bd4a6bbc6a43f6a9d6643f45ba87f Mon Sep 17 00:00:00 2001 From: Dave Page Date: Thu, 20 Aug 2026 14:32:22 +0100 Subject: [PATCH 3/3] review: guard writeServerLog itself from throwing in rememberVisitedDirectory writeServerLog does an unguarded fs.writeFileSync, so if configStore.set fails and the log write also fails, the second error would escape uncaught and reject the IPC handler after a successful file selection. --- runtime/src/js/pgadmin.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/src/js/pgadmin.js b/runtime/src/js/pgadmin.js index 5c9b43c68f1..ef14f02733c 100644 --- a/runtime/src/js/pgadmin.js +++ b/runtime/src/js/pgadmin.js @@ -509,7 +509,11 @@ function rememberVisitedDirectory(options, result) { try { configStore.set('lastVisitedDirectory', isDirectory ? chosenPath : path.dirname(chosenPath)); } catch (error) { - misc.writeServerLog(`Error remembering last visited directory: ${error}`); + try { + misc.writeServerLog(`Error remembering last visited directory: ${error}`); + } catch (logError) { + console.error('Error remembering last visited directory:', error, logError); + } } }