feat(timeline): add now pill and time indicator#204
Conversation
Extract timeToOffset/offsetToTime as the single place the 2px/minute timeline scale lives; TimeScale and set positioning now consume them instead of re-deriving the 2/120 constants. Closes #191
differenceInMinutes truncates to whole minutes, so timeToOffset and offsetToTime did not round-trip for sub-minute moments. Base the conversion on epoch milliseconds; output is unchanged for whole-minute inputs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
Add a scrollTo search param that captures the moment centered in the Timeline viewport. A pure resolveTimelineMountMoment precedence function (scrollTo -> day filter -> festival start) decides where to center on mount; useTimelineScrollSync owns that one-time centering plus debounced (~300ms), 5-minute-rounded, history-replace writes on user scroll. useTimelineUrlState now selects its filter params with structural sharing so scrollTo writes don't recompute the filtered schedule. Closes #192 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
…' into claude/188-192-scrollto-url-state
Sticky toolbar above the Timeline strip with one button per festival day (weekday + date, e.g. "Thu 13"). Tapping a day writes scrollTo and smooth-scrolls to that day's first set, via a new jumpTo(moment) on useTimelineScrollSync. When a day filter is active, only that day's button renders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
Position-based suppression of programmatic scroll events (a browser may fire more than one per scrollLeft write), and the back-restore e2e half now skips explicitly instead of silently passing when no set link is rendered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
…e' into claude/188-193-day-jump-toolbar
When a day jump clamps at the strip start (first festival day), the requested moment is unreachable and the debounced post-scroll write would silently replace scrollTo with a different value. Write the clamped center moment up front so the URL never drifts; e2e now asserts write stability and covers the first-day clamp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
Adds a Now pill in the day-jump toolbar and a dashed current-time indicator on the Timeline strip, both rendered only while now (festival timezone) falls inside the rendered timeline's festival window. Extends resolveTimelineMountMoment's precedence with a now-1h rule (scrollTo -> day filter -> now-1h inside window -> festival start), backed by a new isNowWithinFestivalWindow pure function and a useNow(60s) hook that is the sole place `new Date()` is read for this feature. Closes #194
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… into claude/188-194-now-indicator
Active stage/time filters shrank TimelineData's bounds and hid the Now pill mid-festival; the pill and the mount now-rule are now gated on a new calculateScheduleWindow(scheduleDays) computed from the unfiltered schedule, while the indicator stays gated on the rendered strip's own bounds. Also clamps the mount now-rule to max(now - 1h, window start) so a now within the window's first hour can't resolve before it opens.
Also drops a redundant schema comment, per review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
The browser clamps scrollLeft to the scrollable range, so the suppression ref must record the read-back value, not the requested target, for the scroll handler to recognize the mount event. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
…e' into claude/188-193-day-jump-toolbar
Per review: shorter docblocks on the scroll-sync hook, toolbar, and day-jump helper; JSX section comments removed; test helpers moved below the cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
Add a scrollTo search param that captures the moment centered in the Timeline viewport. A pure resolveTimelineMountMoment precedence function (scrollTo -> day filter -> festival start) decides where to center on mount; useTimelineScrollSync owns that one-time centering plus debounced (~300ms), 5-minute-rounded, history-replace writes on user scroll. useTimelineUrlState now selects its filter params with structural sharing so scrollTo writes don't recompute the filtered schedule. Closes #192 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
Position-based suppression of programmatic scroll events (a browser may fire more than one per scrollLeft write), and the back-restore e2e half now skips explicitly instead of silently passing when no set link is rendered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
Also drops a redundant schema comment, per review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
The browser clamps scrollLeft to the scrollable range, so the suppression ref must record the read-back value, not the requested target, for the scroll handler to recognize the mount event. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
The rebase onto main brought in #199's removal of the view search param; the select still read it, failing typecheck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AziTqr3f12fxALYD6jhrW8
…e' into claude/188-193-day-jump-toolbar # Conflicts: # src/hooks/useTimelineScrollSync.ts # src/hooks/useTimelineUrlState.ts # src/lib/timelineCalculator.test.ts # src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx
… into claude/188-194-now-indicator # Conflicts: # src/hooks/useTimelineScrollSync.ts # src/pages/EditionView/tabs/ScheduleTab/horizontal/Timeline.tsx # src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx # src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineToolbar.tsx
4cfe485 to
ff8ecf0
Compare
…dicator # Conflicts: # src/hooks/useTimelineScrollSync.ts # src/hooks/useTimelineUrlState.ts # src/lib/timeUtils.test.ts # src/lib/timeUtils.ts # src/lib/timelineDayJump.test.ts # src/lib/timelineDayJump.ts # src/lib/timelineMountMoment.test.ts # src/lib/timelineMountMoment.ts # src/pages/EditionView/tabs/ScheduleTab/horizontal/DayJumpButtons.tsx # src/pages/EditionView/tabs/ScheduleTab/horizontal/Timeline.tsx # src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx # src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineToolbar.tsx # tests/e2e/timeline-day-toolbar.spec.ts # tests/e2e/timeline-scroll.spec.ts
There was a problem hiding this comment.
Pull request overview
Adds “time awareness” to the horizontal schedule Timeline: a Now pill for jumping to the current time and a current-time indicator on the strip, both rendered only while “now” is within the festival window, plus updates to mount-time centering precedence (now − ~1h).
Changes:
- Introduces a Now pill in the day-jump toolbar and a dashed current-time indicator on the timeline strip, driven by a new
useNowhook. - Extends mount-time centering precedence with a “now − 1h (clamped)” rule between day-filter and festival-start fallback, with added unit tests.
- Expands e2e coverage around scroll restoration and the new time-awareness behaviors (Now pill + indicator).
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/e2e/timeline-scroll.spec.ts | Adds e2e coverage for restoring timeline viewport after navigating to a set detail and going back. |
| tests/e2e/timeline-now-indicator.spec.ts | New e2e spec for Now pill + current-time indicator presence/absence and Now jump behavior under a fixed clock. |
| src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineToolbar.tsx | Adds showNowButton + onJumpToNow support and renders NowButton when enabled. |
| src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx | Computes gating for Now pill + indicator, renders CurrentTimeIndicator, and wires “jump to now”. |
| src/pages/EditionView/tabs/ScheduleTab/horizontal/Timeline.tsx | Computes unfiltered scheduleWindow, injects now, and plumbs both to the container. |
| src/pages/EditionView/tabs/ScheduleTab/horizontal/NowButton.tsx | New Now pill component used in the toolbar. |
| src/pages/EditionView/tabs/ScheduleTab/horizontal/CurrentTimeIndicator.tsx | New dashed vertical indicator component positioned by left offset. |
| src/lib/timelineMountMoment.ts | Adds the “now − 1h” mount-precedence rule and introduces isNowWithinFestivalWindow. |
| src/lib/timelineMountMoment.test.ts | Adds unit tests for now-rule precedence/clamping and window boundary checks. |
| src/lib/timelineCalculator.ts | Adds ScheduleWindow + calculateScheduleWindow for unfiltered festival-window gating. |
| src/lib/timelineCalculator.test.ts | Adds tests covering schedule window spanning logic and behavior under filtering. |
| src/hooks/useTimelineUrlState.ts | Uses useSearch selection + structural sharing to avoid recomputes from scrollTo writes. |
| src/hooks/useTimelineScrollSync.ts | Plumbs scheduleWindow + now into mount resolution while keeping URL→scroll mount-only. |
| src/hooks/useNow.ts | New hook ticking a fresh Date on a 60s interval to drive indicator movement. |
…ant structuralSharing option
…ar style Anchors it beside Show overview so it can't scroll out of view on mobile, and swaps the mismatched filled white pill for a ghost button with a pulsing dot, matching the toolbar's flat fuchsia/purple palette.
Matches the Now pill's precedent so an active stage/time filter can't hide the indicator mid-festival as a side effect. Addresses PR #204 review comment.
Per CLAUDE.md ("don't add comments unless really necessary") and PR review
feedback — strip JSDoc blocks from small hooks/components and cut verbose
inline comments down to one line where the context still needs stating.
- useTimelineUrlState.ts had drifted from main by dropping the select() narrowing, causing the over-subscription/scroll-jank risk flagged in review; restored to match main. - timelineCalculator.test.ts: build the calculateScheduleWindow fixture with the shared makeScheduleDay helper and move it below makeSet, instead of inlining ScheduleDay objects by hand.
Line comments directly above a function signature (calculateScheduleWindow, resolveTimelineMountMoment, isNowWithinFestivalWindow, useTimelineScrollSync) now use JSDoc block form instead of //.
main.tsx already sets defaultStructuralSharing: true globally.
Adds a Now pill in the day-jump toolbar and a dashed current-time indicator on the Timeline strip, both present only while now (festival timezone) falls inside the festival window. Mount precedence now includes a now−1h rule between the day filter and the festival-start fallback.
Closes #194. Stacked on #193 (day-jump toolbar) - base branch is
claude/188-193-day-jump-toolbar, notmain.Verification
supabase/seed.sql, "test"/"2025"): Timeline opens near now minus ~1h, the Now pill renders in the toolbar, and the dashed indicator sits at the right offset on the strip.scrollTois written to the URL and the strip smooth-scrolls to center on now; the viewport never auto-scrolls on its own as the indicator ticks.pnpm run lint,pnpm run typecheck, andpnpm vitest runall pass.src/lib/timelineMountMoment.test.ts(now-1h precedence rule,isNowWithinFestivalWindowboundaries). New e2e spec:tests/e2e/timeline-now-indicator.spec.ts(controlled clock viapage.clock.setFixedTime; not run locally, no local Supabase in this environment).Generated by Claude Code