[personal-wp] Add update flow guard for PWA#3802
Conversation
|
Interesting! Did you notice an incompatibility (= broken boot?) yourself? |
|
This is awesome, thanks for flagging and fixing! Is there a reason why you would not want to do the update? I had assumed then it would not boot at all. Thus we could just forgo showing a toast with a decision and just do the update? |
|
There are a few scenarios:
|
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a Personal WP–specific PWA update/versioning flow to prevent stale app shells from booting against newer deployed assets, and fixes offline caching crashes caused by duplicate manifest URLs.
Changes:
- Generate and serve a no-store
app-version.jsonartifact and exclude it from offline/service-worker caching. - Add a Personal WP update gate/notice flow with service worker update + cross-tab update propagation via
BroadcastChannel. - Deduplicate offline manifest URLs before caching and add unit/E2E coverage for update + offline behaviors.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/vite-extensions/vite-list-assets-required-for-offline-mode.ts | Excludes app-version.json from offline manifest and deduplicates listed asset URLs. |
| packages/vite-extensions/vite-build-version.ts | Exposes reusable build-version getter used by both virtual module and build artifact generation. |
| packages/playground/website-deployment/tests.php | Adds regression test asserting app-version.json is not edge/browser cached. |
| packages/playground/website-deployment/custom-redirects-lib.php | Applies no-store headers for app-version.json responses. |
| packages/playground/remote/src/lib/offline-mode-cache.ts | Deduplicates URLs before Cache.addAll() and excludes app-version.json from caching decisions. |
| packages/playground/remote/src/lib/offline-mode-cache.spec.ts | Tests URL deduplication to prevent Cache.addAll() duplicate-request failures. |
| packages/playground/personal-wp/vite.config.ts | Emits app-version.json during build using the shared build version source. |
| packages/playground/personal-wp/src/main.tsx | Wraps the app in an update provider + gate and mounts the update notice UI. |
| packages/playground/personal-wp/src/lib/pwa-update/use-app-update.tsx | Implements periodic/focus/visibility update checks + required/notice state and broadcast integration. |
| packages/playground/personal-wp/src/lib/pwa-update/update-channel.ts | Adds BroadcastChannel helpers to propagate update availability across tabs. |
| packages/playground/personal-wp/src/lib/pwa-update/update-channel.spec.ts | Unit tests for update broadcast and sender self-filtering behavior. |
| packages/playground/personal-wp/src/lib/pwa-update/apply-update.ts | Applies updates via SW registration update + controllerchange race + reload fallback. |
| packages/playground/personal-wp/src/lib/pwa-update/apply-update.spec.ts | Unit tests covering controllerchange, update failures, and hanging SW promises/timeouts. |
| packages/playground/personal-wp/src/lib/pwa-update/app-version.ts | Fetches/validates app-version.json with cache-busting + timeout and reports update availability. |
| packages/playground/personal-wp/src/lib/pwa-update/app-version.spec.ts | Unit tests for current/update/invalid/failed/not-ok version check outcomes. |
| packages/playground/personal-wp/src/components/app-update-notice/style.module.css | Styles for the required update screen, loading gate, and compact toast notice. |
| packages/playground/personal-wp/src/components/app-update-notice/index.tsx | Renders the gate, toast notice, loading screen, and required update UI. |
| packages/playground/personal-wp/playwright/e2e/app-update.spec.ts | E2E coverage for stale-load blocking, notice dismissal, reload update, failure fallback, and two-tab broadcast. |
| packages/playground/personal-wp/.htaccess | Ensures app-version.json is served with no-store headers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const updateAndWaitForController = (async () => { | ||
| await updateServiceWorker(serviceWorker); | ||
| await controllerChanged; | ||
| })(); | ||
| await Promise.race([updateAndWaitForController, delay(timeoutMs)]); | ||
| removeControllerChangeListener(); | ||
| reloadOnce(); |
| <div className={css.notice} role="status" aria-live="polite"> | ||
| <div className={css.noticeContent}> | ||
| <strong>Update available</strong> | ||
| <span>Restart My WordPress to load the latest version.</span> | ||
| </div> | ||
| <div className={css.noticeActions}> |
| const files = Array.from( | ||
| new Set( | ||
| distDirectoriesToList.flatMap((dir) => { | ||
| const absoluteDirPath = join(outputDir, dir); | ||
| console.log(`Listing files in ${absoluteDirPath}`); | ||
| return listFiles(absoluteDirPath) |
| }) | ||
| .filter((item) => { | ||
| return !patternsToNotCache.some((pattern) => { | ||
| if (pattern instanceof RegExp) { | ||
| return pattern.test(item); | ||
| } | ||
| return pattern === item; | ||
| }); | ||
| }); | ||
| }); | ||
| ) | ||
| ); |
What changed
app-version.jsonartifact for Personal WP production builds using the bundled build version.controllerchangewhen available, and reloads once.Update nowflow so the reload fallback timeout starts before service-workergetRegistration(),ready, orupdate()promises can hang.BroadcastChannelto propagate update availability across open Personal WP tabs.@wordpress/componentsbuttons for the compact update toast actions.app-version.jsonas no-store in Personal WP.htaccessand website deployment redirects, and excludes it from offline/service-worker caching.Cache.addAll()and in the generated offline manifest so duplicate entries such as/sw.jscannot crash offline cache population.Update nowreload, offline/version-check failure, and two-tab update broadcast.Scope note
The update guard and
app-version.jsonendpoint are specific to Personal WP /my.wordpress.net.The offline-cache deduplication uses shared
playground-remoteservice worker code and the shared offline-manifest Vite helper. That means it also affects the website package used forplayground.wordpress.net, but only as a narrow bug fix: duplicate offline asset URLs are collapsed before caching. It does not change which unique assets are cached, routing, domains, or website app-shell behavior.Why
Users with an installed Personal WP PWA can otherwise keep running an old app shell against newer deployed Playground assets. That mismatch can crash startup flows. The version endpoint lets the app detect the mismatch before WordPress boots and prompt for an update instead.
This also avoids the observed
Cache.addAll(): duplicate requestsfailure when an offline manifest contains duplicate asset URLs such as/sw.js.Validation
npm exec nx test playground-personal-wp -- --runInBandnpm exec nx test playground-personal-wpnpm exec nx test playground-personal-wp --testFile=apply-update.spec.tsnpm exec nx typecheck playground-personal-wpnpm exec nx lint playground-personal-wpnpm exec nx run playground-personal-wp:e2e:playwrightnpm exec nx run playground-personal-wp:e2e:playwright -- app-update.spec.tsnpm exec nx test playground-remote --testFile=offline-mode-cache.spec.tsnpm exec nx typecheck playground-remotenpm exec nx lint playground-remotenpm exec nx build playground-personal-wpphp packages/playground/website-deployment/tests.phpgit diff --checkdist/packages/playground/my-wordpress-net/app-version.jsonhas schemapersonal-wp-app-version/v1and is not listed inassets-required-for-offline-mode.json.dist/packages/playground/personal-wp/assets-required-for-offline-mode.jsonanddist/packages/playground/my-wordpress-net/assets-required-for-offline-mode.jsonhave no duplicate URLs and include/sw.jsexactly once.