From ad1e203adfeea60192b7687e4a46819ec75d6ae7 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 14 Jun 2026 07:42:18 +0800 Subject: [PATCH] Retry transient chart detail misses --- .../LiveTemperatureThresholdChart.tsx | 34 +++++++++++++++++-- .../__tests__/refreshCadencePolicy.test.ts | 7 ++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx index d1a3adff..5d93f46f 100644 --- a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx +++ b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx @@ -64,6 +64,7 @@ const PROBABILITY_REFRESH_AFTER_PATCH_MS = DASHBOARD_REFRESH_POLICY_MS.metar; const FOREGROUND_FULL_DETAIL_REFRESH_DEDUP_MS = 90_000; const NO_PATCH_CACHED_DETAIL_REFRESH_MS = DASHBOARD_REFRESH_POLICY_MS.observation; const DETAIL_LOAD_BATCH_DELAY_MS = 0; +const TRANSIENT_DETAIL_RETRY_DELAY_MS = 3_000; const INITIAL_DETAIL_LOAD_SLOTS = 3; const DEFERRED_DETAIL_LOAD_DELAY_MS = 1_200; const DEFERRED_DETAIL_LOAD_GROUP_SIZE = 3; @@ -792,13 +793,41 @@ export function LiveTemperatureThresholdChart({ setIsHourlyLoading(true); markDetailRequest("network"); let cancelled = false; + let retryScheduled = false; + let retryTimer: ReturnType | null = null; + + const scheduleTransientDetailRetry = () => { + retryScheduled = true; + markDetailDegraded(); + retryTimer = setTimeout(() => { + if (cancelled) return; + markDetailRequest("network"); + fetchHourlyForecastForCity(city, { bypassLocalCache: true, resolution: targetResolution }) + .then((data) => { + if (cancelled) return; + if (!data) { + markDetailDegraded({ showUserError: true }); + return; + } + applySuccessfulHourlyDetail(data); + }) + .catch(() => { + if (!cancelled) { + markDetailDegraded({ showUserError: true }); + } + }) + .finally(() => { + if (!cancelled) setIsHourlyLoading(false); + }); + }, TRANSIENT_DETAIL_RETRY_DELAY_MS); + }; const timer = setTimeout(() => { fetchHourlyForecastForCity(city, { resolution: targetResolution }) .then((data) => { if (cancelled) return; if (!data) { - markDetailDegraded({ showUserError: true }); + scheduleTransientDetailRetry(); return; } applySuccessfulHourlyDetail(data); @@ -809,13 +838,14 @@ export function LiveTemperatureThresholdChart({ } }) .finally(() => { - if (!cancelled) setIsHourlyLoading(false); + if (!cancelled && !retryScheduled) setIsHourlyLoading(false); }); }, DETAIL_LOAD_BATCH_DELAY_MS); return () => { cancelled = true; clearTimeout(timer); + if (retryTimer !== null) clearTimeout(retryTimer); }; }, [ city, diff --git a/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts index c9f0413f..ca0c2cf1 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts @@ -299,6 +299,13 @@ export async function runTests() { chartCanvasSourceIncludes(chartSource, "handleRetryDetail"), "city detail charts should show stale cache first and expose a retryable unavailable state", ); + assert( + chartSource.includes("TRANSIENT_DETAIL_RETRY_DELAY_MS") && + chartSource.includes("scheduleTransientDetailRetry") && + chartSource.includes("fetchHourlyForecastForCity(city, { bypassLocalCache: true, resolution: targetResolution })") && + chartSource.includes("!retryScheduled"), + "cold partial detail-batch misses should stay in loading state and retry cached detail once before showing unavailable", + ); const successfulHourlyDetailBlock = /const applySuccessfulHourlyDetail = useCallback\([\s\S]*?\n \}, \[row\]\);/.exec(chartSource)?.[0] || ""; assert(