Summary
When a user visits the SPA on Android, the browser should prompt to install it as a Progressive Web App. After install, the app loads from a service-worker-cached shell — near-instant on subsequent visits, and the read paths keep working when the network is flaky.
Why this matters
For mobile-heavy admin users (oncall, ops staff), the cold-start cost of "open browser → type URL → wait for cookie + bundle" is the difference between "I'll do that later" and "I'll do that now." A 200-KB cached shell + a service worker collapses repeat-visit latency to near-zero. The HTML admin doesn't and can't do this.
What I expect as a consumer
- A manifest served at
<mount>/web.manifest (one Django view; start_url / scope / name resolved from the consumer-chosen mount + AdminSite.site_header at request time). Theme colour resolved server-side from Sec-CH-Prefers-Color-Scheme (pair with the dark-mode issue #84).
- A hand-rolled service worker at
<mount>/sw.js with Service-Worker-Allowed: <mount> header. No vite-plugin-pwa / Workbox — the SW JS stays auditable and the dependency surface stays small.
- Cache policy:
- SPA shell (
index.html, JS, CSS) → stale-while-revalidate, versioned cache.
- Static icons / fonts → cache-first, immutable.
/api/v1/registry/, /api/v1/<app>/<model>/, /api/v1/<app>/<model>/<pk>/ → network-first, last-good cache.
- Action invocations / writes → no caching, no replay.
- Anything outside the SPA mount → pass through.
- Server
Cache-Control: no-store responses → skip caching (honour the existing security middleware).
- Install prompt UX: browser's
beforeinstallprompt event captured; SPA renders an "Install this admin" affordance in the user menu (with the Lucide Download icon). User clicks → SPA calls prompt(). Native dialog renders. 14-day cooldown on dismiss. iOS Safari (no API) gets a one-line "Add to Home Screen" tip in the user menu.
- Offline behaviour: read paths keep working from the SW cache; write paths show a sticky banner ("You're offline. Changes will save when you reconnect."); mutations queue and flush on
online.
- Cache purge on logout (Security ask): the logout flow runs
caches.delete(...) for the SPA caches so cached API payloads don't outlive the session.
Out of scope for a first cut
- Background sync for writes (post-v1).
- Push notifications.
- Periodic background sync (Chrome's "Periodic Background Sync API").
- Wear OS / tablet-only variants.
Acceptance signal
A staff user opens the SPA on Android Chrome, gets the install prompt, accepts; the installed app launches in standalone mode, first paint < 200 ms on the second cold start, reads work for ≥5 minutes after killing wifi, mutations queue and flush on reconnect.
Workaround today
None — there is no manifest, no service worker, no install affordance.
CSP additions needed
worker-src 'self' must be added to the package's recommended CSP sample. Security lane will sign off on this when the issue is picked up.
— PM/UX-owned issue, opened from the lane that ran the 2026-05-26 directive cycle.
Summary
When a user visits the SPA on Android, the browser should prompt to install it as a Progressive Web App. After install, the app loads from a service-worker-cached shell — near-instant on subsequent visits, and the read paths keep working when the network is flaky.
Why this matters
For mobile-heavy admin users (oncall, ops staff), the cold-start cost of "open browser → type URL → wait for cookie + bundle" is the difference between "I'll do that later" and "I'll do that now." A 200-KB cached shell + a service worker collapses repeat-visit latency to near-zero. The HTML admin doesn't and can't do this.
What I expect as a consumer
<mount>/web.manifest(one Django view;start_url/scope/nameresolved from the consumer-chosen mount +AdminSite.site_headerat request time). Theme colour resolved server-side fromSec-CH-Prefers-Color-Scheme(pair with the dark-mode issue #84).<mount>/sw.jswithService-Worker-Allowed: <mount>header. Novite-plugin-pwa/ Workbox — the SW JS stays auditable and the dependency surface stays small.index.html, JS, CSS) → stale-while-revalidate, versioned cache./api/v1/registry/,/api/v1/<app>/<model>/,/api/v1/<app>/<model>/<pk>/→ network-first, last-good cache.Cache-Control: no-storeresponses → skip caching (honour the existing security middleware).beforeinstallpromptevent captured; SPA renders an "Install this admin" affordance in the user menu (with the LucideDownloadicon). User clicks → SPA callsprompt(). Native dialog renders. 14-day cooldown on dismiss. iOS Safari (no API) gets a one-line "Add to Home Screen" tip in the user menu.online.caches.delete(...)for the SPA caches so cached API payloads don't outlive the session.Out of scope for a first cut
Acceptance signal
A staff user opens the SPA on Android Chrome, gets the install prompt, accepts; the installed app launches in standalone mode, first paint < 200 ms on the second cold start, reads work for ≥5 minutes after killing wifi, mutations queue and flush on reconnect.
Workaround today
None — there is no manifest, no service worker, no install affordance.
CSP additions needed
worker-src 'self'must be added to the package's recommended CSP sample. Security lane will sign off on this when the issue is picked up.— PM/UX-owned issue, opened from the lane that ran the 2026-05-26 directive cycle.