Skip to content

feat: add PWA support for Android and iOS installation#427

Merged
fatherlinux merged 2 commits into
masterfrom
feature/413-pwa
May 27, 2026
Merged

feat: add PWA support for Android and iOS installation#427
fatherlinux merged 2 commits into
masterfrom
feature/413-pwa

Conversation

@fatherlinux
Copy link
Copy Markdown
Member

Summary

  • Add manifest.webmanifest with app identity, icons, and standalone display mode
  • Add minimal service worker (sw.js) for installability (network-first, shell-only cache)
  • Add PWA meta tags to index.html (manifest link, theme-color, iOS support)
  • Register service worker in main.jsx
  • Scaffold spec 028-pwa with spec and plan docs

No new npm dependencies. Reuses existing 192x192 and 512x512 brand icons. No backend, Vite config, or Containerfile changes needed.

Closes #413
Closes #414

Test plan

  • Visit /manifest.webmanifest — returns valid JSON with application/manifest+json content type
  • Visit /sw.js — returns JavaScript
  • Chrome DevTools → Application → Manifest shows name, icons, colors
  • Chrome DevTools → Application → Service Workers shows sw.js active
  • Android Chrome shows "Add to Home Screen" prompt
  • iOS Safari → Share → "Add to Home Screen" works
  • Installed app launches in standalone mode with ROTV icon

🤖 Generated with Claude Code

fatherlinux and others added 2 commits May 27, 2026 17:50
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add web app manifest, minimal service worker, and PWA meta tags so users
can install ROTV from their browser on both Android and iOS. The installed
app launches in standalone mode with the ROTV icon and forest green theme.

No new dependencies — leverages existing brand icons (192x192, 512x512)
and the Express static-serving pipeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Progressive Web App (PWA) support for Roots of The Valley by adding a web app manifest, a minimal service worker, and iOS-specific meta tags. The review feedback highlights two key improvements: first, returning the cached index.html on network failure in the service worker can cause a blank screen because Vite-hashed assets are not cached, so returning a self-contained offline HTML fallback is recommended instead. Second, registering the service worker on the load event may fail if the script is loaded asynchronously after the event has already fired, which can be resolved by checking document.readyState before registering.

Comment thread frontend/public/sw.js
Comment on lines +25 to +36
self.addEventListener('fetch', function (event) {
if (event.request.method !== 'GET') return;

event.respondWith(
fetch(event.request).catch(function () {
if (event.request.mode === 'navigate') {
return caches.match('/');
}
return caches.match(event.request);
})
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Returning the cached / (index.html) when offline or on network failure will result in a White Screen of Death (blank page) for the user.

Why this happens:

  1. The service worker only caches / (index.html) during installation. It does not cache the compiled JS and CSS assets (which are hashed by Vite, e.g., index-XXXX.js).
  2. When the user is offline and navigates, the service worker intercepts the request and returns the cached index.html.
  3. The browser loads this HTML and immediately attempts to fetch the referenced JS and CSS assets from the network.
  4. Since the user is offline and these assets are not cached, the network requests fail, leaving the user with a completely blank, non-functional page.

Solution:

Instead of returning the cached / which depends on non-cached external assets, return a self-contained, branded offline HTML fallback response directly from the service worker. This satisfies the PWA installability requirement (serving a 200 OK when offline) while providing a clean, user-friendly offline message instead of a broken blank screen.

Suggested change
self.addEventListener('fetch', function (event) {
if (event.request.method !== 'GET') return;
event.respondWith(
fetch(event.request).catch(function () {
if (event.request.mode === 'navigate') {
return caches.match('/');
}
return caches.match(event.request);
})
);
});
self.addEventListener('fetch', function (event) {
if (event.request.method !== 'GET') return;
event.respondWith(
fetch(event.request).catch(function () {
if (event.request.mode === 'navigate') {
return new Response(
"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Offline - Roots of The Valley</title><style>body { font-family: system-ui, sans-serif; text-align: center; padding: 2rem; background: #f5f5f5; color: #333; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 80vh; } h1 { color: #2d5016; margin-bottom: 0.5rem; } p { color: #666; margin-top: 0; }</style></head><body><h1>Roots of The Valley</h1><p>You are currently offline. Please check your connection and try again.</p></body></html>",
{
status: 200,
headers: { "Content-Type": "text/html; charset=utf-8" }
}
);
}
return caches.match(event.request);
})
);
});

Comment thread frontend/src/main.jsx
Comment on lines +15 to +21
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch((err) => {
console.warn('SW registration failed:', err);
});
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the script is loaded asynchronously or deferred (which is common for Vite module scripts), the browser's load event might have already fired by the time this code executes. In such cases, the load event listener will never trigger, and the service worker will not be registered.

To ensure reliable registration, check document.readyState and register immediately if the document is already fully loaded.

Suggested change
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch((err) => {
console.warn('SW registration failed:', err);
});
});
}
if ('serviceWorker' in navigator) {
const registerSW = () => {
navigator.serviceWorker.register('/sw.js').catch((err) => {
console.warn('SW registration failed:', err);
});
};
if (document.readyState === 'complete') {
registerSW();
} else {
window.addEventListener('load', registerSW);
}
}

@fatherlinux fatherlinux merged commit 612ad31 into master May 27, 2026
3 checks passed
@fatherlinux fatherlinux deleted the feature/413-pwa branch May 27, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Create iPhone App [Feature]: Create Android App

1 participant