"Since you last visited" cue + PWA install (add to home screen)
Two calm returning-reader features. Since-last-visit (Highlights companion, not a nav lane — per Codex): * queries.feed gains a `since` filter; GET /api/since?ts= returns the count + a few accepted/non-dup/visible articles discovered since the reader's last visit (boundary-respecting; invalid/future ts → 0, no error). * Home stores last_seen in localStorage (reads prev, then stamps now); on Highlights, a gentle "Since you were last here, N new calm reads came in" note with a "See what's new" reveal of a compact inline section. Dismissible. No badges, no unread counts, no "missed" language. PWA: * Real PNG icons (192/512 + full-bleed maskable) rasterized from favicon.svg; manifest fixed (azure theme to match the brand, PNG icons); apple-touch-icon. * Minimal service worker: precache the app shell, always-fresh API + /a/ pages. * Gentle, dismissible install banner (beforeinstallprompt → Install; iOS → the Share → Add to Home Screen hint). Never nags. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// Calm PWA install state. Captures the browser's install prompt (Android/desktop
|
||||
// Chrome), detects iOS (which has no prompt — needs the Share → Add to Home Screen
|
||||
// hint), and remembers a dismissal so we never nag.
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export const pwa = $state({ canInstall: false, isIOS: false, isStandalone: false, dismissed: false });
|
||||
let deferred = null;
|
||||
|
||||
if (browser) {
|
||||
try {
|
||||
pwa.isStandalone =
|
||||
window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
|
||||
pwa.isIOS = /iphone|ipad|ipod/i.test(window.navigator.userAgent);
|
||||
pwa.dismissed = localStorage.getItem('goodnews:pwa_dismissed') === '1';
|
||||
} catch {
|
||||
/* private mode / unavailable */
|
||||
}
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault();
|
||||
deferred = e;
|
||||
pwa.canInstall = true;
|
||||
});
|
||||
window.addEventListener('appinstalled', () => {
|
||||
pwa.canInstall = false;
|
||||
pwa.isStandalone = true;
|
||||
});
|
||||
}
|
||||
|
||||
export async function installApp() {
|
||||
if (!deferred) return;
|
||||
deferred.prompt();
|
||||
try { await deferred.userChoice; } catch { /* dismissed */ }
|
||||
deferred = null;
|
||||
pwa.canInstall = false;
|
||||
}
|
||||
|
||||
export function dismissPwa() {
|
||||
pwa.dismissed = true;
|
||||
if (browser) {
|
||||
try { localStorage.setItem('goodnews:pwa_dismissed', '1'); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user