Fix QA findings: rename ambient component, add clock tab-resume sync
W-02: Renamed layout/AmbientBackground → AppAmbientBackground to avoid naming collision with auth/AmbientBackground (IDE auto-import confusion). S-01: Added visibilitychange listener to re-sync clock after tab sleep/resume. Previously the interval would drift after laptop sleep or long tab backgrounding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b663455c26
commit
6e0a848c45
@ -44,16 +44,35 @@ export default function DashboardPage() {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [clockNow, setClockNow] = useState(() => new Date());
|
||||
|
||||
// Live clock — synced to the minute boundary
|
||||
// Live clock — synced to the minute boundary, re-syncs after tab sleep/resume
|
||||
useEffect(() => {
|
||||
let intervalId: ReturnType<typeof setInterval>;
|
||||
// Wait until the next :00 second mark, then tick every 60s
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
function startClock() {
|
||||
clearTimeout(timeoutId);
|
||||
clearInterval(intervalId);
|
||||
setClockNow(new Date());
|
||||
const msUntilNextMinute = (60 - new Date().getSeconds()) * 1000 - new Date().getMilliseconds();
|
||||
const timeoutId = setTimeout(() => {
|
||||
timeoutId = setTimeout(() => {
|
||||
setClockNow(new Date());
|
||||
intervalId = setInterval(() => setClockNow(new Date()), 60_000);
|
||||
}, msUntilNextMinute);
|
||||
return () => { clearTimeout(timeoutId); clearInterval(intervalId); };
|
||||
}
|
||||
|
||||
startClock();
|
||||
|
||||
// Re-sync when tab becomes visible again (after sleep/background throttle)
|
||||
function handleVisibility() {
|
||||
if (document.visibilityState === 'visible') startClock();
|
||||
}
|
||||
document.addEventListener('visibilitychange', handleVisibility);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
clearInterval(intervalId);
|
||||
document.removeEventListener('visibilitychange', handleVisibility);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Click outside to close dropdown
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
* Also renders a noise texture for tactile depth and a radial vignette
|
||||
* to darken edges and draw focus to center content.
|
||||
*/
|
||||
export default function AmbientBackground() {
|
||||
export default function AppAmbientBackground() {
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 z-0 overflow-hidden" aria-hidden="true">
|
||||
{/* Animated gradient orbs — oversized so drift never clips at visible edges */}
|
||||
@ -7,7 +7,7 @@ import { LockProvider } from '@/hooks/useLock';
|
||||
import { NotificationProvider } from '@/hooks/useNotifications';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import Sidebar from './Sidebar';
|
||||
import AmbientBackground from './AmbientBackground';
|
||||
import AppAmbientBackground from './AppAmbientBackground';
|
||||
import LockOverlay from './LockOverlay';
|
||||
import NotificationToaster from '@/components/notifications/NotificationToaster';
|
||||
|
||||
@ -35,7 +35,7 @@ export default function AppLayout() {
|
||||
onMobileClose={() => setMobileOpen(false)}
|
||||
/>
|
||||
<div className="flex-1 flex flex-col overflow-hidden relative ambient-glass">
|
||||
<AmbientBackground />
|
||||
<AppAmbientBackground />
|
||||
{/* Mobile header */}
|
||||
<div className="relative z-10 flex md:hidden items-center h-14 border-b bg-card px-4">
|
||||
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(true)}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user