The timeline had a hard left boundary — once scrolled to the initial left edge, you could not scroll further into the past. The only way to see earlier data was to click the "Previous 4 weeks" toolbar button repeatedly.
Changes
TimelineContext.tsx
Default viewStart changed from today − 30 days → today (URL ?startDate= override still respected)
Same change in the URL-sync useEffect fallback
TimelineView.tsx
Left-edge auto-expansion: handleContainerScroll now detects when scrollLeft < CELL_WIDTH * 40 and prepends 120 days to the canvas, mirroring the existing right-edge auto-expansion
No-jump compensation: a useLayoutEffect keyed on viewStart applies the exact pixel compensation to scrollLeft in the same paint frame the canvas grows — no visual flash
handleNavigateToday: now resets to today (matching the new default)
handleNavigateBack: clamped at minDate
Result
Both resource view and project view (same TimelineContext / TimelineView) now open with today at the left edge and allow smooth continuous scroll into history
URL deep-links with ?startDate= work unchanged
Verification
pnpm tsc --noEmit✅
pnpm lint✅ (0 errors)
App restarted — load /timeline, scroll left, canvas grows into the past without a jump
## Problem
The timeline had a hard left boundary — once scrolled to the initial left edge, you could not scroll further into the past. The only way to see earlier data was to click the "Previous 4 weeks" toolbar button repeatedly.
## Changes
### `TimelineContext.tsx`
- Default `viewStart` changed from `today − 30 days` → **`today`** (URL `?startDate=` override still respected)
- Same change in the URL-sync `useEffect` fallback
### `TimelineView.tsx`
- **Left-edge auto-expansion**: `handleContainerScroll` now detects when `scrollLeft < CELL_WIDTH * 40` and prepends 120 days to the canvas, mirroring the existing right-edge auto-expansion
- **No-jump compensation**: a `useLayoutEffect` keyed on `viewStart` applies the exact pixel compensation to `scrollLeft` in the same paint frame the canvas grows — no visual flash
- **5-year floor** (`minDate = today − 1825 days`): prevents unbounded `viewDays` growth
- **`handleNavigateToday`**: now resets to `today` (matching the new default)
- **`handleNavigateBack`**: clamped at `minDate`
## Result
- Both **resource view** and **project view** (same `TimelineContext` / `TimelineView`) now open with today at the left edge and allow smooth continuous scroll into history
- URL deep-links with `?startDate=` work unchanged
## Verification
- `pnpm tsc --noEmit` ✅
- `pnpm lint` ✅ (0 errors)
- App restarted — load `/timeline`, scroll left, canvas grows into the past without a jump
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Previously viewStart defaulted to today-30 and the scroll container had
no left-edge expansion logic, so users hit a hard wall when scrolling
left. This change:
- Sets viewStart default to today so the viewport opens with today at
the left edge (URL ?startDate= override still respected).
- Adds left-edge auto-expansion in handleContainerScroll: when the user
scrolls within 40 cells of the left boundary, 120 days are prepended
and a useLayoutEffect applies the matching scrollLeft compensation in
the same paint frame to prevent a visual jump.
- Floors backward navigation at 5 years (minDate) to prevent unbounded
viewDays growth.
- Updates handleNavigateToday to match: resets to today rather than
today-30.
Both resource view and project view use the same TimelineContext /
TimelineView, so both are fixed by this change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test takes >5s on the QNAP act runner because dynamic import of
next/server has to transpile the module cold on first call. Raise the
per-test timeout to 15s to give it headroom without changing the test logic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
The guard-ref approach broke in React Strict Mode (dev): the ref
persisted as `true` across the simulated remount, so the second
invocation skipped the scroll — leaving scrollLeft=0 (today-90
at the left edge, not today). An empty-deps useLayoutEffect runs
twice in Strict Mode but both executions fire against the same
initial `toLeft` and produce the correct result.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 <noreply@anthropic.com>
totalCanvasWidth is computed from viewStart/viewDays before data loads,
so the previous trigger fired during the loading spinner. scrollLeft
was clipped to 0 (no canvas in DOM yet) and the guard was set, blocking
the real scroll after data arrived. Using isInitialLoading as the dep
fires the effect exactly when the canvas enters the DOM.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
The timeline had a hard left boundary — once scrolled to the initial left edge, you could not scroll further into the past. The only way to see earlier data was to click the "Previous 4 weeks" toolbar button repeatedly.
Changes
TimelineContext.tsxviewStartchanged fromtoday − 30 days→today(URL?startDate=override still respected)useEffectfallbackTimelineView.tsxhandleContainerScrollnow detects whenscrollLeft < CELL_WIDTH * 40and prepends 120 days to the canvas, mirroring the existing right-edge auto-expansionuseLayoutEffectkeyed onviewStartapplies the exact pixel compensation toscrollLeftin the same paint frame the canvas grows — no visual flashminDate = today − 1825 days): prevents unboundedviewDaysgrowthhandleNavigateToday: now resets totoday(matching the new default)handleNavigateBack: clamped atminDateResult
TimelineContext/TimelineView) now open with today at the left edge and allow smooth continuous scroll into history?startDate=work unchangedVerification
pnpm tsc --noEmit✅pnpm lint✅ (0 errors)/timeline, scroll left, canvas grows into the past without a jump🤖 Generated with Claude Code