From 2999b38c6d8863966a81fa3ed3641f8a19f3bfdb Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Fri, 6 Feb 2026 11:40:53 +0100 Subject: [PATCH 1/7] Add storage support for custom site directory --- apps/studio/src/storage/storage-types.ts | 1 + apps/studio/src/storage/user-data.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/studio/src/storage/storage-types.ts b/apps/studio/src/storage/storage-types.ts index 199c7b7719..9e05557d05 100644 --- a/apps/studio/src/storage/storage-types.ts +++ b/apps/studio/src/storage/storage-types.ts @@ -33,6 +33,7 @@ export interface UserData { preferredEditor?: SupportedEditor; betaFeatures?: BetaFeatures; stopSitesOnQuit?: boolean; + defaultSiteDirectory?: string; } export interface PersistedUserData extends Omit< UserData, 'sites' > { diff --git a/apps/studio/src/storage/user-data.ts b/apps/studio/src/storage/user-data.ts index 6ca0ca6985..b1fbbf690d 100644 --- a/apps/studio/src/storage/user-data.ts +++ b/apps/studio/src/storage/user-data.ts @@ -160,7 +160,8 @@ type UserDataSafeKeys = | 'lastSeenVersion' | 'preferredTerminal' | 'preferredEditor' - | 'betaFeatures'; + | 'betaFeatures' + | 'defaultSiteDirectory'; type PartialUserDataWithSafeKeysToUpdate = Partial< Pick< UserData, UserDataSafeKeys > >; From ae8f790cb162eb377ff625b4931392db2e43f001 Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Fri, 6 Feb 2026 11:47:04 +0100 Subject: [PATCH 2/7] Add IPC helpers for default site directory --- apps/studio/src/ipc-handlers.ts | 5 ++++- .../src/modules/user-settings/lib/ipc-handlers.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/studio/src/ipc-handlers.ts b/apps/studio/src/ipc-handlers.ts index f9f5c18a82..f5af8b003d 100644 --- a/apps/studio/src/ipc-handlers.ts +++ b/apps/studio/src/ipc-handlers.ts @@ -87,7 +87,7 @@ import { supportedEditorConfig, SupportedEditor } from 'src/modules/user-setting import { getUserEditor, getUserTerminal } from 'src/modules/user-settings/lib/ipc-handlers'; import { winFindEditorPath } from 'src/modules/user-settings/lib/win-editor-path'; import { SiteServer, stopAllServers as triggerStopAllServers } from 'src/site-server'; -import { DEFAULT_SITE_PATH, getSiteThumbnailPath } from 'src/storage/paths'; +import { getSiteThumbnailPath } from 'src/storage/paths'; import { loadUserData, lockAppdata, @@ -95,6 +95,7 @@ import { unlockAppdata, updateAppdata, } from 'src/storage/user-data'; +import { resolveDefaultSiteDirectory } from 'src/storage/default-site-directory'; import { Blueprint } from 'src/stores/wpcom-api'; import type { RawDirectoryEntry } from 'src/modules/sync/types'; import type { WpCliResult } from 'src/site-server'; @@ -137,6 +138,8 @@ export { saveUserLocale, saveUserTerminal, showUserSettings, + getDefaultSiteDirectory, + saveDefaultSiteDirectory, } from 'src/modules/user-settings/lib/ipc-handlers'; export async function getAgentInstructionsStatus( diff --git a/apps/studio/src/modules/user-settings/lib/ipc-handlers.ts b/apps/studio/src/modules/user-settings/lib/ipc-handlers.ts index 392bc18239..7b843d83cf 100644 --- a/apps/studio/src/modules/user-settings/lib/ipc-handlers.ts +++ b/apps/studio/src/modules/user-settings/lib/ipc-handlers.ts @@ -7,6 +7,7 @@ import { SUPPORTED_EDITORS, SupportedEditor } from 'src/modules/user-settings/li import { SupportedTerminal } from 'src/modules/user-settings/lib/terminal'; import { UserSettingsTabName } from 'src/modules/user-settings/user-settings-types'; import { loadUserData, updateAppdata } from 'src/storage/user-data'; +import { ensureWritableDirectory } from 'src/storage/default-site-directory'; export function getInstalledAppsAndTerminals(): InstalledApps { return { @@ -49,6 +50,17 @@ export async function saveUserEditor( event: IpcMainInvokeEvent, editor: Support await updateAppdata( { preferredEditor: editor } ); } +export async function getDefaultSiteDirectory(): Promise< string | undefined > { + const userData = await loadUserData(); + return userData.defaultSiteDirectory; +} + +export async function saveDefaultSiteDirectory( event: IpcMainInvokeEvent, directory: string ) { + await ensureWritableDirectory( directory ); + await sendIpcEventToRenderer( 'user-preference-changed' ); + await updateAppdata( { defaultSiteDirectory: directory } ); +} + export async function getUserLocale() { return getUserLocaleWithFallback(); } From a864256189481b9aa8abe3fe707047bcdb2eb720 Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Fri, 6 Feb 2026 11:48:37 +0100 Subject: [PATCH 3/7] Use default site preference in dialog helpers --- apps/studio/src/ipc-handlers.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/studio/src/ipc-handlers.ts b/apps/studio/src/ipc-handlers.ts index f5af8b003d..b61a83c8cb 100644 --- a/apps/studio/src/ipc-handlers.ts +++ b/apps/studio/src/ipc-handlers.ts @@ -611,9 +611,11 @@ export async function showSaveAsDialog( event: IpcMainInvokeEvent, options: Save throw new Error( `No window found for sender of showSaveAsDialog message: ${ event.frameId }` ); } + const defaultSiteDirectory = await resolveDefaultSiteDirectory(); const defaultPath = - options.defaultPath === nodePath.basename( options.defaultPath ?? '' ) - ? nodePath.join( DEFAULT_SITE_PATH, options.defaultPath ) + typeof options.defaultPath === 'string' && + options.defaultPath === nodePath.basename( options.defaultPath ) + ? nodePath.join( defaultSiteDirectory, options.defaultPath ) : options.defaultPath; const { canceled, filePath } = await dialog.showSaveDialog( parentWindow, { defaultPath, @@ -648,9 +650,10 @@ export async function showOpenFolderDialog( }; } + const defaultSiteDirectory = await resolveDefaultSiteDirectory(); const { canceled, filePaths } = await dialog.showOpenDialog( parentWindow, { title, - defaultPath: defaultDialogPath !== '' ? defaultDialogPath : DEFAULT_SITE_PATH, + defaultPath: defaultDialogPath !== '' ? defaultDialogPath : defaultSiteDirectory, properties: [ 'openDirectory', 'createDirectory', // allow user to create new directories; macOS only @@ -892,7 +895,8 @@ export async function generateProposedSitePath( _event: IpcMainInvokeEvent, siteName: string ): Promise< FolderDialogResponse > { - const path = nodePath.join( DEFAULT_SITE_PATH, sanitizeFolderName( siteName ) ); + const defaultSiteDirectory = await resolveDefaultSiteDirectory(); + const path = nodePath.join( defaultSiteDirectory, sanitizeFolderName( siteName ) ); try { return { From aec3673cf2c3bc7273c98c577fdfc57d599cbc0c Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Fri, 6 Feb 2026 14:03:13 +0100 Subject: [PATCH 4/7] wip: create new component --- apps/studio/src/ipc-handlers.ts | 2 +- .../components/default-directory-picker.tsx | 39 +++++++++++++ .../components/preferences-tab.tsx | 58 ++++++++++++++++++- .../modules/user-settings/lib/ipc-handlers.ts | 10 ++-- apps/studio/src/preload.ts | 3 + .../src/storage/default-site-directory.ts | 42 ++++++++++++++ 6 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 apps/studio/src/modules/user-settings/components/default-directory-picker.tsx create mode 100644 apps/studio/src/storage/default-site-directory.ts diff --git a/apps/studio/src/ipc-handlers.ts b/apps/studio/src/ipc-handlers.ts index b61a83c8cb..ae0abf21b9 100644 --- a/apps/studio/src/ipc-handlers.ts +++ b/apps/studio/src/ipc-handlers.ts @@ -87,6 +87,7 @@ import { supportedEditorConfig, SupportedEditor } from 'src/modules/user-setting import { getUserEditor, getUserTerminal } from 'src/modules/user-settings/lib/ipc-handlers'; import { winFindEditorPath } from 'src/modules/user-settings/lib/win-editor-path'; import { SiteServer, stopAllServers as triggerStopAllServers } from 'src/site-server'; +import { resolveDefaultSiteDirectory } from 'src/storage/default-site-directory'; import { getSiteThumbnailPath } from 'src/storage/paths'; import { loadUserData, @@ -95,7 +96,6 @@ import { unlockAppdata, updateAppdata, } from 'src/storage/user-data'; -import { resolveDefaultSiteDirectory } from 'src/storage/default-site-directory'; import { Blueprint } from 'src/stores/wpcom-api'; import type { RawDirectoryEntry } from 'src/modules/sync/types'; import type { WpCliResult } from 'src/site-server'; diff --git a/apps/studio/src/modules/user-settings/components/default-directory-picker.tsx b/apps/studio/src/modules/user-settings/components/default-directory-picker.tsx new file mode 100644 index 0000000000..f6960cd13e --- /dev/null +++ b/apps/studio/src/modules/user-settings/components/default-directory-picker.tsx @@ -0,0 +1,39 @@ +import { useI18n } from '@wordpress/react-i18n'; +import Button from 'src/components/button'; +import { SettingsFormField } from './settings-form-field'; + +interface DefaultDirectoryPickerProps { + directory?: string; + isLoading: boolean; + isSelecting: boolean; + onPick: () => void; +} + +export const DefaultDirectoryPicker = ( { + directory, + isLoading, + isSelecting, + onPick, +}: DefaultDirectoryPickerProps ) => { + const { __ } = useI18n(); + + return ( + +
+

+ { isLoading ? __( 'Loading...' ) : directory ?? '' } +

+
+ +
+
+
+ ); +}; diff --git a/apps/studio/src/modules/user-settings/components/preferences-tab.tsx b/apps/studio/src/modules/user-settings/components/preferences-tab.tsx index 9ff501bbfd..c328d11be6 100644 --- a/apps/studio/src/modules/user-settings/components/preferences-tab.tsx +++ b/apps/studio/src/modules/user-settings/components/preferences-tab.tsx @@ -1,10 +1,11 @@ import { SupportedLocale } from '@studio/common/lib/locale'; import { useI18n } from '@wordpress/react-i18n'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Button from 'src/components/button'; import { useFeatureFlags } from 'src/hooks/use-feature-flags'; import { isWindowsStore } from 'src/lib/app-globals'; import { McpSettings } from 'src/modules/mcp/components/mcp-settings'; +import { getIpcApi } from 'src/lib/get-ipc-api'; import { EditorPicker } from 'src/modules/user-settings/components/editor-picker'; import { LanguagePicker } from 'src/modules/user-settings/components/language-picker'; import { StudioCliToggle } from 'src/modules/user-settings/components/studio-cli-toggle'; @@ -21,6 +22,7 @@ import { useGetStudioCliIsInstalledQuery, useSaveStudioCliIsInstalledMutation, } from 'src/stores/installed-apps-api'; +import { DefaultDirectoryPicker } from './default-directory-picker'; export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => { const { __ } = useI18n(); @@ -40,6 +42,10 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => { const [ dirtyEditor, setDirtyEditor ] = useState< SupportedEditor | null >(); const [ dirtyTerminal, setDirtyTerminal ] = useState< SupportedTerminal >(); const [ dirtyIsCliInstalled, setDirtyIsCliInstalled ] = useState< boolean >(); + const [ storedDefaultSiteDirectory, setStoredDefaultSiteDirectory ] = useState< string >(); + const [ defaultSiteDirectory, setDefaultSiteDirectory ] = useState< string >(); + const [ isLoadingDefaultSiteDirectory, setIsLoadingDefaultSiteDirectory ] = useState( true ); + const [ isSelectingDefaultDirectory, setIsSelectingDefaultDirectory ] = useState( false ); const savePreferences = async () => { if ( dirtyLocale ) { @@ -54,6 +60,13 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => { if ( dirtyIsCliInstalled !== undefined ) { await saveCliIsInstalled( dirtyIsCliInstalled ); } + const isDefaultDirectoryDirty = + storedDefaultSiteDirectory !== undefined && + defaultSiteDirectory !== undefined && + storedDefaultSiteDirectory !== defaultSiteDirectory; + if ( isDefaultDirectoryDirty && defaultSiteDirectory ) { + await getIpcApi().saveDefaultSiteDirectory( defaultSiteDirectory ); + } onClose(); }; @@ -67,8 +80,45 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => { [ dirtyEditor, editor ], [ dirtyTerminal, terminal ], [ dirtyIsCliInstalled, isCliInstalled ], + [ defaultSiteDirectory, storedDefaultSiteDirectory ], ].some( ( [ a, b ] ) => a !== undefined && a !== b ); + useEffect( () => { + let isMounted = true; + void ( async () => { + try { + const directory = await getIpcApi().getDefaultSiteDirectory(); + if ( ! isMounted ) { + return; + } + setStoredDefaultSiteDirectory( directory ); + setDefaultSiteDirectory( directory ); + } finally { + if ( isMounted ) { + setIsLoadingDefaultSiteDirectory( false ); + } + } + } )(); + return () => { + isMounted = false; + }; + }, [] ); + + const handleChangeDefaultDirectory = async () => { + setIsSelectingDefaultDirectory( true ); + try { + const response = await getIpcApi().showOpenFolderDialog( + __( 'Select default site directory' ), + defaultSiteDirectory ?? '' + ); + if ( response?.path ) { + setDefaultSiteDirectory( response.path ); + } + } finally { + setIsSelectingDefaultDirectory( false ); + } + }; + return ( <> @@ -84,6 +134,12 @@ export const PreferencesTab = ( { onClose }: { onClose: () => void } ) => { { enableAgentSuite && } ) } +