skills/pwa/SKILL.md
Build Progressive Web Apps with service workers, web app manifest, caching strategies, and offline support. Use when: making app installable, adding offline mode, configuring service worker, caching API responses, push notifications, background sync, creating web manifest, PWA audit, Lighthouse PWA score, workbox configuration, app shell architecture, precaching, runtime caching, install prompt, or converting SPA to PWA.
npx skillsauth add congiuluc/my-awesome-copilot pwaInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
┌────────────────────────────────────────────┐
│ Browser │
│ ┌──────────────────────────────────────┐ │
│ │ App Shell │ │
│ │ (HTML + CSS + JS — cached locally) │ │
│ └──────────────┬───────────────────────┘ │
│ │ │
│ ┌──────────────▼───────────────────────┐ │
│ │ Service Worker │ │
│ │ ┌───────────┐ ┌─────────────────┐ │ │
│ │ │ Precache │ │ Runtime Cache │ │ │
│ │ │ (static) │ │ (API + dynamic) │ │ │
│ │ └───────────┘ └─────────────────┘ │ │
│ └──────────────┬───────────────────────┘ │
│ │ │
└─────────────────┼──────────────────────────┘
│
┌───────▼───────┐
│ Network │
│ (origin API) │
└───────────────┘
| Requirement | How to Meet |
|-------------|-------------|
| HTTPS | Serve over HTTPS (required for service workers) |
| Web App Manifest | manifest.json linked in <head> with name, icons, display |
| Service Worker | Register SW that controls fetch events |
| Installable | Valid manifest + registered SW + HTTPS |
| Offline Fallback | SW returns cached page when network unavailable |
| Icons | At least 192×192 and 512×512 PNG icons |
| Splash Screen | theme_color + background_color + icon in manifest |
| Viewport Meta | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| Start URL | start_url in manifest resolves when offline |
| Status Bar | theme_color in manifest and <meta name="theme-color"> |
Handle the beforeinstallprompt event to show a custom install UI:
// hooks/useInstallPrompt.ts
import { useState, useEffect, useCallback } from 'react';
interface BeforeInstallPromptEvent extends Event {
prompt(): Promise<void>;
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
}
export function useInstallPrompt() {
const [installPrompt, setInstallPrompt] =
useState<BeforeInstallPromptEvent | null>(null);
const [isInstalled, setIsInstalled] = useState(false);
useEffect(() => {
const handler = (e: Event) => {
e.preventDefault();
setInstallPrompt(e as BeforeInstallPromptEvent);
};
const installedHandler = () => setIsInstalled(true);
window.addEventListener('beforeinstallprompt', handler);
window.addEventListener('appinstalled', installedHandler);
// Check if already installed
if (window.matchMedia('(display-mode: standalone)').matches) {
setIsInstalled(true);
}
return () => {
window.removeEventListener('beforeinstallprompt', handler);
window.removeEventListener('appinstalled', installedHandler);
};
}, []);
const promptInstall = useCallback(async () => {
if (!installPrompt) return false;
await installPrompt.prompt();
const { outcome } = await installPrompt.userChoice;
setInstallPrompt(null);
return outcome === 'accepted';
}, [installPrompt]);
return { canInstall: !!installPrompt, isInstalled, promptInstall };
}
// utils/pushNotifications.ts
export async function subscribeToPush(
vapidPublicKey: string
): Promise<PushSubscription | null> {
if (!('PushManager' in window)) return null;
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
});
return subscription;
}
function urlBase64ToUint8Array(base64String: string): Uint8Array {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = atob(base64);
return Uint8Array.from(rawData, (char) => char.charCodeAt(0));
}
npm install -D vite-plugin-pwa
// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'],
manifest: {
name: 'My App',
short_name: 'App',
description: 'My Progressive Web App',
theme_color: '#ffffff',
background_color: '#ffffff',
display: 'standalone',
scope: '/',
start_url: '/',
icons: [
{ src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable',
},
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\./i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 },
networkTimeoutSeconds: 10,
},
},
],
},
}),
],
});
ng add @angular/pwa
This generates ngsw-config.json, manifest.webmanifest, and registers the SW in app.module.ts. Customize caching groups in ngsw-config.json.
manifest.json present with name, short_name, icons, display, start_url, theme_color<meta name="theme-color"> set in HTML <head><meta name="viewport"> set for responsive layouttools
Build VS Code extensions with TypeScript. Covers extension anatomy, activation events, commands, tree views, webview panels, language features, testing, and publishing. Use when: creating a new VS Code extension, adding commands/views/providers, building webview UIs, implementing language server features, testing extensions, or packaging for the marketplace.
development
Track implementations, features, bugs, and releases in a versioning document. Use when: adding a commit, completing a feature, fixing a bug, or preparing a release. Automatically updates CHANGELOG.md following Keep a Changelog format and Semantic Versioning.
development
Write frontend tests using Vitest and React Testing Library. Use when: testing React components, hooks, user interactions, form submissions, accessibility assertions, or mocking API services.
development
Write Angular frontend tests using Jasmine, Karma, and Angular TestBed. Use when: testing Angular components, services, pipes, directives, user interactions, form submissions, accessibility assertions, or mocking HTTP services.