Skip to content

feat: enforce PWA install wall on mobile login#116

Merged
ChitkulLakshya merged 8 commits into
mobilefrom
PWA
Apr 24, 2026
Merged

feat: enforce PWA install wall on mobile login#116
ChitkulLakshya merged 8 commits into
mobilefrom
PWA

Conversation

@ChitkulLakshya
Copy link
Copy Markdown
Collaborator

Summary

  • configure ZYNC PWA manifest via vite-plugin-pwa with standalone display mode and production icon paths
  • add installability meta tags for Android Chrome and iOS Safari in index.html
  • register the service worker in app bootstrap and enforce mobile install wall before Login with iOS/Android-specific install guidance

Test plan

  • Run npm run dev and open on desktop: Login remains accessible in browser
  • Open on mobile browser (not installed): install wall appears instead of login
  • On Android, tap Install App and confirm native install prompt appears
  • Launch installed app in standalone mode and verify Login becomes accessible
  • Confirm npm run typecheck (note: existing unrelated ProjectDetails.tsx path error still present)

feat(mobile): mobile UX and accessibility polish
Invalidate cached user and team data after membership changes so team updates propagate across settings and people views.

Made-with: Cursor
Set up vite-plugin-pwa with standalone manifest settings for ZYNC and add the manifest artifact needed for installability.

Made-with: Cursor
Add manifest, theme, and Apple web app head tags so Android and iOS recognize ZYNC as installable.

Made-with: Cursor
Register the PWA service worker via virtual:pwa-register and include plugin client typings for TypeScript support.

Made-with: Cursor
Block mobile browser sessions from reaching Login until ZYNC runs in standalone mode, with platform-specific install guidance for iOS and Android.

Made-with: Cursor
Copilot AI review requested due to automatic review settings April 24, 2026 15:18
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
zync Ready Ready Preview, Comment Apr 24, 2026 3:21pm

Raise workbox.maximumFileSizeToCacheInBytes to 4 MiB so service worker generation succeeds with the current Dashboard bundle size in CI.

Made-with: Cursor
@ChitkulLakshya ChitkulLakshya merged commit ade04b0 into mobile Apr 24, 2026
4 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds PWA installability + a mobile-only “install wall” gating the Login page, and improves team-related cache/query refresh so membership changes propagate quickly.

Changes:

  • Configure vite-plugin-pwa (manifest, Workbox settings) and add PWA meta/manifest tags in index.html.
  • Register the service worker at app bootstrap and introduce an install-wall feature (hook + UI) enforced from Login.
  • Invalidate backend /users/me Redis cache on team membership mutations and add a Jest test for team deletion; refresh relevant React Query caches after team actions.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
vite.config.ts Adds vite-plugin-pwa config (manifest, SW/workbox, dev options).
src/vite-env.d.ts Adds PWA client types for TS.
src/pages/Login.tsx Enforces install wall before showing the login experience on mobile.
src/main.tsx Registers the PWA service worker during app bootstrap.
src/features/install-wall/index.ts Barrel exports for install-wall feature.
src/features/install-wall/hooks/useAppInstallStatus.ts Detects mobile/iOS/Android + standalone mode to decide when to show wall.
src/features/install-wall/components/InstallPromptView.tsx UI + Android beforeinstallprompt handling for installation guidance.
src/components/views/SettingsView.tsx Invalidates team/user queries after team membership mutations.
src/components/views/PeopleView.tsx Forces refetch behavior for team/user lists on mount.
public/manifest.json Adds a static PWA manifest file.
index.html Adds PWA meta tags + manifest/apple-touch-icon links.
backend/routes/teamRoutes.js Invalidates user:me:${uid} cache on team create/join/leave/delete/remove-member; normalizes membership filtering.
backend/tests/teamRoutes.delete.test.js Adds coverage for delete-team behavior + cache invalidation.
package.json Adds vite-plugin-pwa dependency.
package-lock.json Locks new PWA/workbox dependency tree.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pages/Login.tsx
}
};

if (hasCheckedStatus && requiresInstallWall) {
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

On mobile, hasCheckedStatus starts false and is only set true in an effect, so the login UI can render briefly before the install wall replaces it (a visible flash, and it undermines the idea of an “install wall before login”). Consider computing the initial standalone status synchronously (e.g. in the useState initializer) and/or rendering a neutral loading state until the install check completes.

Suggested change
if (hasCheckedStatus && requiresInstallWall) {
if (!hasCheckedStatus) {
return (
<div className="min-h-screen flex items-center justify-center bg-background p-4">
<div className="text-sm text-muted-foreground">Loading...</div>
</div>
);
}
if (requiresInstallWall) {

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +27
const isMobileDevice = MOBILE_UA_REGEX.test(userAgent);
const isIOS = /iPad|iPhone|iPod/i.test(userAgent);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The iOS/iPad detection based solely on navigator.userAgent is unreliable on modern iPadOS (Safari can report a macOS-like UA), which would cause iPads to bypass the install wall. Consider using a more robust check (e.g. navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 for iPadOS, or feature-based detection) so iPads are treated as iOS devices consistently.

Suggested change
const isMobileDevice = MOBILE_UA_REGEX.test(userAgent);
const isIOS = /iPad|iPhone|iPod/i.test(userAgent);
const isIPadOS =
navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
const isMobileDevice = MOBILE_UA_REGEX.test(userAgent) || isIPadOS;
const isIOS = /iPad|iPhone|iPod/i.test(userAgent) || isIPadOS;

Copilot uses AI. Check for mistakes.
},
enabled: !!currentUser && !isPreview,
staleTime: 0,
refetchOnMount: 'always',
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

With staleTime: 0, queries are always stale immediately, and the default refetchOnMount: true already refetches on mount when stale. Adding refetchOnMount: 'always' here is redundant and guarantees a network request even if you later increase staleTime. Consider removing one of these settings (or setting a small staleTime) and relying on targeted invalidateQueries when team membership changes.

Suggested change
refetchOnMount: 'always',

Copilot uses AI. Check for mistakes.
},
enabled: !!teamInfo?.id && !isPreview,
staleTime: 0,
refetchOnMount: 'always',
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

Same as above: staleTime: 0 makes the query stale immediately, so refetchOnMount: 'always' is redundant and forces refetches on every mount. Consider simplifying the query options to avoid unnecessary network traffic.

Suggested change
refetchOnMount: 'always',

Copilot uses AI. Check for mistakes.
Comment thread vite.config.ts
Comment on lines +51 to +78
includeAssets: ["pwa-192x192.png", "pwa-512x512.png", "pwa-maskable-512x512.png"],
manifestFilename: "manifest.json",
manifest: {
name: "ZYNC",
short_name: "ZYNC",
description: "ZYNC collaboration platform",
background_color: "#09090b",
theme_color: "#09090b",
display: "standalone",
start_url: "/",
scope: "/",
icons: [
{
src: "/pwa-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "/pwa-512x512.png",
sizes: "512x512",
type: "image/png",
},
{
src: "/pwa-maskable-512x512.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The PWA config references icon assets (includeAssets + manifest icons) that are not present in public/ (e.g. pwa-192x192.png). This will produce broken icon URLs in the generated manifest and can fail installability checks. Add the referenced icon files under public/ (or update the paths/names to match existing assets) so the manifest and <link rel="apple-touch-icon"> resolve correctly.

Copilot uses AI. Check for mistakes.
Comment thread vite.config.ts
react(),
VitePWA({
registerType: "autoUpdate",
injectRegister: "auto",
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

injectRegister: "auto" will inject service-worker registration into the built HTML, but src/main.tsx also calls registerSW(). That can lead to duplicate SW registrations / duplicated update handling. Choose one approach: either disable injection (injectRegister: null/false) and keep the explicit registerSW, or remove the manual registration and rely on injection.

Suggested change
injectRegister: "auto",
injectRegister: null,

Copilot uses AI. Check for mistakes.
Comment thread public/manifest.json
Comment on lines +1 to +28
{
"name": "ZYNC",
"short_name": "ZYNC",
"description": "ZYNC collaboration platform",
"background_color": "#09090b",
"theme_color": "#09090b",
"display": "standalone",
"start_url": "/",
"scope": "/",
"icons": [
{
"src": "/pwa-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/pwa-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/pwa-maskable-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

manifest.json is defined twice: as a static file in public/manifest.json and also generated by vite-plugin-pwa (manifestFilename: "manifest.json" + manifest). This duplication risks the build output depending on copy/generation order and makes future changes easy to desync. Keep a single source of truth: either remove public/manifest.json and let the plugin generate it, or remove the plugin manifest config and serve the static manifest.

Suggested change
{
"name": "ZYNC",
"short_name": "ZYNC",
"description": "ZYNC collaboration platform",
"background_color": "#09090b",
"theme_color": "#09090b",
"display": "standalone",
"start_url": "/",
"scope": "/",
"icons": [
{
"src": "/pwa-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/pwa-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/pwa-maskable-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

Copilot uses AI. Check for mistakes.
Comment thread src/main.tsx
Comment on lines +5 to 8
import { registerSW } from "virtual:pwa-register";

registerSW({ immediate: true });

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

This explicit registerSW() call likely duplicates the registration injected by vite-plugin-pwa when injectRegister: "auto" is enabled in vite.config.ts, leading to double registration/update handlers. Consider removing this call if you keep injected registration, or disable injection and keep this call (but not both).

Suggested change
import { registerSW } from "virtual:pwa-register";
registerSW({ immediate: true });

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants