feat(tauri): proactive update notifications via toast (P5-40)
TauriUpdateFeature component in ClientNonUIFeatures checks for updates on mount and every 12h (skips if checked within the window). On update available, fires a Lotus toast: "Lotus Chat vX.Y.Z is ready to install." Clicking the toast calls install(). No-op on web (isTauri guard). Also adds optional onClick to ToastNotif type and wires it in LotusToastContainer so custom click handlers can skip hash navigation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -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`.
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<string | null>(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) {
|
||||
<InviteNotifications />
|
||||
<MessageNotifications />
|
||||
<ReminderMonitor />
|
||||
<TauriUpdateFeature />
|
||||
<LotusDenoiseFeature />
|
||||
<DeepLinkNavigator />
|
||||
{children}
|
||||
|
||||
@@ -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<ToastNotif[]>([]);
|
||||
|
||||
Reference in New Issue
Block a user