Skip to content

refactor: migrate PWA update prompt to nuxt-skew-protection#294

Open
RedStar071 wants to merge 7 commits into
mainfrom
claude/nuxt-skew-protection-r25ael
Open

refactor: migrate PWA update prompt to nuxt-skew-protection#294
RedStar071 wants to merge 7 commits into
mainfrom
claude/nuxt-skew-protection-r25ael

Conversation

@RedStar071

@RedStar071 RedStar071 commented Jul 11, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

🧭 Context

This change migrates the PWA update prompt logic from the custom @vite-pwa/nuxt implementation to the nuxt-skew-protection module, which provides more robust deployment detection and update handling.

📚 Description

Changes made:

  1. PWA Prompt Component (app/components/pwa/Prompt.client.vue)

    • Wrapped the update notification UI with SkewNotification component, which manages the visibility state and reload/dismiss actions
    • Replaced direct $pwa API calls with scoped slot bindings (isOpen, reload, dismiss)
    • Simplified state management by delegating to the skew-protection module
  2. PWA Configuration (config/pwa.ts)

    • Removed periodicSyncForUpdates: 3600 — skew-protection handles polling internally
    • Changed registerType from "prompt" to "autoUpdate" — the service worker now applies asset updates silently in the background while skew-protection owns deployment detection and the reload prompt
  3. Nuxt Configuration (nuxt.config.ts)

    • Added nuxt-skew-protection to modules list
    • Added skewProtection config block with:
      • Storage backend using Netlify Blobs on Netlify deployments (matching the cache storage setup)
      • Polling-based update strategy (more reliable than SSE/WS for serverless functions)
      • bundleAssets: false to avoid build-time asset uploads until confirmed safe on Netlify
  4. Dependencies (package.json)

    • Added nuxt-skew-protection@^1.3.0

Why this change:

The nuxt-skew-protection module provides a more reliable and maintainable approach to detecting new deployments and prompting users to reload. It decouples deployment detection from the service worker lifecycle, uses configurable polling/streaming strategies, and integrates with persistent storage backends for better reliability across deployments.

Test Plan

Existing PWA functionality is preserved through the new module. The update prompt UI remains visually and functionally identical; only the underlying state management has changed. CI tests should pass without modification.

https://claude.ai/code/session_019Z94QMdW4SEC1nmLXBM6BL


View with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is enabled.

Confidence Score: 5/5

Safe to merge — the migration is self-contained, all changed paths are well-commented, and the version-aware dismiss logic correctly handles the polling case where serverVersion is undefined.

The rewrite of the update prompt is careful and internally consistent. The currentVersion fallback to manifest.value?.id correctly addresses the polling-dismissal problem noted in earlier review rounds. The empty onAppOutdated side-effect, isPrerendered guard, and activeViewTransition check are all justified by comments. No data-loss, auth, or runtime-crash paths were introduced.

No files require special attention.

Reviews (4): Last reviewed commit: "fix(pwa): use manifest id instead of ser..." | Re-trigger Greptile

…ction

The "new deployment available" prompt was previously an implicit side
effect of the Workbox service worker (registerType: "prompt" +
periodicSyncForUpdates), polling only as often as the SW itself and
only for clients that already had it installed.

nuxt-skew-protection now owns that job exclusively via its own
polling and <SkewNotification>, backed by Netlify Blobs storage
(mirroring the existing cache/fetch-cache Nitro mounts). The service
worker keeps its original job — offline caching and installability —
unchanged, just applying asset updates silently (registerType:
"autoUpdate") instead of holding them for a user prompt.
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.68%. Comparing base (243bb14) to head (dbb2e7c).
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #294   +/-   ##
=======================================
  Coverage   69.68%   69.68%           
=======================================
  Files         136      136           
  Lines        3002     3002           
  Branches      648      648           
=======================================
  Hits         2092     2092           
  Misses        456      456           
  Partials      454      454           
Flag Coverage Δ
component 60.29% <ø> (ø)
unit 69.54% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
package.json 100.00% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 44 untouched benchmarks


Comparing claude/nuxt-skew-protection-r25ael (dbb2e7c) with main (243bb14)

Open in CodSpeed

claude added 2 commits July 11, 2026 20:47
Replaces the Vue <Transition> wrapper (and the headless
<SkewNotification> slot component) with the same
document.startViewTransition pattern used elsewhere in the app
(guild/cards.vue, dashboard.vue) — consuming useSkewProtection()
directly for script-level control over the transition, guarded by
reduced-motion and an already-active-transition check.
Adding nuxt-skew-protection changed pnpm-lock.yaml enough that knip
now resolves @nuxtjs/robots as a satisfied dependency on its own,
making the suppression entry dead config. knip.ts sets
treatConfigHintsAsErrors, so the stale entry was failing CI's
"Unused code check" job on PR #294.
Comment thread app/components/pwa/Prompt.client.vue Outdated
claude added 2 commits July 11, 2026 20:58
dismissed was a sticky boolean that, once set, permanently gated
isOpen to false for the rest of the tab's lifetime -- a user
dismissing one prompt would never see a reload prompt for any
subsequent deployment in that tab. Track the dismissed server
version instead, so a newer deploy (a changed serverVersion)
re-opens the prompt while the already-dismissed one stays hidden.
Keeps it in sync with the rest of the Nuxt SEO family now that
nuxt-skew-protection is installed alongside it. Verified with a full
typecheck, lint, knip, unit-test, and production build pass.
Comment thread app/components/pwa/Prompt.client.vue Outdated
RedStar071 and others added 2 commits July 11, 2026 21:35
In polling mode nuxt-skew-protection registers no connection plugin, so `skew:message` never fires and `serverVersion` stays undefined. Comparing it against the dismissed value made the version-based re-open logic a no-op: the first dismiss stored undefined, and every later polling-detected deployment still saw undefined === undefined, suppressing the prompt for the rest of the tab (and the initial prompt too, since it started undefined).

Track the deployment identity via `serverVersion` with a fallback to the manifest build id, which changes on every deployment and is always set when `isAppOutdated` is true.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…king

serverVersion is only ever populated by nuxt-skew-protection from
WS/SSE skew:message events -- never from the polling update strategy
this app uses -- so it stayed undefined forever and the previous
dismissal tracking never re-opened the prompt for later deployments.

Also register onAppOutdated(), which is what actually populates
`manifest` (and therefore drives isAppOutdated) on the polling path;
without a listener registered, manifest never updates and
isAppOutdated could never become true.

manifest.id is populated by both the polling and WS/SSE paths, so
dismissal tracking now works under either strategy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants