From bc9b6fe237e76398f2ddece410a5f8154192826a Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Tue, 17 Mar 2026 10:36:39 +0100 Subject: [PATCH 1/3] Remove updateSingleConnectedWpcomSite IPC handler Replace all usages with the existing updateConnectedWpcomSites handler by wrapping the single site in an array. This eliminates a redundant IPC handler that duplicated logic already present in the bulk variant. --- .../tests/site-management-actions.test.tsx | 8 +++--- .../use-listen-deep-link-connection.ts | 2 +- apps/studio/src/ipc-handlers.ts | 1 - .../src/modules/sync/lib/ipc-handlers.ts | 28 ------------------- apps/studio/src/preload.ts | 2 -- .../studio/src/stores/sync/connected-sites.ts | 2 +- 6 files changed, 6 insertions(+), 37 deletions(-) diff --git a/apps/studio/src/components/tests/site-management-actions.test.tsx b/apps/studio/src/components/tests/site-management-actions.test.tsx index 603146436f..fca0ec9eab 100644 --- a/apps/studio/src/components/tests/site-management-actions.test.tsx +++ b/apps/studio/src/components/tests/site-management-actions.test.tsx @@ -11,12 +11,12 @@ import { store } from 'src/stores'; import { connectedSitesApi } from 'src/stores/sync/connected-sites'; const mockGetConnectedWpcomSites = vi.fn(); -const mockUpdateSingleConnectedWpcomSite = vi.fn(); +const mockUpdateConnectedWpcomSites = vi.fn(); vi.mock( 'src/lib/get-ipc-api', () => ( { getIpcApi: vi.fn( () => ( { getConnectedWpcomSites: mockGetConnectedWpcomSites, - updateSingleConnectedWpcomSite: mockUpdateSingleConnectedWpcomSite, + updateConnectedWpcomSites: mockUpdateConnectedWpcomSites, } ) ), } ) ); @@ -44,10 +44,10 @@ describe( 'SiteManagementActions', () => { beforeEach( () => { // Reset mock calls but preserve implementations mockGetConnectedWpcomSites.mockClear(); - mockUpdateSingleConnectedWpcomSite.mockClear(); + mockUpdateConnectedWpcomSites.mockClear(); // Set default return values mockGetConnectedWpcomSites.mockResolvedValue( [] ); - mockUpdateSingleConnectedWpcomSite.mockResolvedValue( {} ); + mockUpdateConnectedWpcomSites.mockResolvedValue( {} ); // Clear RTK Query cache between tests store.dispatch( connectedSitesApi.util.resetApiState() ); } ); diff --git a/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts b/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts index 508efb0305..04983732ad 100644 --- a/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts +++ b/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts @@ -102,7 +102,7 @@ export function useListenDeepLinkConnection() { localSiteId: studioSiteId, syncSupport: 'already-connected', }; - await getIpcApi().updateSingleConnectedWpcomSite( fullSiteData ); + await getIpcApi().updateConnectedWpcomSites( [ fullSiteData ] ); dispatch( connectedSitesApi.util.invalidateTags( [ 'ConnectedSites' ] ) ); } } catch ( error ) { diff --git a/apps/studio/src/ipc-handlers.ts b/apps/studio/src/ipc-handlers.ts index f9f5c18a82..ae6e9c4fa4 100644 --- a/apps/studio/src/ipc-handlers.ts +++ b/apps/studio/src/ipc-handlers.ts @@ -119,7 +119,6 @@ export { removeSyncBackup, resumeSyncUpload, updateConnectedWpcomSites, - updateSingleConnectedWpcomSite, } from 'src/modules/sync/lib/ipc-handlers'; export { diff --git a/apps/studio/src/modules/sync/lib/ipc-handlers.ts b/apps/studio/src/modules/sync/lib/ipc-handlers.ts index 277aaa935d..ce40e05be5 100644 --- a/apps/studio/src/modules/sync/lib/ipc-handlers.ts +++ b/apps/studio/src/modules/sync/lib/ipc-handlers.ts @@ -529,34 +529,6 @@ export async function updateConnectedWpcomSites( } } -export async function updateSingleConnectedWpcomSite( - event: IpcMainInvokeEvent, - updatedSite: SyncSite -) { - try { - await lockAppdata(); - const userData = await loadUserData(); - const currentUserId = userData.authToken?.id; - - if ( ! currentUserId ) { - throw new Error( 'User not authenticated' ); - } - - const connections = userData.connectedWpcomSites?.[ currentUserId ] || []; - const index = connections.findIndex( - ( conn ) => conn.id === updatedSite.id && conn.localSiteId === updatedSite.localSiteId - ); - - if ( index !== -1 ) { - connections[ index ] = updatedSite; - } - - await saveUserData( userData ); - } finally { - await unlockAppdata(); - } -} - export async function getConnectedWpcomSites( event: IpcMainInvokeEvent, localSiteId?: string diff --git a/apps/studio/src/preload.ts b/apps/studio/src/preload.ts index 279caeda86..ec7eed15a5 100644 --- a/apps/studio/src/preload.ts +++ b/apps/studio/src/preload.ts @@ -48,8 +48,6 @@ const api: IpcApi = { disconnectWpcomSites: ( ...args ) => ipcRendererInvoke( 'disconnectWpcomSites', ...args ), updateConnectedWpcomSites: ( ...args ) => ipcRendererInvoke( 'updateConnectedWpcomSites', ...args ), - updateSingleConnectedWpcomSite: ( updatedSite ) => - ipcRendererInvoke( 'updateSingleConnectedWpcomSite', updatedSite ), authenticate: ( isSignup ) => ipcRendererSend( 'authenticate', isSignup ), exportSite: ( options ) => ipcRendererInvoke( 'exportSite', options ), isAuthenticated: () => ipcRendererInvoke( 'isAuthenticated' ), diff --git a/apps/studio/src/stores/sync/connected-sites.ts b/apps/studio/src/stores/sync/connected-sites.ts index 2d6ac1e763..a72651c5fe 100644 --- a/apps/studio/src/stores/sync/connected-sites.ts +++ b/apps/studio/src/stores/sync/connected-sites.ts @@ -151,7 +151,7 @@ export const connectedSitesApi = createApi( { [ timestampKey ]: new Date().toISOString(), }; - await getIpcApi().updateSingleConnectedWpcomSite( updatedConnectedSite ); + await getIpcApi().updateConnectedWpcomSites( [ updatedConnectedSite ] ); return { data: undefined }; }, From a934dcec8d254a29b5a2d2ace36ad02e50ac838b Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Tue, 17 Mar 2026 10:54:31 +0100 Subject: [PATCH 2/3] Restore timestamp updates on push/pull success The updateSiteTimestamp calls were dropped in #2037 when push/pull states moved from SyncSitesProvider to a Redux slice. Wire them back up by dispatching the RTK Query mutation from the thunks. --- .../src/stores/sync/sync-operations-slice.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/studio/src/stores/sync/sync-operations-slice.ts b/apps/studio/src/stores/sync/sync-operations-slice.ts index d0d2d5b933..550944d82b 100644 --- a/apps/studio/src/stores/sync/sync-operations-slice.ts +++ b/apps/studio/src/stores/sync/sync-operations-slice.ts @@ -611,6 +611,13 @@ const pollPushProgressThunk = createTypedAsyncThunk( switch ( response.status ) { case 'finished': status = pushStatesProgressInfo.finished; + void dispatch( + connectedSitesApi.endpoints.updateSiteTimestamp.initiate( { + siteId: remoteSiteId, + localSiteId: selectedSiteId, + type: 'push', + } ) + ); void dispatch( connectedSitesApi.util.invalidateTags( [ 'ConnectedSites' ] ) ); getIpcApi().showNotification( { title: currentPushState.selectedSite.name, @@ -823,6 +830,13 @@ const pollPullBackupThunk = createTypedAsyncThunk( await getIpcApi().removeSyncBackup( remoteSiteId ); + void dispatch( + connectedSitesApi.endpoints.updateSiteTimestamp.initiate( { + siteId: remoteSiteId, + localSiteId: selectedSiteId, + type: 'pull', + } ) + ); void dispatch( connectedSitesApi.util.invalidateTags( [ 'ConnectedSites' ] ) ); dispatch( From e1765724bc93e14873a719269791018645726491 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Tue, 17 Mar 2026 16:23:06 +0100 Subject: [PATCH 3/3] Remove updateSiteTimestamp RTK Query mutation, call IPC directly Replace the RTK Query mutation with a plain async function that calls getIpcApi().updateConnectedWpcomSites() directly from the thunks. Await the timestamp write before invalidating the cache to avoid a race where the re-fetch reads stale data. --- .../studio/src/stores/sync/connected-sites.ts | 30 ----------- .../src/stores/sync/sync-operations-slice.ts | 51 ++++++++++++++----- 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/apps/studio/src/stores/sync/connected-sites.ts b/apps/studio/src/stores/sync/connected-sites.ts index a72651c5fe..4342eafc2c 100644 --- a/apps/studio/src/stores/sync/connected-sites.ts +++ b/apps/studio/src/stores/sync/connected-sites.ts @@ -130,35 +130,6 @@ export const connectedSitesApi = createApi( { { type: 'ConnectedSites', localSiteId }, ], } ), - - updateSiteTimestamp: builder.mutation< - void, - { siteId: number; localSiteId: string; type: 'pull' | 'push' } - >( { - queryFn: async ( { siteId, localSiteId, type } ) => { - const connectedSites = await getIpcApi().getConnectedWpcomSites( localSiteId ); - const connectedSite = connectedSites.find( - ( { id, localSiteId: siteLocalId } ) => siteId === id && localSiteId === siteLocalId - ); - - if ( ! connectedSite ) { - return { error: { status: 'CUSTOM_ERROR', error: 'Site not found' } }; - } - - const timestampKey = type === 'pull' ? 'lastPullTimestamp' : 'lastPushTimestamp'; - const updatedConnectedSite = { - ...connectedSite, - [ timestampKey ]: new Date().toISOString(), - }; - - await getIpcApi().updateConnectedWpcomSites( [ updatedConnectedSite ] ); - - return { data: undefined }; - }, - invalidatesTags: ( result, error, { localSiteId } ) => [ - { type: 'ConnectedSites', localSiteId }, - ], - } ), } ), } ); @@ -166,5 +137,4 @@ export const { useGetConnectedSitesForLocalSiteQuery, useConnectSiteMutation, useDisconnectSiteMutation, - useUpdateSiteTimestampMutation, } = connectedSitesApi; diff --git a/apps/studio/src/stores/sync/sync-operations-slice.ts b/apps/studio/src/stores/sync/sync-operations-slice.ts index 550944d82b..2e7dd736ed 100644 --- a/apps/studio/src/stores/sync/sync-operations-slice.ts +++ b/apps/studio/src/stores/sync/sync-operations-slice.ts @@ -17,6 +17,33 @@ import type { AppDispatch, RootState } from 'src/stores'; import type { SyncOption } from 'src/types'; import type { WPCOM } from 'wpcom/types'; +async function updateSiteTimestamp( { + siteId, + localSiteId, + type, +}: { + siteId: number; + localSiteId: string; + type: 'pull' | 'push'; +} ) { + const connectedSites = await getIpcApi().getConnectedWpcomSites( localSiteId ); + const connectedSite = connectedSites.find( + ( { id, localSiteId: siteLocalId } ) => siteId === id && localSiteId === siteLocalId + ); + + if ( ! connectedSite ) { + return; + } + + const timestampKey = type === 'pull' ? 'lastPullTimestamp' : 'lastPushTimestamp'; + await getIpcApi().updateConnectedWpcomSites( [ + { + ...connectedSite, + [ timestampKey ]: new Date().toISOString(), + }, + ] ); +} + export type SyncBackupState = { remoteSiteId: number; backupId: number | null; @@ -611,13 +638,11 @@ const pollPushProgressThunk = createTypedAsyncThunk( switch ( response.status ) { case 'finished': status = pushStatesProgressInfo.finished; - void dispatch( - connectedSitesApi.endpoints.updateSiteTimestamp.initiate( { - siteId: remoteSiteId, - localSiteId: selectedSiteId, - type: 'push', - } ) - ); + await updateSiteTimestamp( { + siteId: remoteSiteId, + localSiteId: selectedSiteId, + type: 'push', + } ); void dispatch( connectedSitesApi.util.invalidateTags( [ 'ConnectedSites' ] ) ); getIpcApi().showNotification( { title: currentPushState.selectedSite.name, @@ -830,13 +855,11 @@ const pollPullBackupThunk = createTypedAsyncThunk( await getIpcApi().removeSyncBackup( remoteSiteId ); - void dispatch( - connectedSitesApi.endpoints.updateSiteTimestamp.initiate( { - siteId: remoteSiteId, - localSiteId: selectedSiteId, - type: 'pull', - } ) - ); + await updateSiteTimestamp( { + siteId: remoteSiteId, + localSiteId: selectedSiteId, + type: 'pull', + } ); void dispatch( connectedSitesApi.util.invalidateTags( [ 'ConnectedSites' ] ) ); dispatch(