From acd7ed8ba34ed0e55cd90faf89ef7a637dc52481 Mon Sep 17 00:00:00 2001 From: nyk <93952610+0xNyk@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:18:00 +0700 Subject: [PATCH] fix(ui): persist doctor banner dismiss with 24h expiry (#328) * fix(ui): persist doctor banner dismiss with 24h expiry (#320) Replace ephemeral useState dismiss with Zustand store + localStorage. Banner stays hidden for 24 hours after dismiss, then resurfaces so users can re-check. Mirrors the existing UpdateBanner pattern. Closes #320 * test: add unit tests for doctor banner dismiss persistence Tests verify localStorage persistence, 24h expiry, page refresh survival, and graceful handling of corrupted values. --- .../layout/openclaw-doctor-banner.tsx | 10 +- .../__tests__/doctor-banner-dismiss.test.ts | 109 ++++++++++++++++++ src/store/index.ts | 18 +++ 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 src/lib/__tests__/doctor-banner-dismiss.test.ts diff --git a/src/components/layout/openclaw-doctor-banner.tsx b/src/components/layout/openclaw-doctor-banner.tsx index 29ac1f9..bc88ccf 100644 --- a/src/components/layout/openclaw-doctor-banner.tsx +++ b/src/components/layout/openclaw-doctor-banner.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import { Button } from '@/components/ui/button' +import { useMissionControl } from '@/store' interface OpenClawDoctorStatus { level: 'healthy' | 'warning' | 'error' @@ -23,7 +24,8 @@ type BannerState = 'idle' | 'fixing' | 'success' | 'error' export function OpenClawDoctorBanner() { const [doctor, setDoctor] = useState(null) const [loading, setLoading] = useState(true) - const [dismissed, setDismissed] = useState(false) + const doctorDismissedAt = useMissionControl(s => s.doctorDismissedAt) + const dismissDoctor = useMissionControl(s => s.dismissDoctor) const [state, setState] = useState('idle') const [errorMsg, setErrorMsg] = useState(null) const [showDetails, setShowDetails] = useState(false) @@ -38,7 +40,6 @@ export function OpenClawDoctorBanner() { } const data = await res.json() setDoctor(data) - setDismissed(false) } catch { setDoctor(null) } finally { @@ -95,6 +96,9 @@ export function OpenClawDoctorBanner() { } } + const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000 + const dismissed = doctorDismissedAt != null && (Date.now() - doctorDismissedAt) < TWENTY_FOUR_HOURS + if (loading || dismissed || !doctor || doctor.healthy) return null const tone = @@ -176,7 +180,7 @@ export function OpenClawDoctorBanner() {