fix(timeline): pre-load 90-day past buffer + scroll to today on mount
CI / Architecture Guardrails (pull_request) Successful in 5m6s
CI / Typecheck (pull_request) Successful in 7m31s
CI / Assistant Split Regression (pull_request) Successful in 6m45s
CI / Lint (pull_request) Successful in 6m19s
CI / Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Fresh-Linux Docker Deploy (pull_request) Has been cancelled
CI / Release Images (pull_request) Has been cancelled
CI / Build (pull_request) Has been cancelled
CI / Architecture Guardrails (pull_request) Successful in 5m6s
CI / Typecheck (pull_request) Successful in 7m31s
CI / Assistant Split Regression (pull_request) Successful in 6m45s
CI / Lint (pull_request) Successful in 6m19s
CI / Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Fresh-Linux Docker Deploy (pull_request) Has been cancelled
CI / Release Images (pull_request) Has been cancelled
CI / Build (pull_request) Has been cancelled
viewStart=today left no canvas to the left of scrollLeft=0, making left-scroll physically impossible. Now viewStart defaults to today-90 so the canvas always has 90 days to scroll into, and a mount-time useLayoutEffect positions the viewport with today at the left edge. The Today button restores this view: scrolls in-range, or resets viewStart and schedules a post-layout scroll if today has scrolled out of the visible window. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -284,7 +284,7 @@ export function TimelineProvider({
|
||||
const d = new Date(sp);
|
||||
if (!isNaN(d.getTime())) return d;
|
||||
}
|
||||
return today;
|
||||
return addDays(today, -90);
|
||||
});
|
||||
const [viewDays, setViewDays] = useState(() => {
|
||||
const sp = searchParams.get("days");
|
||||
@@ -310,7 +310,7 @@ export function TimelineProvider({
|
||||
const d = new Date(spStart);
|
||||
if (!isNaN(d.getTime())) return d;
|
||||
}
|
||||
return today;
|
||||
return addDays(today, -90);
|
||||
});
|
||||
|
||||
const spDays = searchParams.get("days");
|
||||
|
||||
@@ -687,15 +687,33 @@ function TimelineViewContent({
|
||||
|
||||
// Pixels to add to scrollLeft after a left-extension re-render (prevents jump).
|
||||
const pendingLeftCompensationPx = useRef(0);
|
||||
// Flag: scroll viewport to today after the next viewStart-driven re-layout.
|
||||
const pendingScrollToTodayRef = useRef(false);
|
||||
// Guard: only auto-scroll to today once on initial mount.
|
||||
const scrolledToTodayOnMount = useRef(false);
|
||||
|
||||
// Apply scroll compensation synchronously after the canvas grows leftward.
|
||||
// Scroll to today on first mount so the viewport opens with today at the left edge.
|
||||
useLayoutEffect(() => {
|
||||
const px = pendingLeftCompensationPx.current;
|
||||
if (px === 0) return;
|
||||
if (scrolledToTodayOnMount.current) return;
|
||||
const el = scrollContainerRef.current;
|
||||
if (el) el.scrollLeft += px;
|
||||
if (!el) return;
|
||||
el.scrollLeft = toLeft(today);
|
||||
scrolledToTodayOnMount.current = true;
|
||||
}, [toLeft, today]);
|
||||
|
||||
// Apply scroll compensation synchronously after the canvas grows (left-extend or Today button).
|
||||
useLayoutEffect(() => {
|
||||
const el = scrollContainerRef.current;
|
||||
if (!el) return;
|
||||
const px = pendingLeftCompensationPx.current;
|
||||
if (px !== 0) {
|
||||
el.scrollLeft += px;
|
||||
pendingLeftCompensationPx.current = 0;
|
||||
}, [viewStart]);
|
||||
} else if (pendingScrollToTodayRef.current) {
|
||||
el.scrollLeft = toLeft(today);
|
||||
pendingScrollToTodayRef.current = false;
|
||||
}
|
||||
}, [viewStart, toLeft, today]);
|
||||
|
||||
// 5-year floor — no practical data exists further back; prevents runaway growth.
|
||||
const minDate = useMemo(() => addDays(today, -(365 * 5)), [today]);
|
||||
@@ -709,7 +727,20 @@ function TimelineViewContent({
|
||||
}),
|
||||
[setViewStart, minDate],
|
||||
);
|
||||
const handleNavigateToday = useCallback(() => setViewStart(today), [setViewStart, today]);
|
||||
const handleNavigateToday = useCallback(() => {
|
||||
const el = scrollContainerRef.current;
|
||||
const todayMs = new Date(today).setHours(0, 0, 0, 0);
|
||||
const vsMs = new Date(viewStart).setHours(0, 0, 0, 0);
|
||||
const veMs = new Date(addDays(viewStart, viewDays)).setHours(0, 0, 0, 0);
|
||||
if (todayMs >= vsMs && todayMs < veMs && el) {
|
||||
// Today is in range — just scroll without touching state.
|
||||
el.scrollLeft = toLeft(today);
|
||||
} else {
|
||||
// Today is out of range — reset the window and schedule a scroll.
|
||||
pendingScrollToTodayRef.current = true;
|
||||
setViewStart(addDays(today, -90));
|
||||
}
|
||||
}, [today, viewStart, viewDays, toLeft, setViewStart]);
|
||||
const handleNavigateForward = useCallback(
|
||||
() => setViewStart((v) => addDays(v, 28)),
|
||||
[setViewStart],
|
||||
|
||||
Reference in New Issue
Block a user