Skip to content

Move pull and push states from SyncSitesProvider to a new Redux slice - #2037

Merged
gcsecsey merged 73 commits into
trunkfrom
stu-711-studio-refactor-pull-and-push-states-from-syncsitesprovider
Mar 3, 2026
Merged

Move pull and push states from SyncSitesProvider to a new Redux slice#2037
gcsecsey merged 73 commits into
trunkfrom
stu-711-studio-refactor-pull-and-push-states-from-syncsitesprovider

Conversation

@gcsecsey

@gcsecsey gcsecsey commented Nov 7, 2025

Copy link
Copy Markdown
Member

Related issues

Fixes STU-711

Proposed Changes

Sync operations are currently managed through a React Context (SyncSitesProvider) with two large custom hooks (useSyncPull and useSyncPush). This works, but it has drawbacks: polling is driven by useEffect intervals tied to the component lifecycle, state is only accessible to components wrapped in the provider, and the logic is difficult to test in isolation. The hooks mix business logic (API calls, file operations, error handling) with React state management (useState, useEffect), making them hard to reason about.

This PR replaces the Context + hooks architecture with a single Redux Toolkit slice (sync-operations-slice.ts) and listener middleware:

  • New syncOperations slice (src/stores/sync/sync-operations-slice.ts): consolidates all pull/push state, async thunks for operations (pushSiteThunk, pullSiteThunk, polling thunks, cancel thunks), 22 selectors, and IPC event listeners into one file
  • Listener middleware for polling (src/stores/index.ts): replaces useEffect-based polling intervals. Listeners watch for state changes and automatically dispatch polling thunks after 2s delays, creating self-sustaining loops decoupled from React component lifecycle
  • Listener middleware for side effects: IPC sync with Main process (addSyncOperation/clearSyncOperation) and error modal display are now handled by listeners reacting to state changes and thunk rejections, rather than being scattered across hooks
  • Deleted SyncSitesProvider and hooks (sync-sites-context.tsx, use-sync-pull.ts, use-sync-push.ts): ~1,155 lines removed
  • Migrated all consuming components to use useRootSelector with selectors and dispatch with thunks instead of useSyncSites() context hook
  • Extracted useLastSyncTimeText (src/hooks/sync-sites/use-last-sync-time-text.ts): small utility previously embedded in the context
  • Thunk condition for dedup: polling thunks use createAsyncThunk's condition option to skip execution if the operation was cancelled, replacing manual guard checks
  • Removed SyncSitesProvider from root.tsx, moved initialization to app.tsx via initializeSyncStatesThunk

Testing Instructions

This is a pure refactor, no user-facing behavior should change. The goal is to Check that everything still works exactly as before:

  • Connect a local site to a WordPress.com site via the Sync tab
  • Push: check that the progress UI updates through stages (creating backup, uploading with percentage, creating remote backup, applying changes, finished) and the success state appears
  • Pull: Check that progress UI updates through stages (in-progress, downloading, importing, finished) and the success state appears
  • Cancel: start a push or pull, then cancel it. Check that the cancelled state appears and you can clear it.
  • Upload pause/resume: start a push, then disconnect the network. Check that the UI shows the paused state and resumes when the network returns.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
@gcsecsey
gcsecsey requested a review from a team November 7, 2025 12:12
@github-actions

github-actions Bot commented Nov 7, 2025

Copy link
Copy Markdown
Contributor

📊 Performance Test Results

Comparing 2179acf vs trunk

site-editor

Metric trunk 2179acf Diff Change
load 1478.00 ms 1436.00 ms -42.00 ms ⚪ 0.0%

site-startup

Metric trunk 2179acf Diff Change
siteCreation 8074.00 ms 8104.00 ms +30.00 ms ⚪ 0.0%
siteStartup 4948.00 ms 4945.00 ms -3.00 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@nightnei nightnei left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Push: it seems it stucks at "Creating backup..." step. Tried a few times and also tried in trunk - worked quickly for me.

Pull: not related to the changes, since I reproduce the error in trunk too, but if you are working there - take a look please at this error when pull is finished, maybe it's a quck win:

Screenshot 2025-11-18 at 19 07 39

@gcsecsey

Copy link
Copy Markdown
Member Author

Push: it seems it stucks at "Creating backup..." step. Tried a few times and also tried in trunk - worked quickly for me.

Pull: not related to the changes, since I reproduce the error in trunk too, but if you are working there - take a look please at this error when pull is finished, maybe it's a quck win: Screenshot 2025-11-18 at 19 07 39

Thanks for reviewing this @nightnei! I took another look at this, and found that earlier we used a ref to keep track of the state, which let us read it back immediately after updating it. For now, I added the ref to the hooks that were previously in the usePullPushStates hook.

I'm also testing a simpler approach that updated the state objects directly where needed. If it works reliably, I'll update the PR with this approach.

@fredrikekelund

Copy link
Copy Markdown
Contributor

@gcsecsey, I would challenge us to remove src/hooks/sync-sites/sync-sites-context.tsx completely as part of this refactor.

With this change, we are preserving functions like updatePushState in the React context, even though they are now essentially Redux action creators (or thunks, I guess). The logic around sync operations would be much easier to follow if we moved src/hooks/sync-sites/use-sync-push.ts and src/hooks/sync-sites/use-sync-pull.ts into src/stores/sync/sync-operations-slice.ts.

@gcsecsey

Copy link
Copy Markdown
Member Author

@gcsecsey, I would challenge us to remove src/hooks/sync-sites/sync-sites-context.tsx completely as part of this refactor.

With this change, we are preserving functions like updatePushState in the React context, even though they are now essentially Redux action creators (or thunks, I guess). The logic around sync operations would be much easier to follow if we moved src/hooks/sync-sites/use-sync-push.ts and src/hooks/sync-sites/use-sync-pull.ts into src/stores/sync/sync-operations-slice.ts.

That's a good point @fredrikekelund, thanks! I'll try to move as much as possible to the Redux slice, and ping for another review.

@gcsecsey
gcsecsey marked this pull request as draft December 17, 2025 14:30
Remove the SyncSitesProvider React context layer and migrate all
components to use useSyncPull and useSyncPush hooks directly.

Changes:
- Extract getLastSyncTimeText to useLastSyncTimeText hook
- Extract initialization logic to useInitializeSyncStates hook
- Move useListenDeepLinkConnection and initialization to App component
- Update all components to use hooks directly instead of context
- Remove sync-sites-context.tsx
…dio-refactor-pull-and-push-states-from-syncsitesprovider
…dio-refactor-pull-and-push-states-from-syncsitesprovider
@gcsecsey

Copy link
Copy Markdown
Member Author

I carried out the changes to move most of the logic into thunks within src/stores/sync/sync-operations-slice.ts. I experimented with removing the src/hooks/sync-sites/use-sync-push.ts and src/hooks/sync-sites/use-sync-pull.ts hooks completely too, and update the components to directly use the thunks, but I found this needed quite a bit of boilerplate in each component, so for the time being, I kept the hooks as thin wrappers.

@fredrikekelund could you please take another look when you're back? Thanks!

@fredrikekelund

Copy link
Copy Markdown
Contributor

I discovered that the ability to manually pause pushes had been lost in the diff, so I restored it. I also went ahead and added zod schemas for the API responses and restored tests that had been botched or skipped.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change (and all the corresponding changes in the other files) was to appease TS. Apparently, TS interfaces can be implicitly merged, which is why the compiler was complaining in apps/studio/src/stores/index.ts when the state interfaces weren't exported. Silly stuff, but this change appeases TS.

gcsecsey and others added 5 commits February 25, 2026 11:50
…itesprovider' of github.com:Automattic/studio into stu-711-studio-refactor-pull-and-push-states-from-syncsitesprovider
…dio-refactor-pull-and-push-states-from-syncsitesprovider
@fredrikekelund

Copy link
Copy Markdown
Contributor

I know it's not easy to see how, but e041e31 breaks sync polling, @gcsecsey. Listeners need to wait for the dispatched actions to finish, because the signal is aborted when the listener completes.

Your change might be taken from my previous suggestion. If so, know that I've already implemented everything I suggested.

@gcsecsey

Copy link
Copy Markdown
Member Author

I know it's not easy to see how, but e041e31 breaks sync polling, @gcsecsey. Listeners need to wait for the dispatched actions to finish, because the signal is aborted when the listener completes.

Your change might be taken from my previous suggestion. If so, know that I've already implemented everything I suggested.

Sorry, yes I was trying to implement what you suggested earlier, I didn't realize that you also updated the listeners. I'll revert e041e31.

gcsecsey and others added 4 commits February 25, 2026 16:21
… types, and to cancel other active listeners"

This reverts commit e041e31.
If I start a push and cancel it before it starts uploading, then quickly start a new push, the second operation will fail.

That's because it generates an identical archive path for every push, and the abortion of the first push call is async, meaning the file isn't deleted when the operation is aborted, but only after the first export finishes.
@gcsecsey

gcsecsey commented Mar 2, 2026

Copy link
Copy Markdown
Member Author

@fredrikekelund thank you for iterating on this, I re-tested the pull/push functionality with the most recent changes and found no regressions. Resumable uploads and polling all work well too.

I think we're in good shape to proceed with merging this. 👍

@gcsecsey

gcsecsey commented Mar 3, 2026

Copy link
Copy Markdown
Member Author

As this PR is pretty big, could we get another reviewer from @Automattic/yolo to take a look? @nightnei, you already reviewed this once, could you give it a second look when you have some bandwidth?

We already spent a lot of time with @fredrikekelund on code reviews, so testing/verifying would be the priority.

@nightnei

nightnei commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

@gcsecsey I tested and all casses work correctly - push/pull, cancel both, offlane mode and pausing/resuming while pushing. But I didn't review code thoroughly, I just glanced it a bit.

@gcsecsey

gcsecsey commented Mar 3, 2026

Copy link
Copy Markdown
Member Author

@gcsecsey I tested and all casses work correctly - push/pull, cancel both, offlane mode and pausing/resuming while pushing. But I didn't review code thoroughly, I just glanced it a bit.

Thanks @nightnei for confirming! 🙌 I'll proceed with merging this.

@gcsecsey
gcsecsey merged commit 53bbfb6 into trunk Mar 3, 2026
10 checks passed
@gcsecsey
gcsecsey deleted the stu-711-studio-refactor-pull-and-push-states-from-syncsitesprovider branch March 3, 2026 16:21
epeicher added a commit that referenced this pull request Mar 17, 2026
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.
epeicher added a commit that referenced this pull request Mar 17, 2026
* 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.

* 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.

* 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants