diff --git a/LOTUS_TODO.md b/LOTUS_TODO.md index 91ad2d7dc..79294a854 100644 --- a/LOTUS_TODO.md +++ b/LOTUS_TODO.md @@ -349,7 +349,7 @@ Themes: --- -### [ ] P5-40 · Desktop — Proactive Update Notifications (Tauri) +### [x] P5-40 · Desktop — Proactive Update Notifications (Tauri) ⚠️ UNTESTED (requires Tauri build) **What:** Automatically check for app updates on launch and periodically during long sessions. If an update is available, show an in-app toast or badge (e.g., on the Settings icon) to alert the user without requiring a manual check in settings. **Mechanism:** Use the `useTauriUpdater` hook in a global component like `ClientNonUIFeatures.tsx`. diff --git a/src/app/features/toast/LotusToastContainer.tsx b/src/app/features/toast/LotusToastContainer.tsx index 655e14525..09f6b511f 100644 --- a/src/app/features/toast/LotusToastContainer.tsx +++ b/src/app/features/toast/LotusToastContainer.tsx @@ -41,8 +41,12 @@ function ToastCard({ toast }: ToastCardProps) { }, [dismiss, toast.id]); const handleCardClick = () => { - // window.location.hash setter auto-prepends '#', so values must not include it - window.location.hash = toast.hashPath ?? `/room/${toast.roomId}`; + if (toast.onClick) { + toast.onClick(); + } else { + // window.location.hash setter auto-prepends '#', so values must not include it + window.location.hash = toast.hashPath ?? `/room/${toast.roomId}`; + } dismiss(toast.id); }; diff --git a/src/app/pages/client/ClientNonUIFeatures.tsx b/src/app/pages/client/ClientNonUIFeatures.tsx index e8d71838d..e6dfcf101 100644 --- a/src/app/pages/client/ClientNonUIFeatures.tsx +++ b/src/app/pages/client/ClientNonUIFeatures.tsx @@ -37,6 +37,7 @@ import { usePresenceUpdater } from '../../hooks/usePresenceUpdater'; import { useDeepLinkNavigate } from '../../hooks/useDeepLinkNavigate'; import { toastQueueAtom } from '../../state/toast'; import { useReminders } from '../../hooks/useReminders'; +import { useTauriUpdater } from '../../hooks/useTauriUpdater'; function isInQuietHours(start: string, end: string): boolean { const now = new Date(); @@ -429,6 +430,46 @@ function ReminderMonitor() { return null; } +const TAURI_UPDATE_CHECK_INTERVAL = 12 * 60 * 60_000; // 12 hours +const TAURI_UPDATE_LAST_CHECK_KEY = 'lotus.tauriUpdateLastCheck'; + +function TauriUpdateFeature() { + const { isTauri, status, check, install } = useTauriUpdater(); + const setToast = useSetAtom(toastQueueAtom); + const firedRef = useRef(null); + + useEffect(() => { + if (!isTauri) return; + + const runCheck = () => { + const last = Number(localStorage.getItem(TAURI_UPDATE_LAST_CHECK_KEY) ?? '0'); + if (Date.now() - last < TAURI_UPDATE_CHECK_INTERVAL) return; + localStorage.setItem(TAURI_UPDATE_LAST_CHECK_KEY, String(Date.now())); + check(); + }; + + runCheck(); + const interval = setInterval(runCheck, TAURI_UPDATE_CHECK_INTERVAL); + return () => clearInterval(interval); + }, [isTauri, check]); + + useEffect(() => { + if (status.state !== 'available') return; + if (firedRef.current === status.version) return; + firedRef.current = status.version; + setToast({ + id: `tauri-update-${status.version}`, + displayName: 'Update Available', + body: `Lotus Chat ${status.version} is ready to install.`, + roomName: 'System', + roomId: '', + onClick: install, + }); + }, [status, setToast, install]); + + return null; +} + function LotusDenoiseFeature() { const setToast = useSetAtom(toastQueueAtom); @@ -465,6 +506,7 @@ export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) { + {children} diff --git a/src/app/state/toast.ts b/src/app/state/toast.ts index 750176c4a..61b084d5a 100644 --- a/src/app/state/toast.ts +++ b/src/app/state/toast.ts @@ -8,6 +8,7 @@ export type ToastNotif = { roomName: string; roomId: string; hashPath?: string; // overrides window.location.hash navigation when set + onClick?: () => void; // custom click handler; skips hash navigation when set }; const baseAtom = atom([]);