Implicit
jg-pwa
Manifests, install prompts, service worker updates, caching, and push for progressive web apps.
- Usage
- Loads automatically
When to use it
It loads on its own when a task touches a web app manifest, install prompts, a service worker, offline caching, or web push. Call it by name to load it before such a task.
When it loads
Your agent loads the skill on its own when a request like this touches its area.
Make the new dashboard installable on phones and desktops.
Adds a complete manifest with standard and maskable icons, theme color, and an iOS touch icon, and proposes a service worker only if offline use is in scope.
SKILL.md
What the agent reads
The complete instructions your agent loads when a task touches its area.
Application
The agent loads this skill on its own when a task touches its area. This skill’s area is progressive web apps: web app manifests and install metadata, install prompts, service worker registration, updates, and caching, offline behavior, web push notifications and app badges, and Home Screen web app behavior on iOS and Safari.
- It adds domain rules to the task. It never authorizes actions, widens scope, or replaces the workflow or approvals of a skill the developer invoked.
- It ranks with the repository’s coding instructions. When they conflict, the coding instructions win, except for the exceptions this skill states for its own area.
- The repository’s established conventions win over this skill’s naming, layout, and style rules. Its correctness and safety rules, and the exceptions it states for its own area, still apply.
- Check the installed versions first. This skill was written for the web platform as of October 2026, meaning current Chromium browsers, Firefox, and Safari 27 on macOS, iOS, and iPadOS, plus Playwright 1.64, and
jg-sw1.4 where installed. An item marked with a minimum version, written like (18+), applies only when that version or a later one is installed. For an older installed version, follow that version’s official documentation. - Before relying on one of this skill’s version-specific items, check it against the current official guidance when the installed version of the technology is newer than the one this skill was written for, or when the version bullet gives a review month for something without an installed version, such as a hosted service or browser behavior, and that month is more than six months before the current date. Where an item differs from that guidance, follow the guidance. Version-specific items are API, syntax, and feature recommendations, and anything marked with a minimum version. This skill’s conventions and requirements, such as its naming, layout, style, reuse, and safety rules, keep their usual precedence even where official guidance suggests otherwise. Report each item that looks outdated in the summary, with the installed version and what the current guidance says, and offer there to open an issue on the jg-skills repository,
https://github.com/jesusgraterol/jg-skills:- Never pause or stop a run for the offer, including an autonomous run. When no one can answer, include the pre-filled link described below instead of asking.
- First search the repository’s open issues, and skip the offer when one already covers the item. When the search can’t run, say so next to the link, so the developer checks the existing issues before submitting.
- Open an issue only after the developer agrees, because it is public. Title it
<skill>: <item> looks outdated on <technology> <version>, and include only the skill name, the outdated item, the installed version, and a link to the official documentation, never code, file paths, or project details. Add no label. - Use
gh issue createwhenghis installed and signed in. Otherwise give the developer a pre-filled link,https://github.com/jesusgraterol/jg-skills/issues/new?title=…&body=…, with both values URL-encoded.
- This skill isn’t a complete list. Where it says nothing, follow the official guidance for the installed version, and in new code the task touches, prefer the features that guidance currently recommends over older ones that do the same job, unless the repository has an established pattern for that job. Before using a newer approach instead of one this skill gives, confirm it in that version’s official documentation, never from memory alone, and report the item as outdated as described above. When that documentation can’t be reached, keep this skill’s approach and report that the newer approach couldn’t be confirmed, without pausing the run.
- Rules that say “prefer” are defaults: departing from one with a stated reason is fine. Rules that say “never”, “always”, or “must” are requirements.
- Nothing in this skill requires adding a dependency, enabling a compiler or tool, upgrading, or migrating existing code.
Reference files
These files belong to this skill and hold rules that apply only to some work. Read each one before the work it covers:
- Before adding or changing offline behavior, such as an offline page, offline data, or retrying work after reconnecting, read
references/offline.md. Navigations fall back to the offline page only when the network request fails, never on an error response. - Before adding or changing web push notifications, notification permission requests, or app badges, read
references/notifications.md. Ask for notification permission only from a click on a control that explains what the user will get.
Scope
- Make a web application installable only when the developer asks for it or an approved plan includes it. An installable app gets the manifest and install metadata below, without a service worker.
- Add a service worker only when the task or an approved plan calls for one, such as for offline use or push, and reuse the repository’s established service-worker tooling.
- Propose hosting, CDN, and dependency changes, such as new response headers or a service-worker library, instead of making them.
- Never migrate an existing app’s manifest or service worker only to follow this skill.
Manifest and install metadata
- An installable app has a web app manifest, linked with
<link rel="manifest">from every HTML entry point, withid,name,short_name,description,start_url,scope,display: "standalone",theme_color, andbackground_color. - Set
idexplicitly and never change it, because browsers treat an app with a differentidas a different app. - Provide 192×192 and 512×512 PNG icons with
purpose: "any", plus separatemaskableicons whose content fits the central safe circle, with a radius of 40% of the icon’s width. Never combine them as"any maskable"in one entry, because the padding a maskable icon needs shrinks it everywhere else. - Prefer
screenshotswith bothform_factor: "wide"andform_factor: "narrow", each with alabel, for Chromium’s richer install dialog. - Add
shortcutsordisplay_overrideonly for a real need, and never setorientationunless the app can’t work in another one. - Serve the manifest as
application/manifest+jsonwithCache-Control: no-cache, and never let the single-page-app fallback answer its URL with HTML. <meta name="theme-color">follows the active theme: prefer one tag perprefers-color-schemevalue, and update it when the user switches the app’s theme. (Safari 26+) Safari uses it only for installed web apps.- Add a 180×180 opaque PNG
apple-touch-icon, which iOS and iPadOS use on the Home Screen.
Installability
- Chromium lets users install a site from the browser menu without a service worker (Android 108+, desktop 112+). Its automatic install prompt also needs user engagement and a service worker with a fetch handler.
- Never add a service worker, or an empty fetch handler, only to qualify for the install prompt.
- (iOS and iPadOS 26+) Safari opens every site added to the Home Screen as a web app. Users install through Share, then Add to Home Screen, and on macOS (Safari 17+) through File, then Add to Dock.
Install experience
beforeinstallpromptis Chromium-only. Capture it withpreventDefault(), show the app’s own install control only while a captured event exists, and call itsprompt()only from a click.- Never show a blocking or page-covering install invitation on first load. Prefer a permanent entry in a menu or in settings, plus at most one dismissible invitation after real engagement, and remember the dismissal.
- Detect the installed context with
matchMedia('(display-mode: standalone)'), and hide install controls onappinstalled. - On iOS and iPadOS, show Add to Home Screen instructions only where installing unlocks something, such as push notifications.
Service worker requirements
- Never cache or serve dev-server modules. In development, register nothing, or a worker without a caching fetch handler, such as an empty
public/sw.js, and test the real worker on a production build. - Serve the worker from the root of its scope, such as
/sw.js, with theService-Worker-Allowedheader for a broader scope, and never rename it per release, because a registration updates only from its own URL. - Serve the worker script with
Cache-Control: no-cacheorno-store. The defaultupdateViaCachealready fetches the main script past the HTTP cache; preferupdateViaCache: 'none'when content loaded throughimportScripts()can change without its URL changing. - Prefer registering the worker after the
loadevent when it precaches, so precaching doesn’t compete with the first page load. - An open page keeps working with the build it loaded: never delete a previous build’s cache in
install, and never callskipWaiting()unconditionally when the worker serves HTML or code-split assets. - Prefix cache names with the app’s name and a build version, and in
activatedelete only that prefix’s older caches, never caches of other apps on the same origin. - Precache only content-hashed build output, plus the shell and the offline page when offline use is in scope, never files the app doesn’t load, such as manifest screenshots.
- Handle only same-origin
GETrequests, and cache onlyokresponses, never opaque,206,Vary: *, or error responses. - Never cache authenticated or personal responses unless the feature is designed for it, and then clear them on sign-out and on account switch. Never cache responses marked
no-storeorprivate. - For hand-written workers, never serve navigation HTML cache-first. With jg-sw, an explicitly precached shell may be cache-first: each build has a fresh cache, installation never skips waiting, and users accept the waiting worker before activation and reload. Keep all other HTML excluded from runtime caching.
- Keep a recovery path: to remove a worker, deploy a replacement at the same URL that deletes the app’s caches and unregisters itself, because deleting
sw.jsleaves the installed worker running.
Updates and deploys
- Tell users when a new version is ready and let them choose when to reload. Never reload on its own over unsaved input.
- When the worker serves the shell, detect the waiting worker, such as
registration.waitingor an installed worker afterupdatefound, have it callskipWaiting()when the user accepts, and reload once oncontrollerchange, behind a guard so it can’t loop. - When HTML always comes from the network, a plain reload loads the new build.
- Missing-chunk 404s, HTML revalidation, and guarded recovery from failed chunk loads belong to jg-frontend.
Offline
- Add offline behavior only when the task or an approved plan calls for it.
- Treat
navigator.onLineas a hint:falsemeans offline, buttruedoesn’t prove a connection. While offline, show one offline notice instead of an error toast per failed request, and never disable features or hide errors only because of it.
Using jg-sw
When the repository uses jg-sw, resolve its installed version and read its bundled API.md, then RECIPES.md and README for the relevant task. An older installed contract takes precedence over a newer recipe; never assume a version-gated option exists.
- Use
serviceWorkerPluginfromjg-sw/buildwith Vite: it generates the worker aftervite buildand serves one without caching duringvite, so no emptypublic/sw.jsis needed. Without the plugin, run the CLI after the bundler, such asvite build && jg-sw. - Give each app on a shared origin its own
cacheNamePrefix. - When offline use is in scope, precache
/,/index.html, and the content-hashed assets directory throughincludeToPrecache, as the offline recipe does. Each build uses a new<cacheNamePrefix>--<10 random characters>cache. Keeptext/html, API-data MIME types such asapplication/json,audio/, andvideo/inexcludeMIMETypesFromCache; the explicitly precached shell is the HTML exception. - (1.2+) Set
navigationFallbackto a precached page:/index.htmllets offline client routes such as/settingsopen, or use a dedicated offline page for a multi-page site. It answers only navigations absent from the cache whose network request fails, never a server’s 4xx or 5xx response. The build fails withNAVIGATION_FALLBACK_NOT_PRECACHEDwhen the fallback isn’t precached. - Runtime caching rejects responses marked
Cache-Control: no-storeorprivate, as well as opaque,206,Vary: *, and error responses. Keep private same-origin responses out with those headers or an appropriate MIME exclusion; they don’t need another origin solely to avoid this cache. - jg-sw has no navigation preload or static routing. Propose package additions when needed instead of editing the generated worker.
- Read
hasWaitingWorkerfromgetServiceWorkerSnapshot()and follow changes withsubscribeToServiceWorker(listener), which also works with React’suseSyncExternalStore. CallupdateApp()from a user action: it messages the waiting worker to activate and reloads once. Without one, it checks for an update, waits up to 30 seconds for installation, then reloads once even if no update installs. Never reload on its own over unsaved input. - In a Vite app, also offer
updateApp()when the snapshot’spreloadErrorDetectedis true (1.3+): a chunk of a build a newer deployment replaced failed to load while online, on a page with the build’s entry module, and jg-sw prevented Vite’s error. CallregisterServiceWorker()as the app starts, so it hears the first failure. A failure offline, or on a page without an entry module, stays Vite’s error, for the app’s own error handling. registerServiceWorker()starts installation tracking even when registration fails or workers are unsupported. Show installation controls only wheninstallation.canInstallorcanInstallApp()allows it, and callinstallApp()from a click; an available prompt resolves to'accepted'or'dismissed'.- (1.4+) Use
isIosBrowserTab()to decide where Add to Home Screen instructions apply: it is true on an iPhone, iPad, or iPod, including an iPad in desktop mode, in a browser tab rather than the installed app. registerServiceWorker()never rejects; failures appear inregistrationErroras anExceptionwith anERRORScode. TreatSW_NO_BROWSER_SUPPORTas online-only operation, not an error to report. Register afterloadwhen precaching, as the recipe does.- When the worker’s response carries a Content Security Policy, allow the Firebase compat SDK scripts that the
firebase-fcmtemplate loads fromhttps://www.gstatic.comthroughimportScripts().
Repositories still using sw-builder and sw-service
jg-sw replaced sw-builder and sw-service; the older packages are not deprecated. Keep existing apps on their established tooling unless migration is part of the task. For their 1.2 contracts:
- Run the bundler before
sw-builderand keep the empty developmentpublic/sw.js. Keep/andindex.htmlout ofincludeToPrecache, and excludetext/html,audio/, andvideo/from runtime caching. sw-builderignoresCache-Control, so keep private same-origin responses on another origin or excluded by MIME type. It has no offline fallback or navigation preload; propose additions rather than editing generated workers. Its Firebase template needs the same Content Security Policy allowance.sw-servicehas no waiting-worker snapshot: detect new versions in the app and apply them withSWService.updateApp(). ItsSWService.installerexists only after successful registration, so don’t rely on it for install controls when registration fails.
iOS and Safari
- Safari has no
beforeinstallprompt, Background Sync, Periodic Background Sync, or Background Fetch. - Home Screen web apps don’t share storage or sign-in with Safari, so users sign in again after installing.
- With tracking prevention on, Safari deletes script-writable storage after 7 days of Safari use without interaction with the site. Home Screen web apps keep their own counter, and (Safari 17+)
navigator.storage.persist()can exempt an origin. - Home Screen web apps support only the
standaloneandbrowserdisplay modes, and links never open in an installed app.
When the numbers justify it
- The static routing API,
event.addRoutes()in the worker’sinstallevent (Chrome 123+, Safari 27+, not Baseline), sends chosen requests to the network or the cache without starting the worker. - Tune navigation preload, such as the value of its header, when worker startup measurably delays navigations.
Verification
- Lighthouse 12 removed the PWA category, so never cite a PWA score. Check installability and the worker in the browser’s Application panel instead.
- In Playwright, set
serviceWorkers: 'block'in tests that usepage.route(), because requests a worker handles bypass routing. - Test the real worker on a production build: wait for it with
context.waitForEvent('serviceworker')orcontext.serviceWorkers(), and for the page to have a controller, before asserting. - Use
response.fromServiceWorker()to check which responses the worker served, andcontext.setOffline(true)to test the offline page. - Test the transition from one deployed build to the next, and check that it never loops on reloads.
Reference files
The agent reads each file only when its task needs it, as SKILL.md says.
references/notifications.md
Notifications and badges
- Request notification permission only from a click on a control that explains what the user will get. Never ask again after
denied; explain instead how to turn notifications back on in the browser’s settings. - Subscribe with
pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }). - In the
pushhandler, show a notification for every push: awaitregistration.showNotification()insideevent.waitUntil(), because Safari revokes subscriptions whose pushes show nothing. Show notifications from pages withregistration.showNotification()too. - On
notificationclick, close the notification, resolve its target to a same-origin URL, and focus an open window on it or open a new one. - (iOS and iPadOS 16.4+) Push works only for web apps added to the Home Screen. (Safari 18.4+) Declarative Web Push delivers notifications without a service worker.
- Revoke the device’s push subscription on sign-out, and keep sensitive data out of push payloads.
- Badging, with
navigator.setAppBadge()andnavigator.clearAppBadge(), works only for installed apps, so feature-detect it and treat it as optional.
references/offline.md
Offline behavior
- In hand-written workers, navigations go network-first and fall back to the precached offline page only when the network request throws, never on a 4xx or 5xx response, which the app’s own error page handles.
- In hand-written workers, enable navigation preload and use
event.preloadResponsein the navigation handler, so starting the worker doesn’t delay the request. - With jg-sw, the explicitly precached shell is served cache-first. (1.2+)
navigationFallbacknames the precached page used only for a navigation absent from the cache whose network request fails; a 4xx or 5xx passes through. jg-sw has no navigation preload. Keep other HTML out of runtime caching. - Never answer a
Rangerequest with a cached full200response. Keep media out of the cache unless the worker serves206slices. - Store offline data in IndexedDB, and let the client data cache decide what persists, as jg-react describes.
- Call
navigator.storage.persist()only after the user opts in to offline use, check the space left withnavigator.storage.estimate(), and handleQuotaExceededError. Never treat browser storage as the only copy of user data. - Background Sync is Chromium-only, so also retry queued work on the
onlineevent or at the next launch.