feat(app): add PWA support with service worker and update prompt#31279
Open
shoootyou wants to merge 37 commits into
Open
feat(app): add PWA support with service worker and update prompt#31279shoootyou wants to merge 37 commits into
shoootyou wants to merge 37 commits into
Conversation
- set theme_color and background_color to #F8F7F7 to match <head> meta - add purpose:any icon entry (512x512) to satisfy Lighthouse PWA audit Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- install vite-plugin-pwa@1.3.0 and workbox-window - configure VitePWA with prompt registration and NetworkOnly for localhost:4096 - implement PwaUpdatePrompt component (B1-B8) using SolidJS programmatic API - mount PwaUpdatePrompt outside provider tree in entry.tsx - add vite-plugin-pwa/solid types to tsconfig - add solid-web-browser-shim.ts preload to fix bun test JSX resolution (bun test uses node condition for solid-js; shim redirects to browser builds and provides React.createElement shim for JSX compat) - update bunfig.toml to load solid-web-browser-shim.ts in test preload Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- set id: '/' to stabilize app identity across domains - set start_url: '/' to fix Lighthouse 'start_url is not valid' error Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- resolve from src/ → app/public/, not packages/public/ Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
useRegisterSW returns needRefresh as [Accessor<boolean>, Setter<boolean>] not a bare accessor. Calling it directly threw TypeError at runtime. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Real API returns needRefresh as [Accessor<boolean>, Setter<boolean>]. Previous bare-accessor mock hid the runtime TypeError. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
…ld tests - Fix createComponent(Show, ...) overload ambiguity via props cast - Fix _banner() call: template() returns a factory function, not an element; call it directly instead of .cloneNode() on the function reference - Add tests for start_url, id, theme_color exact value, and background_color in pwa.manifest.test.ts — all fields listed in plan must_haves.artifacts Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Import JSX type directly from solid-js instead of the invalid
typeof import('solid-js').JSX.Element namespace reference.
Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Navigation requests are relative paths in production; the absolute URL pattern for localhost:4096 was misleading and had no effect. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Verifies banner reappears if SW fires onNeedRefresh after user dismisses. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Without this, the manifest is not in the SW precache and a network miss during install eligibility check silently prevents the install prompt. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
…gnal gate - call SolidJS render dispose() to prevent reactive tree leaks per test - extract findButton() helper with exact label matching - B9 now pins that needRefresh() participates in visibility gate Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- add apple-mobile-web-app-capable for standalone mode on pre-iOS-17 - add apple-mobile-web-app-status-bar-style and title - add 152x152 and 167x167 apple-touch-icon links for iPad and iPad Pro Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- add scope: '/' for explicit PWA scope declaration - add purpose:any entry for 192x192 icon (Android older launchers) - add SVG icon entry for Chromium desktop crisp rendering Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- desktop: 1280x720 (form_factor: wide) - mobile: 390x844 (form_factor: narrow) - source: opencode.ai docs screenshot Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Resized from apple-touch-icon-v3.png (180x180) for: - 152x152: iPad / iPad mini (2x) - 167x167: iPad Pro retina Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Use space-separated 'maskable any' per W3C spec instead of two separate entries — avoids Lighthouse audit warnings. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
…rompt Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Add onRegisterError callback to show the banner again if the service worker update fails, preventing silent prompt loss. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
apple-touch-icon.png, favicon-96x96.png, favicon.ico, favicon.svg are all superseded by their -v3 counterparts referenced in index.html. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
…ations - B10: verify banner reappears after SW registration error (onRegisterError recovery path) - remove '(fails until E5)' suffixes from manifest test names Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Use portable path resolution instead of machine-specific absolute path. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
role=status does not universally imply a live region across all AT. Explicit aria-live=polite ensures screen readers announce the update banner when it appears. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
The mock updateServiceWorker resets needRefresh to false on Reload. triggerRegisterError must restore it before firing onRegisterError so the show() && needRefresh() gate can pass and B10 is valid. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
…waUpdatePrompt - replace neutral-* with semantic design system tokens for theme compat - remove aria-live=polite (redundant — role=status implies it per ARIA spec) Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
It is a build-time tool only — workbox-window stays in dependencies as it is imported at runtime via virtual:pwa-register/solid. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- 'maskable any' combined purpose discouraged per Chrome/Lighthouse - SVG icon removed from manifest (fails to load in SW context) - use separate maskable/any entries for 512x512 PNG - replace PWA screenshots with real captures (1440x940 desktop, 390x844 mobile) Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
- add crossorigin=use-credentials to manifest link for auth proxy compat - add Cache-Control: immutable to /assets/* in _headers for edge caching Addresses anomalyco#30405, anomalyco#19301, anomalyco#27931 Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
…nylist - remove onRegisterError: setShow(true) was dead code (needRefresh() stays false after handleReload, so the show() && needRefresh() gate never passed) - add purpose:any entry for 192x192 icon in manifest - add /pty/* to navigateFallbackDenylist Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
The component's onRegisterError set show(true) but needRefresh() stays false after handleReload, so show() && needRefresh() never passed. The behavior was untestable in production. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
bunfig.toml already declares both happydom.ts and solid-web-browser-shim.ts as test preloads. The explicit --preload flags in package.json scripts caused GlobalRegistrator.register() to run twice per test process. Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
…x192 any icon test - add og:title, og:description, og:url to index.html - add label field to both screenshots in site.webmanifest - add Cache-Control: no-cache to root-level JS/CSS/MJS in _headers - pin 192x192 any-purpose icon in manifest test Co-authored-by: yui-soul <yui-soul@users.noreply.github.com>
Contributor
|
The following comment was made by an LLM, it may be inaccurate: Potential related PRs found:
These are not duplicates of PR #31279 but represent overlapping work on the same system (PWA, service workers, caching). The PR author is already aware of the #30399 overlap and has offered to coordinate. |
Author
|
Tested locally Screen.Recording.2026-06-07.at.22.14.49.mov |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes #19174
Closes #19119
Closes #27933
Addresses #27931
Addresses #30405
Addresses #19301
Note: #30405 is also addressed by the open PR #30399 — both add
crossorigin="use-credentials"to the manifest link. Happy to drop that commit from this PR if #30399 merges first.Type of change
What does this PR do?
The web UI had no service worker, so every reload re-fetched all assets and the app couldn't be installed as a PWA.
Service worker — adds
vite-plugin-pwawithregisterType: 'prompt'and Workbox precaching for the static shell (JS, CSS, fonts, icons). Only explicit SPA routes receive the navigation fallback — all other paths pass to the network by default (navigateFallbackAllowlist). SW is disabled in dev to preserve Vite HMR.Update prompt —
PwaUpdatePromptcomponent shows a non-blocking banner when a new SW is waiting. Reload activates it; Dismiss defers it. Mounted outsideAppBaseProvidersso it survives ErrorBoundary and connection errors.Manifest fixes — adds
id,scope,start_url,description, andscreenshotsfields. Fixestheme_colorfrom#ffffffto#F8F7F7. Separatemaskableandanyicon entries per Chrome/Lighthouse guidance.iOS — adds
apple-mobile-web-app-capable, status bar style, andapple-touch-iconlinks for iPad (152×152, 167×167) alongside the existing 180×180.Cache-Control —
Cache-Control: immutableon/assets/*in_headers,no-cacheon root-level assets.Proxy fix —
crossorigin="use-credentials"on the manifest link (same fix as #30399).How did you verify your code works?
bun testinpackages/apppasses (component tests B1–B9, manifest field tests).bun run build+bun run serveinpackages/app— install prompt appeared, SW registered and cached the shell, update banner showed after a second build and reload.Screenshots / recordings
Screenshots of the install prompt, update banner, and Lighthouse audit available on request.
Checklist