From 5b65da60497c1b34b6e15bd8c19df8fe5a275f2f Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 10 Jun 2026 23:36:00 +0800 Subject: [PATCH] refresh visible charts without force refresh --- .../LiveTemperatureThresholdChart.tsx | 14 +++++++------- .../__tests__/refreshCadencePolicy.test.ts | 8 +++++++- .../__tests__/ssePatchArchitecture.test.ts | 14 ++++++++++---- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx index 882da2db..62bb9d0f 100644 --- a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx +++ b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx @@ -62,7 +62,7 @@ const PEAK_GLOW_BADGE_CLASS = { const PROBABILITY_REFRESH_AFTER_PATCH_MS = DASHBOARD_REFRESH_POLICY_MS.metar; const FOREGROUND_FULL_DETAIL_REFRESH_DEDUP_MS = 90_000; -const NO_PATCH_FULL_DETAIL_FALLBACK_MS = DASHBOARD_REFRESH_POLICY_MS.metar; +const NO_PATCH_CACHED_DETAIL_REFRESH_MS = DASHBOARD_REFRESH_POLICY_MS.observation; const DETAIL_LOAD_BATCH_DELAY_MS = 0; const INITIAL_DETAIL_LOAD_SLOTS = 3; const DEFERRED_DETAIL_LOAD_DELAY_MS = 1_200; @@ -913,17 +913,17 @@ export function LiveTemperatureThresholdChart({ }; }, [resyncVersion, city, targetResolution, markDetailDegraded, markDetailRequest, applySuccessfulHourlyDetail]); - // ── SSE fallback: only full-fetch if a visible chart has seen no patch for one METAR cadence ── + // ── SSE fallback: visible charts refresh cached detail at observation cadence if patches stop. ── useEffect(() => { if (!shouldPollLiveChart({ city, compact, isActive, isMaximized })) return; let cancelled = false; - const refreshFullDetail = () => { + const refreshCachedDetail = () => { const now = Date.now(); lastPatchAtRef.current = now; - markDetailRequest("force_refresh"); + markDetailRequest("network"); - fetchHourlyForecastForCity(city, { ignoreCache: true, resolution: targetResolution }) + fetchHourlyForecastForCity(city, { resolution: targetResolution }) .then((data) => { if (cancelled) return; if (!data) { @@ -944,9 +944,9 @@ export function LiveTemperatureThresholdChart({ const checkFallback = () => { if (typeof document !== "undefined" && document.visibilityState === "hidden") return; - if (Date.now() - lastPatchAtRef.current < NO_PATCH_FULL_DETAIL_FALLBACK_MS) return; + if (Date.now() - lastPatchAtRef.current < NO_PATCH_CACHED_DETAIL_REFRESH_MS) return; - refreshFullDetail(); + refreshCachedDetail(); }; const id = setInterval(checkFallback, 60_000); diff --git a/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts index 7cddb122..076ad9dd 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/refreshCadencePolicy.test.ts @@ -74,7 +74,13 @@ export async function runTests() { chartSource.includes("latestPatch") && chartSource.includes("DASHBOARD_REFRESH_POLICY_MS.metar") && !chartSource.includes("2 * 60_000"), - "selected city chart should consume SSE patches and use a METAR-cadence no-patch fallback instead of a 2-minute forced refresh", + "selected city chart should consume SSE patches and keep METAR cadence for heavy probability refreshes instead of a 2-minute forced refresh", + ); + assert( + chartSource.includes("NO_PATCH_CACHED_DETAIL_REFRESH_MS = DASHBOARD_REFRESH_POLICY_MS.observation") && + chartSource.includes("refreshCachedDetail") && + chartSource.includes("fetchHourlyForecastForCity(city, { resolution: targetResolution })"), + "visible charts should do a lightweight cached detail refresh every observation cadence when no SSE patch arrives", ); assert( chartSource.includes("preloadTemperatureChartCanvas"), diff --git a/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts index 75b6aead..121b0727 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/ssePatchArchitecture.test.ts @@ -194,7 +194,11 @@ export function runTests() { assert( chart.includes("DASHBOARD_REFRESH_POLICY_MS.metar") && !chart.includes("2 * 60_000"), - "temperature chart must wait one METAR cadence without patches before full-fetch fallback", + "temperature chart must keep METAR cadence for heavy patch-triggered probability refreshes", + ); + assert( + chart.includes("NO_PATCH_CACHED_DETAIL_REFRESH_MS = DASHBOARD_REFRESH_POLICY_MS.observation"), + "temperature chart must use observation cadence for lightweight cached no-patch refreshes", ); assert(chart.includes("TemperatureChartCanvas"), "temperature chart shell must compose the extracted chart canvas"); assert(chart.includes("TemperatureStatsBars"), "temperature chart shell must compose the extracted stat bars"); @@ -225,10 +229,12 @@ export function runTests() { chart.includes("ignoreCache: true") && chart.includes("currentCityLocalDate !== loadedLocalDate"), "temperature chart must background-refresh full city detail when the city-local day rolls over", ); - const fallbackRefreshBlock = chart.match(/const refreshFullDetail = \(\) => \{[\s\S]*?\n \};/)?.[0] || ""; + const fallbackRefreshBlock = chart.match(/const refreshCachedDetail = \(\) => \{[\s\S]*?\n \};/)?.[0] || ""; assert( - !fallbackRefreshBlock.includes("setIsHourlyLoading(true)"), - "no-patch fallback refresh should update the chart in the background without showing the loading overlay", + fallbackRefreshBlock.includes("fetchHourlyForecastForCity(city, { resolution: targetResolution })") && + !fallbackRefreshBlock.includes("ignoreCache: true") && + !fallbackRefreshBlock.includes("setIsHourlyLoading(true)"), + "no-patch fallback refresh should update the chart through cached batch detail without force-refreshing or showing the loading overlay", ); const resyncBlock = chart.match(/useEffect\(\(\) => \{\s*if \(!resyncVersion \|\| !city\) return;[\s\S]*?\}, \[resyncVersion, city, targetResolution, applySuccessfulHourlyDetail\]\);/)?.[0] || ""; assert(