feat(pwa): Admin-Tab "App" mit Install + Sync + Cache-Reset
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m20s

Neuer vierter Admin-Tab (Smartphone-Icon) mit drei Karten:
1. Installieren — fängt beforeinstallprompt (Android), zeigt
   iOS-Teilen-Hinweis, sonst Info "nicht verfügbar".
2. Offline-Synchronisation — Status + "Jetzt synchronisieren"-
   Button, disabled wenn offline.
3. Cache — "Offline-Cache leeren" löscht alle kochwas-*-Caches
   via caches.keys() + delete.

install-prompt.svelte.ts hält das deferred-Event und die Plattform
(android/ios/other) per UA-Detection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-18 16:57:49 +02:00
parent 3906781c4e
commit 8bb208a613
4 changed files with 191 additions and 2 deletions

View File

@@ -0,0 +1,44 @@
// Captures the beforeinstallprompt event (Android Chrome) and holds it for
// manual triggering by the user. On iOS Safari this event does not exist —
// we detect the browser via UserAgent and show an info hint instead.
class InstallPromptStore {
available = $state(false);
platform = $state<'android' | 'ios' | 'other'>('other');
private deferred: BeforeInstallPromptEvent | null = null;
init(): void {
if (typeof window === 'undefined') return;
this.platform = detectPlatform();
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
this.deferred = e as BeforeInstallPromptEvent;
this.available = true;
});
window.addEventListener('appinstalled', () => {
this.deferred = null;
this.available = false;
});
}
async prompt(): Promise<void> {
if (!this.deferred) return;
await this.deferred.prompt();
this.deferred = null;
this.available = false;
}
}
function detectPlatform(): 'android' | 'ios' | 'other' {
const ua = navigator.userAgent;
if (/iPhone|iPad|iPod/i.test(ua)) return 'ios';
if (/Android/i.test(ua)) return 'android';
return 'other';
}
// Minimal type for the Chrome-specific event
type BeforeInstallPromptEvent = Event & {
prompt: () => Promise<void>;
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
};
export const installPrompt = new InstallPromptStore();