Skip to content

feat(timeline): add now pill and time indicator#204

Merged
chiptus merged 35 commits into
mainfrom
claude/188-194-now-indicator
Jul 20, 2026
Merged

feat(timeline): add now pill and time indicator#204
chiptus merged 35 commits into
mainfrom
claude/188-194-now-indicator

Conversation

@chiptus

@chiptus chiptus commented Jul 14, 2026

Copy link
Copy Markdown
Owner

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, not main.

Verification

  • With now inside the seeded edition's window (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.
  • Tap the Now pill: scrollTo is 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.
  • With now outside the window (before or after), neither the Now pill nor the indicator render (absent, not disabled).
  • pnpm run lint, pnpm run typecheck, and pnpm vitest run all pass.
  • New/updated unit tests: src/lib/timelineMountMoment.test.ts (now-1h precedence rule, isNowWithinFestivalWindow boundaries). New e2e spec: tests/e2e/timeline-now-indicator.spec.ts (controlled clock via page.clock.setFixedTime; not run locally, no local Supabase in this environment).

Generated by Claude Code

claude added 9 commits July 14, 2026 18:20
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
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
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-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
upline Ready Ready Preview, Comment Jul 20, 2026 8:24pm

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.
claude added 5 commits July 15, 2026 19:33
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
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
claude added 7 commits July 16, 2026 14:17
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
@chiptus
chiptus force-pushed the claude/188-193-day-jump-toolbar branch from 4cfe485 to ff8ecf0 Compare July 19, 2026 05:17
Base automatically changed from claude/188-193-day-jump-toolbar to main July 19, 2026 20:22
…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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 useNow hook.
  • 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.

Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx Outdated
…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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread src/hooks/useTimelineUrlState.ts
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.
Comment thread src/hooks/useTimelineUrlState.ts
Comment thread src/lib/timelineCalculator.test.ts Outdated
Comment thread src/lib/timelineCalculator.ts Outdated
Comment thread src/lib/timelineMountMoment.ts Outdated
Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/Timeline.tsx Outdated
Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx Outdated
Comment thread tests/e2e/timeline-now-indicator.spec.ts
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 //.
Comment thread src/hooks/useTimelineScrollSync.ts Outdated
main.tsx already sets defaultStructuralSharing: true globally.
@chiptus
chiptus merged commit c024d37 into main Jul 20, 2026
5 of 9 checks passed
@chiptus
chiptus deleted the claude/188-194-now-indicator branch July 20, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: "Now" pill and current-time indicator during the festival

3 participants