From 0e9d6ec3881380b0a3f70ba24eed412b0325f050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Fri, 22 May 2026 08:45:09 +0200 Subject: [PATCH] fix(timeline): wait for canvas width before scrolling to today MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useLayoutEffect([]) fired before isInitialLoading resolved, so the scroll container had no canvas yet — scrollLeft was clipped to 0. Now the scroll-to-today fires on the first render where totalCanvasWidth becomes non-zero. The cleanup effect resets the guard on unmount so React Strict Mode's fake-unmount+remount also scrolls correctly. Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/timeline/TimelineView.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/timeline/TimelineView.tsx b/apps/web/src/components/timeline/TimelineView.tsx index ec4cc2d..3e5a399 100644 --- a/apps/web/src/components/timeline/TimelineView.tsx +++ b/apps/web/src/components/timeline/TimelineView.tsx @@ -689,15 +689,25 @@ function TimelineViewContent({ const pendingLeftCompensationPx = useRef(0); // Flag: scroll viewport to today after the next viewStart-driven re-layout. const pendingScrollToTodayRef = useRef(false); - - // Scroll to today on mount so the viewport opens with today at the left edge. - // Empty deps: intentionally runs once (and twice in React Strict Mode dev, both correct). - + // Guard reset on every real unmount (including Strict Mode fake-unmount) so the + // scroll-to-today fires correctly on remount. + const hasScrolledToTodayOnLoad = useRef(false); useLayoutEffect(() => { + return () => { + hasScrolledToTodayOnLoad.current = false; + }; + }, []); + + // Scroll to today the first time the canvas has its full width (after initial data load). + // Depends on totalCanvasWidth so it fires after isInitialLoading → false renders the canvas. + useLayoutEffect(() => { + if (totalCanvasWidth === 0) return; + if (hasScrolledToTodayOnLoad.current) return; const el = scrollContainerRef.current; if (!el) return; el.scrollLeft = toLeft(today); - }, []); + hasScrolledToTodayOnLoad.current = true; + }, [totalCanvasWidth, toLeft, today]); // Apply scroll compensation synchronously after the canvas grows (left-extend or Today button). useLayoutEffect(() => {