From dfc2d1001bcce319d1f67a80e69c920bae3c9bdd Mon Sep 17 00:00:00 2001 From: gnacho Date: Sun, 30 Aug 2026 10:12:32 +0200 Subject: [PATCH] fix: sync update ribbon with Settings manual check (#122) --- app/src/components/UpdateRibbon.tsx | 17 +++++++++-------- app/src/lib/update-check.ts | 28 ++++++++++++++++++++++++++++ app/src/pages/Ajustes.tsx | 2 ++ 3 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 app/src/lib/update-check.ts diff --git a/app/src/components/UpdateRibbon.tsx b/app/src/components/UpdateRibbon.tsx index 0794575..16b33f2 100644 --- a/app/src/components/UpdateRibbon.tsx +++ b/app/src/components/UpdateRibbon.tsx @@ -3,16 +3,10 @@ import { useTranslation } from 'react-i18next'; import { RefreshCw, X } from 'lucide-react'; import { apiFetch } from '@/data/api-client'; import { UpdateDialog } from '@/components/UpdateDialog'; +import { CHECK_KEY, CHECK_INTERVAL, DISMISS_KEY, getDismissed, onRibbonSignal } from '@/lib/update-check'; -const CHECK_KEY = 'helios-last-update-check'; -const DISMISS_KEY = 'helios-release-dismissed'; -const CHECK_INTERVAL = 7 * 24 * 60 * 60 * 1000; const REPO_URL = 'https://github.com/gnacho/helios'; -function getDismissed(): string { - try { return window.localStorage.getItem(DISMISS_KEY) ?? ''; } catch { return ''; } -} - export default function UpdateRibbon() { const { t } = useTranslation(); const [state, setState] = useState<'idle' | 'checking' | 'uptodate' | 'available' | 'error'>('idle'); @@ -46,7 +40,14 @@ export default function UpdateRibbon() { } }; void run(); - return () => { stale = true; }; + const off = onRibbonSignal((latest) => { + if (stale) return; + if (latest !== getDismissed()) { + setLatestVersion(latest); + setState('available'); + } + }); + return () => { stale = true; off(); }; }, []); const dismissVersion = useCallback(() => { diff --git a/app/src/lib/update-check.ts b/app/src/lib/update-check.ts new file mode 100644 index 0000000..bc554db --- /dev/null +++ b/app/src/lib/update-check.ts @@ -0,0 +1,28 @@ +const CHECK_KEY = 'helios-last-update-check'; +const DISMISS_KEY = 'helios-release-dismissed'; +const CHECK_INTERVAL = 4 * 60 * 60 * 1000; +const EVENT_NAME = 'helios-update-available'; + +export { CHECK_KEY, DISMISS_KEY, CHECK_INTERVAL }; + +export function invalidateThrottle(): void { + try { window.localStorage.removeItem(CHECK_KEY); } catch { /* no storage */ } +} + +export function notifyRibbon(latest: string): void { + invalidateThrottle(); + window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: { latest } })); +} + +export function getDismissed(): string { + try { return window.localStorage.getItem(DISMISS_KEY) ?? ''; } catch { return ''; } +} + +export function onRibbonSignal(cb: (latest: string) => void): () => void { + const handler = (e: Event) => { + const detail = (e as CustomEvent<{ latest: string }>).detail; + if (detail?.latest) cb(detail.latest); + }; + window.addEventListener(EVENT_NAME, handler); + return () => window.removeEventListener(EVENT_NAME, handler); +} diff --git a/app/src/pages/Ajustes.tsx b/app/src/pages/Ajustes.tsx index c8380a2..bf40a48 100644 --- a/app/src/pages/Ajustes.tsx +++ b/app/src/pages/Ajustes.tsx @@ -56,6 +56,7 @@ import { fmtTime } from '@/lib/format'; import { cn } from '@/lib/utils'; import { ApiError, apiDelete, apiFetch, apiPost, apiPut } from '@/data/api-client'; import { UpdateDialog } from '@/components/UpdateDialog'; +import { notifyRibbon } from '@/lib/update-check'; import pkg from '../../package.json'; const easeOutQuart = [0.25, 1, 0.5, 1] as [number, number, number, number]; @@ -186,6 +187,7 @@ function AdminZone() { } else { setLatestVersion(status.latest); setUpdateStatus('available'); + notifyRibbon(status.latest); } } catch { setUpdateStatus('error');