Alexis315

pwa-review

Comprehensive 185-point PWA audit beyond Lighthouse - analyzes manifest, service worker, offline capabilities, security, iOS compatibility, and advanced PWA features

Alexis315 0 Updated 3mo ago

Resources

5
GitHub

Install

npx skillscat add alexis315/pwa-review-skill

Install via the SkillsCat registry.

SKILL.md

PWA Review Skill

A comprehensive Progressive Web App audit that goes beyond standard Lighthouse testing. This skill analyzes PWAs across 11 categories with a 185-point scoring system, including advanced features and iOS-specific compatibility checks that typical audits miss.

Scoring Overview

Category Points Focus
Manifest Compliance 20 Essential manifest fields
Advanced Manifest 15 Enhanced manifest features + iOS splash
Service Worker & Caching 33 SW implementation quality + caching strategies
Offline Capability 19 Offline functionality + storage + sync triggers
Installability 13 Install requirements
Security 16 Security measures
Performance Signals 17 Performance optimization + network detection
UX & Accessibility 27 User experience + iOS safe areas + mobile dropdowns + themes
SEO & Discoverability 7 Search optimization
PWA Advanced 17 Cutting-edge PWA features
iOS Compatibility 1 iOS-specific meta tags (bonus)

Grading Scale: A+ (90%+), A (80-89%), B (70-79%), C (60-69%), D (40-59%), F (<40%)


Execution Workflow

When the user invokes /pwa-review <url>, follow these steps precisely:

Step 1: Fetch Target HTML

Use WebFetch to retrieve the target URL's HTML content.

WebFetch: {url}
Prompt: "Return the complete HTML source code. I need to analyze the head section for PWA-related tags including manifest link, meta tags, and inline scripts."

Step 2: Extract PWA Resources

From the HTML, identify:

Manifest URL:

  • Look for <link rel="manifest" href="...">
  • Convert relative URLs to absolute using the base URL
  • If not found, note as CRITICAL issue

Service Worker Registration:

  • Search for navigator.serviceWorker.register('...') or navigator.serviceWorker.register("...")
  • Extract the SW file path
  • If not found, note as CRITICAL issue

Meta Tags to Extract:

  • <meta name="theme-color" content="...">
  • <meta name="apple-mobile-web-app-capable" content="...">
  • <meta name="apple-mobile-web-app-status-bar-style" content="...">
  • <meta name="mobile-web-app-capable" content="...">
  • <meta name="viewport" content="..."> (check for viewport-fit=cover)
  • <meta http-equiv="Content-Security-Policy" content="...">
  • <link rel="apple-touch-icon" href="...">
  • <link rel="apple-touch-startup-image" ...> (iOS splash screens)

Step 3: Fetch Manifest

If manifest URL was found, use WebFetch to retrieve it:

WebFetch: {manifest_url}
Prompt: "Return the complete manifest.json content as raw JSON."

If manifest fetch fails (CORS, 404, etc.), score manifest categories as 0 and continue.

Step 4: Fetch Service Worker

If service worker URL was found, use WebFetch to retrieve it:

WebFetch: {sw_url}
Prompt: "Return the complete service worker JavaScript code."

If SW fetch fails, score SW-related categories as 0 and continue.

Step 5: Analyze & Score

Evaluate each category using the detailed checklist below. Track:

  • Passed items (full points)
  • Failed items (0 points) - record as issues
  • Partial items (partial points where applicable)

Step 6: Generate Report

Output a markdown report following the template at the end of this document.


Detailed Scoring Checklist

Category 1: Manifest Compliance (20 points)

Check Points How to Verify
name field present and non-empty 2 manifest.name exists and length > 0
short_name present (≤12 chars recommended) 2 manifest.short_name exists
icons array with 192x192 PNG 4 icons array has item with sizes="192x192"
icons array with 512x512 PNG 4 icons array has item with sizes="512x512"
start_url defined 2 manifest.start_url exists
display mode set (standalone/fullscreen/minimal-ui) 2 manifest.display is one of allowed values
background_color specified 2 manifest.background_color exists (hex/rgb/named)
theme_color specified 2 manifest.theme_color exists

Critical Blocker: If manifest is missing entirely, this category scores 0/20.

Category 2: Advanced Manifest Features (15 points)

Check Points How to Verify
description field present 1 manifest.description exists
screenshots array for install UI 2 manifest.screenshots array with ≥1 item
shortcuts array for quick actions 2 manifest.shortcuts array with ≥1 item
categories array defined 1 manifest.categories exists
orientation preference set 1 manifest.orientation exists
dir and lang for i18n 1 manifest.dir OR manifest.lang exists
id field for app identity 1 manifest.id exists
scope properly defined 1 manifest.scope exists
Maskable icon present 1 icons array has item with purpose="maskable" or "any maskable"
note_taking object 1 manifest.note_taking exists (ChromeOS lock screen notes)
widgets array 1 manifest.widgets exists (Windows 11 Widgets Board)
iOS splash screens present 2 <link rel="apple-touch-startup-image"> tags for multiple device sizes

iOS Splash Screen Note: iOS requires separate <link rel="apple-touch-startup-image"> tags for each device size. Without these, iOS shows a blank white screen during PWA launch. Check for multiple media queries covering different device dimensions.

Category 3: Service Worker & Caching (33 points)

Check Points How to Verify
SW registered in HTML 2 navigator.serviceWorker.register() found
install event handler present 3 SW contains addEventListener('install', ...) or self.oninstall
activate event handler present 3 SW contains addEventListener('activate', ...) or self.onactivate
fetch event handler present 4 SW contains addEventListener('fetch', ...) or self.onfetch
Cache API usage (caches.open/put/match) 3 SW contains caches.open or cache.put or cache.match
Cache versioning/naming strategy 2 SW has cache name variable (CACHE_NAME, CACHE_VERSION, etc.)
Old cache cleanup in activate 2 activate handler deletes old caches
Background Sync support 2 SW contains addEventListener('sync', ...) or addEventListener('periodicsync', ...)
Workbox usage (bonus, not required) 1 SW imports workbox or uses workbox.* methods
skipWaiting() usage 1 SW contains self.skipWaiting() for instant activation
clients.claim() usage 1 SW contains clients.claim() for immediate control
Navigation preload 1 SW uses navigationPreload.enable()
Stale-while-revalidate pattern 1 fetch handler serves cache then updates in background
Push event handler 1 SW contains addEventListener('push', ...)
notificationclick handler 1 SW contains addEventListener('notificationclick', ...)
Notification action buttons 1 Push notifications include actions array OR notificationclick checks event.action
Multiple caching strategies 2 SW uses different strategies for different routes (CacheFirst, NetworkFirst, StaleWhileRevalidate)
Cache expiration config 1 SW has maxEntries or maxAgeSeconds for cache pruning
SW message handler 1 SW contains addEventListener('message', ...) for client communication

Critical Blocker: If no service worker, this category scores 0/33.

Caching Strategies Note: Production-grade PWAs should use different caching strategies based on resource type:

  • CacheFirst: Static assets, fonts, images (rarely change)
  • NetworkFirst: API responses, dynamic content (freshness matters)
  • StaleWhileRevalidate: JS/CSS bundles (speed + freshness balance)
    Look for patterns like new CacheFirst(), new NetworkFirst(), or explicit strategy patterns in fetch handlers.

Cache Expiration Note: Without expiration limits, caches grow unbounded and can exceed storage quotas. Look for ExpirationPlugin with maxEntries or maxAgeSeconds, or custom cleanup logic in the fetch handler.

Category 4: Offline Capability (19 points)

Check Points How to Verify
Offline fallback page defined 3 SW caches and serves an offline.html or similar
App shell resources precached 3 install event caches core HTML/CSS/JS files
Offline indicator in UI (code pattern) 2 Code checks navigator.onLine or listens to online/offline events
Network-first or cache-first strategy evident 2 fetch handler has clear strategy pattern
Update prompt shown to user 1 Code handles SW update with user notification (e.g., "New version available")
Graceful update flow 1 Update doesn't force reload without warning, user can choose when to update
Update state persistence 1 localStorage flag prevents update prompt re-appearing after update (e.g., pwa-just-updated)
Touch event double-fire prevention 1 Update/action handlers prevent duplicate execution from onClick + onTouchEnd
Persistent storage request 1 Code uses navigator.storage.persist() to prevent iOS data eviction
IndexedDB offline storage 1 Code uses indexedDB.open() or idb library for structured offline data
Storage quota monitoring 1 Code uses navigator.storage.estimate() for storage health checks
Background sync client trigger 1 Client triggers registration.sync.register() when coming back online
Periodic sync registration 1 Client registers registration.periodicSync.register() on app init

Update UX Note: Good PWAs notify users when updates are available and let them choose when to apply the update. Look for patterns like useRegisterSW, workbox-window, or custom SW update handling with user-facing notifications.

Background Sync Client Trigger Note: Having a sync event handler in the service worker is not enough. The client must explicitly trigger background sync when coming back online by calling navigator.serviceWorker.ready.then(reg => reg.sync.register('sync-pending-requests')) in the online event listener. Without this, offline requests remain queued indefinitely.

Periodic Sync Registration Note: The service worker periodicsync event handler must be complemented by client-side registration during app initialization. Look for registration.periodicSync.register('sync-content', { minInterval: ... }) wrapped in a permission check (navigator.permissions.query({ name: 'periodic-background-sync' })). This enables automatic background content updates even when the app is closed.

Update State Note: After a user clicks "Update", the PWA reloads. Without state management, the update prompt may immediately re-appear because the new service worker is still "waiting". Use localStorage flags (e.g., pwa-just-updated with timestamp) to suppress the prompt for a brief period (30 seconds) after update completion. Also implement double-fire prevention for touch handlers - on iOS, both onClick and onTouchEnd may fire, causing duplicate updates.

Offline Storage Note: For complex PWAs with user-generated content, localStorage alone is insufficient. Use IndexedDB for structured data storage (images, generation history, preferences). Request persistent storage with navigator.storage.persist() to prevent iOS from evicting data after 7 days of inactivity. Monitor storage quota with navigator.storage.estimate() to warn users before running out of space.

Category 5: Installability Requirements (13 points)

Check Points How to Verify
Served over HTTPS 3 URL starts with https://
Valid manifest linked in HTML 2 exists with valid href
Service worker with fetch handler 2 Covered in SW category, cross-check
192x192 icon present 1 Covered in manifest, cross-check
512x512 icon present 1 Covered in manifest, cross-check
apple-touch-icon for iOS 1 in HTML
beforeinstallprompt handled 2 HTML/JS contains beforeinstallprompt event listener
Custom install UI 1 Code shows/hides custom install button

Note: prefer_related_applications: true in manifest BLOCKS browser install prompt - flag as CRITICAL if found.

Category 6: Security Measures (16 points)

Check Points How to Verify
HTTPS enforced 2 URL is https:// (duplicate check for emphasis)
Content Security Policy present 3 CSP meta tag or mention in SW/HTML
Subresource Integrity (SRI) on scripts 2 <script> tags have integrity="sha..."</td> </tr> <tr> <td>No mixed content</td> <td>2</td> <td>No http:// resources loaded on https:// page</td> </tr> <tr> <td>scope restricted appropriately</td> <td>1</td> <td>manifest.scope doesn't expose unnecessary paths</td> </tr> <tr> <td>Cross-Origin Isolation (COOP/COEP)</td> <td>2</td> <td>Headers: Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy</td> </tr> <tr> <td>HSTS header</td> <td>1</td> <td>Strict-Transport-Security header (note: not detectable from HTML)</td> </tr> <tr> <td>X-Content-Type-Options</td> <td>1</td> <td>nosniff header present (note: not detectable from HTML)</td> </tr> <tr> <td>Referrer-Policy</td> <td>1</td> <td>Appropriate referrer policy set via meta or header</td> </tr> <tr> <td>Permissions-Policy</td> <td>1</td> <td>Feature policy defined (note: not detectable from HTML)</td> </tr> </tbody></table> <p><strong>Note:</strong> Some security headers (HSTS, X-Content-Type-Options, Permissions-Policy) cannot be verified from HTML alone. Mark as "Unable to verify" unless response headers are available.</p> <h3>Category 7: Performance Signals (17 points)</h3> <table> <thead> <tr> <th>Check</th> <th>Points</th> <th>How to Verify</th> </tr> </thead> <tbody><tr> <td>No render-blocking scripts in head</td> <td>2</td> <td>Scripts have defer/async or are at body end</td> </tr> <tr> <td>Images have lazy loading</td> <td>2</td> <td><img loading="lazy"> or Intersection Observer usage</td> </tr> <tr> <td>Resource hints present</td> <td>2</td> <td><link rel="preload/prefetch/preconnect"> found</td> </tr> <tr> <td>Code splitting indicators</td> <td>2</td> <td>Multiple JS chunks or dynamic import() usage</td> </tr> <tr> <td>Font optimization</td> <td>2</td> <td>font-display: swap or preloaded fonts</td> </tr> <tr> <td>LCP optimization signals</td> <td>1</td> <td>Hero image preloaded, above-fold content prioritized</td> </tr> <tr> <td>INP optimization signals</td> <td>1</td> <td>No long tasks, event handlers optimized (qualitative)</td> </tr> <tr> <td>CLS prevention</td> <td>1</td> <td>Images have width/height, no layout shifts expected</td> </tr> <tr> <td>Critical CSS inlined</td> <td>1</td> <td>Critical styles in <head> or preloaded</td> </tr> <tr> <td>Compression headers</td> <td>1</td> <td>Server returns <code>Content-Encoding: gzip</code> or <code>br</code> (note: verify via DevTools)</td> </tr> <tr> <td>Bundle chunking strategy</td> <td>1</td> <td>Build uses <code>manualChunks</code>, vendor splitting, or separate runtime chunks</td> </tr> <tr> <td>Network Information API usage</td> <td>1</td> <td>Code uses <code>navigator.connection</code> for adaptive behavior on slow networks</td> </tr> </tbody></table> <p><strong>Compression Note:</strong> Gzip/Brotli compression can reduce bundle sizes by 60-80%. This cannot be verified from HTML alone - check Network tab in DevTools for <code>Content-Encoding</code> response header. Build tools like <code>vite-plugin-compression</code> can generate pre-compressed <code>.gz</code> and <code>.br</code> files.</p> <p><strong>Bundle Chunking Note:</strong> Good build configurations split vendor dependencies (React, UI libraries, i18n) into separate chunks. Look for patterns like <code>manualChunks</code> in Vite/Rollup config or webpack's <code>splitChunks</code>. This enables better caching (vendor chunks change less frequently) and parallel loading.</p> <p><strong>Network Information API Note:</strong> The Network Information API (<code>navigator.connection</code>) enables adaptive behavior based on connection quality. Look for patterns that check <code>connection.effectiveType</code> (4g/3g/2g/slow-2g), <code>connection.saveData</code>, or <code>connection.downlink</code>. PWAs can reduce image quality, disable prefetching, or extend API timeouts on slow connections. Example:</p> <pre><code class="language-javascript" data-language="javascript">const conn = navigator.connection; if (conn?.effectiveType === &#39;2g&#39; || conn?.saveData) { // Load low-quality images, disable autoplay, extend timeouts }</code></pre><h3>Category 8: UX & Accessibility (27 points)</h3> <table> <thead> <tr> <th>Check</th> <th>Points</th> <th>How to Verify</th> </tr> </thead> <tbody><tr> <td>Responsive viewport meta</td> <td>2</td> <td><meta name="viewport" content="width=device-width, initial-scale=1"></td> </tr> <tr> <td><code>viewport-fit=cover</code> for safe areas</td> <td>2</td> <td>Viewport meta includes <code>viewport-fit=cover</code> (required for iOS notch/Dynamic Island)</td> </tr> <tr> <td>Safe area CSS usage</td> <td>2</td> <td>Code uses <code>env(safe-area-inset-*)</code> for fixed/sticky elements</td> </tr> <tr> <td>Semantic HTML structure</td> <td>2</td> <td><main>, <nav>, <header>, <footer> tags present</td> </tr> <tr> <td>ARIA landmarks or roles</td> <td>2</td> <td>role="..." or aria-* attributes found</td> </tr> <tr> <td>Language declared</td> <td>2</td> <td><html lang="..."> attribute present</td> </tr> <tr> <td>Touch-friendly targets</td> <td>2</td> <td>No evidence of tiny click targets (qualitative)</td> </tr> <tr> <td>Touch event handling for iOS</td> <td>1</td> <td>Critical buttons have <code>onTouchEnd</code> handlers or <code>touch-manipulation</code> CSS</td> </tr> <tr> <td>Focus indicators visible</td> <td>1</td> <td>:focus styles not removed, visible outlines (qualitative)</td> </tr> <tr> <td>Skip to main content link</td> <td>1</td> <td>Skip link present for keyboard navigation</td> </tr> <tr> <td>Mobile dropdown positioning</td> <td>2</td> <td>Dropdowns use <code>fixed</code> on mobile, <code>absolute</code> on desktop with proper margins</td> </tr> <tr> <td>Dropdown safe area handling</td> <td>1</td> <td>Dropdowns apply <code>safe-area-inset-right/left</code> for notch devices</td> </tr> <tr> <td>Theme consistency (light/dark)</td> <td>2</td> <td>All UI elements have both light and <code>dark:</code> variants in Tailwind/CSS</td> </tr> <tr> <td>Dark overlay theme pairs</td> <td>1</td> <td><code>bg-black/X</code> patterns have <code>dark:</code> prefix (e.g., <code>bg-white/60 dark:bg-black/60</code>)</td> </tr> <tr> <td>Border visibility pairs</td> <td>1</td> <td><code>border-white/X</code> has light alternative (e.g., <code>border-zinc-200 dark:border-white/10</code>)</td> </tr> <tr> <td>Hover state theme pairs</td> <td>1</td> <td>Hover backgrounds have both variants (e.g., <code>hover:bg-zinc-100 dark:hover:bg-white/10</code>)</td> </tr> <tr> <td>Gradient theme support</td> <td>1</td> <td>Gradient stops have variants (e.g., <code>from-white/80 dark:from-black/80</code>)</td> </tr> <tr> <td>Contextual text-white</td> <td>1</td> <td>White text only on colored backgrounds, not transparent overlays</td> </tr> </tbody></table> <p><strong>iOS Safe Area Note:</strong> iPhone notch and Dynamic Island require special handling. Without <code>viewport-fit=cover</code> and <code>env(safe-area-inset-*)</code> CSS, content may be obscured or buttons may be unreachable in PWA standalone mode. Fixed headers should use <code>padding-top: env(safe-area-inset-top)</code> and bottom navigation should account for <code>safe-area-inset-bottom</code>.</p> <p><strong>Touch Event Note:</strong> On iOS, <code>onClick</code> handlers may not fire reliably in PWA mode. Critical action buttons (update, install, submit) should include <code>onTouchEnd</code> handlers as backup. The CSS property <code>touch-manipulation</code> prevents double-tap zoom delays.</p> <p><strong>Mobile Dropdown Positioning Note:</strong> Dropdowns positioned with <code>absolute</code> relative to a small parent element (like a button) often extend beyond the viewport on mobile. Solution: Use <code>fixed</code> positioning on mobile to break out of the parent's positioning context, then use <code>left-4 right-4</code> (or similar) for consistent margins instead of transform centering (<code>left-1/2 -translate-x-1/2</code>). On desktop (<code>sm:</code> breakpoint), revert to <code>absolute</code> with <code>right-0</code> for proper alignment. Always apply <code>safe-area-inset-right</code> via inline style for notch devices.</p> <p><strong>Theme Consistency Note:</strong> In Tailwind CSS projects, all UI elements should have both light and dark variants. Look for patterns like <code>bg-zinc-100 dark:bg-zinc-900</code>. Hardcoded colors without a <code>dark:</code> counterpart (e.g., <code>bg-zinc-900</code> alone) will appear incorrectly in light mode. Common problem areas: tooltips, buttons, borders, and dropdown backgrounds.</p> <p><strong>Extended Theme Checks Note:</strong> Alpha/opacity-based colors (<code>bg-black/60</code>, <code>border-white/10</code>, <code>from-black/80</code>) are commonly used for dark mode but invisible or wrong in light mode. Each pattern needs a light mode counterpart:</p> <ul> <li><code>bg-black/60</code> → <code>bg-white/60 dark:bg-black/60</code></li> <li><code>border-white/10</code> → <code>border-zinc-200 dark:border-white/10</code></li> <li><code>hover:bg-black/10</code> → <code>hover:bg-zinc-100 dark:hover:bg-white/10</code></li> <li><code>from-black/80</code> → <code>from-white/80 dark:from-black/80</code></li> <li><code>text-white</code> on overlays → <code>text-zinc-900 dark:text-white</code><br>These patterns are frequently missed because they work in dark mode (the default design) but break in light mode.</li> </ul> <h3>Category 9: SEO & Discoverability (7 points)</h3> <table> <thead> <tr> <th>Check</th> <th>Points</th> <th>How to Verify</th> </tr> </thead> <tbody><tr> <td><code>&lt;title&gt;</code> tag present</td> <td>1</td> <td>HTML has <title> with content</td> </tr> <tr> <td>Meta description</td> <td>2</td> <td><meta name="description" content="..."></td> </tr> <tr> <td>Open Graph tags</td> <td>2</td> <td>og:title, og:description, og:image present</td> </tr> <tr> <td>Canonical URL</td> <td>1</td> <td><link rel="canonical" href="..."></td> </tr> <tr> <td>Structured data (JSON-LD)</td> <td>1</td> <td><script type="application/ld+json"> found</td> </tr> </tbody></table> <h3>Category 10: PWA Advanced Capabilities (17 points)</h3> <table> <thead> <tr> <th>Check</th> <th>Points</th> <th>How to Verify</th> </tr> </thead> <tbody><tr> <td><code>handle_links</code> preference</td> <td>2</td> <td>manifest.handle_links exists (preferred/auto/not-preferred)</td> </tr> <tr> <td><code>launch_handler</code> defined</td> <td>2</td> <td>manifest.launch_handler object exists</td> </tr> <tr> <td><code>file_handlers</code> array</td> <td>2</td> <td>manifest.file_handlers with accept types</td> </tr> <tr> <td><code>protocol_handlers</code> array</td> <td>2</td> <td>manifest.protocol_handlers for custom protocols</td> </tr> <tr> <td><code>share_target</code> defined</td> <td>2</td> <td>manifest.share_target object exists</td> </tr> <tr> <td><code>display_override</code> array</td> <td>1</td> <td>manifest.display_override for fallback displays</td> </tr> <tr> <td><code>edge_side_panel</code> for Edge</td> <td>1</td> <td>manifest.edge_side_panel object exists</td> </tr> <tr> <td><code>scope_extensions</code></td> <td>1</td> <td>manifest.scope_extensions array exists</td> </tr> <tr> <td><code>related_applications</code> (informational)</td> <td>1</td> <td>manifest.related_applications exists</td> </tr> <tr> <td><code>prefer_related_applications</code> is false/absent</td> <td>1</td> <td>Value is false or field is missing (true = CRITICAL issue)</td> </tr> <tr> <td>Web Push configured</td> <td>1</td> <td>VAPID or gcm_sender_id in manifest</td> </tr> <tr> <td>Notification permission UX</td> <td>1</td> <td>Permission requested after user action, not on load</td> </tr> </tbody></table> <h3>Category 11: iOS Compatibility Bonus (1 point)</h3> <table> <thead> <tr> <th>Check</th> <th>Points</th> <th>How to Verify</th> </tr> </thead> <tbody><tr> <td>Complete iOS meta tag set</td> <td>1</td> <td>Has <code>apple-mobile-web-app-capable</code>, <code>apple-mobile-web-app-status-bar-style</code>, AND <code>mobile-web-app-capable</code></td> </tr> </tbody></table> <p><strong>Note:</strong> This is a bonus point for PWAs that have complete iOS compatibility meta tags. The individual checks are scored in their respective categories, but having the complete set demonstrates attention to cross-platform compatibility.</p> <hr> <h2>Issue Classification</h2> <h3>Critical Issues (Must Fix)</h3> <ul> <li>Missing manifest file</li> <li>Missing service worker</li> <li>No fetch event handler in SW</li> <li><code>prefer_related_applications: true</code> (blocks install)</li> <li>Not served over HTTPS</li> <li>Missing required icons (192x192, 512x512)</li> </ul> <h3>Warnings (Should Fix)</h3> <ul> <li>Missing theme_color/background_color</li> <li>No offline fallback page</li> <li>No CSP header/meta</li> <li>Missing apple-touch-icon</li> <li>No cache versioning strategy</li> <li>Missing meta description</li> <li>No skipWaiting/clients.claim</li> <li>No beforeinstallprompt handling</li> <li>Missing <code>viewport-fit=cover</code> (iOS safe areas won't work)</li> <li>No <code>env(safe-area-inset-*)</code> usage for fixed elements</li> <li>Missing iOS splash screens</li> <li>No update notification UX for users</li> <li>No update state persistence (prompt may re-appear after update)</li> <li>Dropdowns use absolute positioning without mobile viewport handling</li> <li>Hardcoded colors without light/dark theme variants</li> <li><code>bg-black/X</code> patterns without <code>dark:</code> prefix (invisible in light mode)</li> <li><code>border-white/X</code> patterns without light alternative (invisible in light mode)</li> <li><code>hover:bg-black/X</code> or <code>hover:bg-white/X</code> without theme pair</li> <li>Gradient stops without theme variants</li> <li><code>text-white</code> on transparent/overlay backgrounds (unreadable in light mode)</li> </ul> <h3>Informational (Nice to Have)</h3> <ul> <li>Missing advanced manifest features</li> <li>No PWA advanced capabilities</li> <li>No structured data</li> <li>Missing shortcuts</li> <li>No navigation preload</li> <li>No stale-while-revalidate</li> <li>No persistent storage request (iOS data may be evicted)</li> <li>No IndexedDB usage (limited to localStorage)</li> <li>No storage quota monitoring</li> <li>No compression headers detected</li> <li>No bundle chunking strategy evident</li> <li>No background sync client trigger (offline requests never sync)</li> <li>No periodic sync registration (no background updates)</li> <li>No Network Information API usage (no adaptive behavior on slow networks)</li> <li>Single caching strategy for all resources (not optimized)</li> <li>No cache expiration config (unbounded cache growth)</li> </ul> <hr> <h2>Report Template</h2> <p>Generate the report in this exact format:</p> <pre><code class="language-markdown" data-language="markdown"># PWA Audit Report **URL:** [analyzed URL] **Date:** [current date] **Overall Score:** [X]/185 ([percentage]%) — Grade: [letter grade] --- ## Score Breakdown | Category | Score | Status | |----------|-------|--------| | Manifest Compliance | X/20 | [status emoji] | | Advanced Manifest | X/15 | [status emoji] | | Service Worker &amp; Caching | X/33 | [status emoji] | | Offline Capability | X/19 | [status emoji] | | Installability | X/13 | [status emoji] | | Security | X/16 | [status emoji] | | Performance Signals | X/17 | [status emoji] | | UX &amp; Accessibility | X/27 | [status emoji] | | SEO &amp; Discoverability | X/7 | [status emoji] | | PWA Advanced | X/17 | [status emoji] | | iOS Compatibility | X/1 | [status emoji] | Status: Pass (80%+), Warn (50-79%), Fail (&lt;50%) --- ## Critical Issues [List any critical blockers that prevent PWA functionality] --- ## Warnings [List important issues that should be addressed] --- ## Passed Checks [Summarize what the PWA does well] --- ## Recommendations ### High Priority 1. [Most impactful fix] 2. [Second priority] ### Medium Priority 1. [Improvement] 2. [Enhancement] ### Quick Wins - [Easy fix 1] - [Easy fix 2] --- ## Resources - [Web App Manifest | web.dev](https://web.dev/add-manifest/) - [Service Workers | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) - [PWA Checklist | web.dev](https://web.dev/pwa-checklist/) - [Workbox | Google](https://developer.chrome.com/docs/workbox/) --- *Generated by PWA Review Skill v5.4.0*</code></pre><hr> <h2>Error Handling</h2> <h3>Manifest Not Found</h3> <ul> <li>Score Category 1 (Manifest Compliance) as 0/20</li> <li>Score Category 2 (Advanced Manifest) as 0/13</li> <li>Add CRITICAL issue: "No manifest.json found"</li> <li>Continue with remaining categories</li> </ul> <h3>Service Worker Not Found</h3> <ul> <li>Score Category 3 (Service Worker & Caching) as 0/33</li> <li>Score Category 4 (Offline Capability) as 0/19</li> <li>Reduce Category 5 (Installability) by 2 points</li> <li>Add CRITICAL issue: "No service worker registered"</li> <li>Continue with remaining categories</li> </ul> <h3>CORS/Fetch Failures</h3> <ul> <li>Note which resource couldn't be fetched</li> <li>Score affected categories as 0</li> <li>Add WARNING: "Could not fetch [resource] - CORS or access issue"</li> <li>Analyze whatever resources were successfully retrieved</li> </ul> <h3>Invalid JSON (Manifest)</h3> <ul> <li>Score manifest categories as 0</li> <li>Add CRITICAL issue: "manifest.json contains invalid JSON"</li> <li>Continue with HTML and SW analysis</li> </ul> <hr> <h2>iOS/Safari Limitations to Note</h2> <p>When generating the report, include these platform-specific notes if relevant:</p> <h3>Installation & Capabilities</h3> <ul> <li>iOS Safari: <code>beforeinstallprompt</code> event not supported (users must manually "Add to Home Screen")</li> <li>iOS Safari: Push notifications require iOS 16.4+ and explicit user permission</li> <li>iOS Safari: Storage limited to ~50MB (may be evicted under storage pressure)</li> <li>iOS Safari: No persistent storage API</li> <li>Safari: Service worker scope limitations more strict</li> </ul> <h3>Safe Area & Display (Critical for PWA Mode)</h3> <ul> <li><strong>Notch/Dynamic Island</strong>: Without <code>viewport-fit=cover</code> in viewport meta, <code>env(safe-area-inset-*)</code> won't work</li> <li><strong>Fixed Headers</strong>: Must use <code>padding-top: env(safe-area-inset-top)</code> to avoid content being hidden behind notch</li> <li><strong>Fixed Bottom Elements</strong>: Must use <code>padding-bottom: env(safe-area-inset-bottom)</code> for home indicator area</li> <li><strong>Status Bar</strong>: <code>apple-mobile-web-app-status-bar-style</code> can be <code>default</code>, <code>black</code>, or <code>black-translucent</code></li> <li>PWA mode on iOS shows no browser chrome - safe area handling is essential</li> </ul> <h3>Touch Events & Interactions</h3> <ul> <li><code>onClick</code> handlers may not fire reliably on some iOS versions in PWA mode</li> <li>Add <code>onTouchEnd</code> as backup for critical buttons (install, update, submit actions)</li> <li>Use <code>touch-manipulation</code> CSS to eliminate 300ms tap delay and prevent double-tap zoom</li> <li>Use <code>cursor: pointer</code> CSS on interactive elements - iOS Safari requires this to recognize elements as clickable</li> <li>Use <code>-webkit-tap-highlight-color: transparent</code> for clean visual feedback</li> <li>Use <code>-webkit-user-select: none</code> on interactive elements to prevent text selection</li> </ul> <h3>Splash Screens</h3> <ul> <li>iOS requires <code>&lt;link rel=&quot;apple-touch-startup-image&quot;&gt;</code> with media queries for each device size</li> <li>Without splash screens, iOS shows blank white screen during PWA launch</li> <li>Each iPhone/iPad dimension needs its own splash image (portrait and landscape)</li> </ul> <h3>Z-Index & Stacking Context (Critical)</h3> <ul> <li><strong>backdrop-filter creates new stacking context</strong>: Headers with <code>backdrop-blur</code> or <code>backdrop-filter</code> create isolated stacking contexts in iOS Safari. Elements with higher z-index values may still appear BEHIND these elements.</li> <li><strong>Fix</strong>: Add <code>transform: translate3d(0,0,0)</code> to elements that need to appear above backdrop-filter elements. This forces GPU layer rendering and fixes stacking order.</li> <li>Toast/notification components must have high z-index (e.g., <code>z-[9999]</code>) AND <code>transform: translate3d(0,0,0)</code> to appear above blurred headers</li> <li>iOS Safari has stricter stacking context behavior than Chrome/Firefox</li> </ul> <p><strong>Example fix for notifications above blurred headers:</strong></p> <pre><code class="language-css" data-language="css">.notification { position: fixed; z-index: 9999; transform: translate3d(0,0,0); /* Forces GPU layer, fixes iOS stacking */ }</code></pre><hr> <h2>Example Usage</h2> <p>User: <code>/pwa-review https://looknex.com</code></p> <p>Claude will:</p> <ol> <li>Fetch <a href="https://looknex.com" target="_blank" rel="noopener noreferrer nofollow">https://looknex.com</a> HTML</li> <li>Find manifest at /manifest.json</li> <li>Find SW at /sw.js</li> <li>Fetch and analyze both files</li> <li>Score across all 10 categories</li> <li>Generate detailed report with findings</li> </ol> </pwa-review>