feat(timeline): start at today + infinite scroll into the past #65

Merged
Hartmut merged 7 commits from feature/timeline-past-scroll into main 2026-05-22 11:43:08 +02:00
Owner

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 daystoday (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

## 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)
Hartmut added 3 commits 2026-05-22 07:17:07 +02:00
rename(cleanup): drop last capakraken strings from UI, scripts, schema, tests
CI / Architecture Guardrails (pull_request) Successful in 4m26s
CI / Assistant Split Regression (pull_request) Successful in 5m38s
CI / Lint (pull_request) Successful in 6m6s
CI / Typecheck (pull_request) Successful in 6m34s
CI / Build (pull_request) Successful in 4m13s
CI / Unit Tests (pull_request) Failing after 10m20s
CI / E2E Tests (pull_request) Successful in 5m28s
CI / Fresh-Linux Docker Deploy (pull_request) Successful in 6m14s
CI / Release Images (pull_request) Has been skipped
a58b99a33a
AppShell.tsx top-left brand → Nexus (desktop sidebar + mobile top-bar),
shell echo strings, prisma schema header, test fixture token, playwright
runtime DB URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ci: retrigger — runner flake on unit-tests step (run #163)
CI / Architecture Guardrails (pull_request) Successful in 4m9s
CI / Typecheck (pull_request) Successful in 5m41s
CI / Lint (pull_request) Successful in 5m47s
CI / Assistant Split Regression (pull_request) Successful in 6m8s
CI / Build (pull_request) Failing after 15m55s
CI / E2E Tests (pull_request) Has been skipped
CI / Fresh-Linux Docker Deploy (pull_request) Has been skipped
CI / Unit Tests (pull_request) Successful in 30m26s
CI / Release Images (pull_request) Failing after 10m48s
749a39097c
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(timeline): start at today and allow infinite scroll into the past
CI / Architecture Guardrails (pull_request) Successful in 17m31s
CI / Assistant Split Regression (pull_request) Successful in 9m42s
CI / Typecheck (pull_request) Successful in 20m48s
CI / Lint (pull_request) Successful in 8m6s
CI / Unit Tests (pull_request) Failing after 7m32s
CI / Build (pull_request) Successful in 9m12s
CI / E2E Tests (pull_request) Successful in 6m12s
CI / Fresh-Linux Docker Deploy (pull_request) Successful in 6m58s
CI / Release Images (pull_request) Has been skipped
4a841d5acb
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>
Hartmut added 1 commit 2026-05-22 08:06:58 +02:00
test(cron): raise timeout for next/server cold-import on act runner
CI / Architecture Guardrails (pull_request) Successful in 4m22s
CI / Typecheck (pull_request) Successful in 6m48s
CI / Assistant Split Regression (pull_request) Successful in 7m49s
CI / Lint (pull_request) Successful in 7m59s
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 / Unit Tests (pull_request) Has been cancelled
CI / Build (pull_request) Has been cancelled
6ec512e302
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>
Hartmut added 1 commit 2026-05-22 08:15:51 +02:00
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
944d36bdb2
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>
Hartmut added 1 commit 2026-05-22 08:38:26 +02:00
fix(timeline): use empty-deps useLayoutEffect for mount scroll to today
CI / Architecture Guardrails (pull_request) Successful in 4m53s
CI / Typecheck (pull_request) Successful in 4m55s
CI / Assistant Split Regression (pull_request) Successful in 5m38s
CI / Build (pull_request) Has been cancelled
CI / Lint (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 / Unit Tests (pull_request) Has been cancelled
7285668c52
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>
Hartmut added 1 commit 2026-05-22 08:45:19 +02:00
fix(timeline): wait for canvas width before scrolling to today
CI / Assistant Split Regression (pull_request) Has been cancelled
CI / Lint (pull_request) Has been cancelled
CI / Typecheck (pull_request) Has been cancelled
CI / Unit Tests (pull_request) Has been cancelled
CI / Build (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 / Architecture Guardrails (pull_request) Has been cancelled
0e9d6ec388
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>
Hartmut added 1 commit 2026-05-22 08:48:34 +02:00
fix(timeline): trigger scroll-to-today on isInitialLoading→false not totalCanvasWidth
CI / Architecture Guardrails (pull_request) Successful in 2m53s
CI / Typecheck (pull_request) Successful in 3m28s
CI / Assistant Split Regression (pull_request) Successful in 3m40s
CI / Lint (pull_request) Successful in 4m26s
CI / Unit Tests (pull_request) Successful in 8m36s
CI / Build (pull_request) Successful in 9m47s
CI / E2E Tests (pull_request) Failing after 14m2s
CI / Fresh-Linux Docker Deploy (pull_request) Successful in 16m53s
CI / Release Images (pull_request) Has been skipped
2383bcbdc0
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>
Hartmut added 1 commit 2026-05-22 09:34:15 +02:00
ci: retrigger — E2E webServer timeout on run #172 (QNAP runner flake)
CI / Typecheck (pull_request) Successful in 3m25s
CI / Architecture Guardrails (pull_request) Successful in 3m44s
CI / Lint (pull_request) Successful in 2m8s
CI / Assistant Split Regression (pull_request) Successful in 3m44s
CI / Unit Tests (pull_request) Successful in 7m29s
CI / Build (pull_request) Successful in 6m52s
CI / Fresh-Linux Docker Deploy (pull_request) Successful in 3m53s
CI / E2E Tests (pull_request) Successful in 20m24s
CI / Release Images (pull_request) Has been skipped
12044f638e
Hartmut merged commit eb1875e524 into main 2026-05-22 11:43:08 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Hartmut/Nexus#65