Sync: Show file upload progress in the UI#2332
Conversation
| <div className="a8c-body-small flex items-center gap-0.5"> | ||
| <Icon icon={ info } size={ 16 } /> | ||
| { pushState.status.message } | ||
| { getPushUploadMessage( pushState.status.message, uploadPercentage ) } |
There was a problem hiding this comment.
I am not sure if this is best practice but this is best I could come up with. Let me know if you have any better idea to implement this in other way.
There was a problem hiding this comment.
Nice! Could also be memoized with useMemo, but fine as-is.
There was a problem hiding this comment.
It makes sense. I have updated in 9d16c42
📊 Performance Test ResultsComparing 9d16c42 vs trunk site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change |
| return key === 'uploadingPaused'; | ||
| }; | ||
|
|
||
| const isKeyUploading = useCallback( ( key: PushStateProgressInfo[ 'key' ] | undefined ) => { |
There was a problem hiding this comment.
All of the isKeyXXX, getPushUploadPercentage, and getPushUploadMessage functions could be defined outside the hook, since they're pure functions with no dependencies on hook state. This provides stable references without the useCallback overhead.
There was a problem hiding this comment.
Interesting. Let me refactor them.
| <div className="a8c-body-small flex items-center gap-0.5"> | ||
| <Icon icon={ info } size={ 16 } /> | ||
| { pushState.status.message } | ||
| { getPushUploadMessage( pushState.status.message, uploadPercentage ) } |
There was a problem hiding this comment.
Nice! Could also be memoized with useMemo, but fine as-is.
- Add sync-upload-progress IPC event to send upload progress from main process - Update Tus onProgress callback to send progress percentage (0-100%) - Store raw upload progress in SyncPushState for display - Display upload percentage next to 'Uploading Studio site…' message - Progress updates in real-time as chunks are uploaded via Tus protocol Fixes STU-1144
- Move upload progress mapping logic to useSyncStatesProgressInfo hook - Add getPushUploadMessage helper for i18n-formatted messages - Add isKeyUploading helper function - Update message format to show percentage in parentheses: 'Uploading site (98%)…' - Follow i18n best practices using sprintf for formatted messages - Centralize all progress-related calculations in one place
e41d130 to
9d16c42
Compare
There was a problem hiding this comment.
This LGTM and work well! 👍
CleanShot.2026-01-06.at.10.13.05.mp4
Just a small heads up that I've been working on refactoring the pull/push states in #2037. I think we can proceed with merging this and it will be fairly straightforward to resolve any conflicts.
Migrate the changes of PR #2332
…#2037) * add new slice for pull and push states * update useSyncPull and useSyncPush hooks * add selectors from hook to slice * move clear/cancel operations to thunks * move pushsite to thunk * move pullsite to thunk * extract polling logic to separate hook * Remove SyncSitesProvider context, use hooks directly 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 * move polling logic to thunk * remove unused onPullSuccess/onPushSuccess props * remove unused actions * fix unused reference after merge * update tests * add null checks * fix linter errors * add runtime checks * add optional chaining to progress data checks * Merge trunk and resolve conflicts * Revert "Merge trunk and resolve conflicts" This reverts commit 55b1c33. * Add back upload progress indicator Migrate the changes of PR #2332 * Compare status keys directly, remove unecessary helpers * make clearPullState and clearPushState thunks responsible for clearing the state as well * Inline single-use helper functions into their sole callers Inline getPushStatusWithProgress, getBackupStatusWithProgress, and completePullThunk directly into their only call sites within pollPushProgressThunk and pollPullBackupThunk. Remove the now-unused copies of getPushStatusWithProgress and getBackupStatusWithProgress from useSyncStatesProgressInfo. * Remove redundant remoteSiteId from updatePushState/updatePullState call sites The reducers now auto-set remoteSiteId from the top-level payload, so callers no longer need to pass it in the nested state object. * Remove updatePushStateWithIpc, move IPC sync to listener middleware The calls to addSyncOperation/clearSyncOperation are now an effect of Redux state updates via listener middleware, rather than being manually managed at each call site. This removes the coupling between state updates and IPC side effects. * Guard against missing status in backup polling response The backup API can return error responses (e.g. job_not_found) without a status field. Without this guard, response.status is undefined, which silently breaks the pull state progression. * Replace if-else chain with switch statement in pollPushProgressThunk, remove getPullStatusWithProgress * Move error state handling to extraReducers, use rejectWithValue in thunks * Move error modals to listener middleware, remove showErrorMessageBox from thunks * Add createAsyncThunk condition to polling thunks, remove manual cancellation guards * Remove isKey* helpers, replace with direct comparisons in sync-connected-sites * Move progress info definitions into slice, remove from thunk payloads * Move polling to listener middleware, delete use-sync-polling hook * Move useInitializeSyncStates to initializeSyncStatesThunk, move mapImportResponseToPushState to slice * Remove useSyncPull and useSyncPush hooks, replace with direct Redux access Delete the wrapper hooks and update all consumers to use useRootSelector + syncOperationsSelectors for reactive state and dispatch + syncOperationsThunks for actions. Move shared types (SyncBackupState, PullSiteOptions, PullStates, SyncPushState, PushStates) into the slice and re-export from stores/sync. * use selectors in settings-site-menu * Replace non-reactive store.getState() with useRootSelector for sync state store.getState() in component render bodies doesn't subscribe to Redux changes, so sync UI (spinners, disabled states) wouldn't update reactively. * fix tests * Addressing review comments * Clean up * Cleanup * Fix bad merge and clear import state when pulls finish * zod schemas * Correctness and simplification * Fix schema * Fix more merge issues * Bring back ability to manually pause pushes * Re-enable skipped tests * Restore more of the tests * Restore even more of the tests * Fix diff * Minor tweaks * remove unused export * Fix tests * Update listeners to react to state updates rather than action types, and to cancel other active listeners * Revert "Update listeners to react to state updates rather than action types, and to cancel other active listeners" This reverts commit e041e31. * Simplify polling setup * Fix bug 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. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Fredrik Rombach Ekelund <fredrik.rombach.ekelund@automattic.com>

Related
Fixes STU-1144
Summary
This PR implements file upload progress tracking for sync operations, showing the upload percentage (0-100%) next to the "Uploading Studio site…" message in the UI.
Changes
Testing