From b663455c267e200b97c035f7cbaaea5b156a0025 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Thu, 12 Mar 2026 18:18:00 +0800 Subject: [PATCH] Sync clock to minute boundary and stabilize "Updated" text Clock: Instead of starting a 60s interval from mount time (which drifts from the system clock), calculate ms until the next :00 second mark, setTimeout to that point, then setInterval every 60s from there. Updated text: Replaced formatDistanceToNow (which flickered between "less than a minute ago" / "a minute ago" / "2 minutes ago" on each render) with a stable minute-based calculation derived from clockNow: "just now" / "1 min ago" / "N min ago". Co-Authored-By: Claude Opus 4.6 --- .../components/dashboard/DashboardPage.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/dashboard/DashboardPage.tsx b/frontend/src/components/dashboard/DashboardPage.tsx index e987940..00263cc 100644 --- a/frontend/src/components/dashboard/DashboardPage.tsx +++ b/frontend/src/components/dashboard/DashboardPage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { format, formatDistanceToNow } from 'date-fns'; +import { format } from 'date-fns'; import { Bell, Plus, Calendar as CalIcon, ListTodo, RefreshCw } from 'lucide-react'; import api from '@/lib/api'; import type { DashboardData, UpcomingResponse, WeatherData } from '@/types'; @@ -44,10 +44,16 @@ export default function DashboardPage() { const dropdownRef = useRef(null); const [clockNow, setClockNow] = useState(() => new Date()); - // Live clock — update every minute + // Live clock — synced to the minute boundary useEffect(() => { - const interval = setInterval(() => setClockNow(new Date()), 60_000); - return () => clearInterval(interval); + let intervalId: ReturnType; + // Wait until the next :00 second mark, then tick every 60s + const msUntilNextMinute = (60 - new Date().getSeconds()) * 1000 - new Date().getMilliseconds(); + const timeoutId = setTimeout(() => { + setClockNow(new Date()); + intervalId = setInterval(() => setClockNow(new Date()), 60_000); + }, msUntilNextMinute); + return () => { clearTimeout(timeoutId); clearInterval(intervalId); }; }, []); // Click outside to close dropdown @@ -167,7 +173,12 @@ export default function DashboardPage() { } const updatedAgo = dataUpdatedAt - ? formatDistanceToNow(new Date(dataUpdatedAt), { addSuffix: true }) + ? (() => { + const mins = Math.floor((clockNow.getTime() - dataUpdatedAt) / 60_000); + if (mins < 1) return 'just now'; + if (mins === 1) return '1 min ago'; + return `${mins} min ago`; + })() : null; return (