Skip to content

feat(timeline): add overview mini-map#203

Merged
chiptus merged 16 commits into
mainfrom
claude/188-197-overview-minimap
Jul 20, 2026
Merged

feat(timeline): add overview mini-map#203
chiptus merged 16 commits into
mainfrom
claude/188-197-overview-minimap

Conversation

@chiptus

@chiptus chiptus commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Adds a collapsible "Show overview" toggle in the timeline toolbar revealing a proportional mini-map of the filtered edition, with vote-colored set density and a draggable viewport window.
Clicking the map jumps the strip (smooth scroll); dragging the viewport window scrubs it directly.

Closes #197

Verification

  • Toggle "Show overview" on the timeline tab; mini-map appears collapsed by default, expands/collapses on click.
  • Click anywhere on the mini-map; strip smooth-scrolls there and scrollTo updates in the URL.
  • Drag the viewport window left/right; strip scrubs live, then settles into a debounced scrollTo write.
  • Apply a day filter (?day=2025-07-12); mini-map shows only that day's sets and density.
  • Log out and revisit; mini-map still renders with neutral (unvoted) set colors.

Generated by Claude Code

@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 7:13pm

Adds a collapsible "Show overview" toggle in the timeline toolbar
revealing a proportional mini-map of the filtered edition, with
vote-colored set density and a draggable viewport window. Clicking
the map jumps the strip (smooth scroll); dragging the viewport
window scrubs it directly.

Rebased onto main, which now already contains the day-jump-toolbar
and scroll-url-state work this branch was previously stacked on.
@chiptus
chiptus force-pushed the claude/188-197-overview-minimap branch from 97f8123 to 06d3581 Compare July 19, 2026 20:27
Copilot AI review requested due to automatic review settings July 19, 2026 20:27

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 a collapsible “Show overview” mini-map to the horizontal schedule timeline, enabling proportional edition-wide navigation (click-to-jump) and scrubbing (drag viewport) while keeping navigation state in the existing scrollTo URL sync.

Changes:

  • Add an overview mini-map UI (per-stage density rows, day-boundary lines, draggable viewport window) gated by a toolbar toggle.
  • Centralize “jump to moment” behavior in useTimelineScrollSync().jumpTo, used by both day-jump buttons and the overview map.
  • Add/adjust unit + e2e coverage for overview geometry and interactions; refactor day label helpers.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
tests/e2e/timeline-overview.spec.ts Adds e2e coverage for overview toggle/click/drag/day-filter behavior.
tests/e2e/timeline-day-toolbar.spec.ts Updates day-jump toolbar e2e assertions to match new DOM/roles.
src/test/setup.ts Test environment setup adjustments (timezone pin removed in diff).
src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineToolbar.tsx Adds “Show overview” toggle button to toolbar.
src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineOverview.tsx Implements overview mini-map rendering + click-to-jump.
src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineContainer.tsx Wires overview expansion state + integrates jumpTo.
src/pages/EditionView/tabs/ScheduleTab/horizontal/Timeline.tsx Rehomes StageLabels to keep it pinned outside the scroller.
src/pages/EditionView/tabs/ScheduleTab/horizontal/StageLabels.tsx Adjusts pinned label positioning within the new container layout.
src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx Adds draggable viewport window for scrubbing the timeline.
src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewStageRow.tsx Renders per-stage density blocks with vote-based coloring.
src/pages/EditionView/tabs/ScheduleTab/horizontal/DayJumpButtons.tsx Simplifies day buttons and updates labels to short weekday+date.
src/main.tsx Removes router-level defaultStructuralSharing setting.
src/lib/timeUtils.ts Replaces day-label parts helper with getFestivalDayShortLabel.
src/lib/timeUtils.test.ts Updates tests for new short label helper and removes adjacency tests.
src/lib/timelineOverviewGeometry.ts Adds shared geometry helpers for overview proportions and viewport.
src/lib/timelineOverviewGeometry.test.ts Adds unit tests for overview geometry calculations.
src/lib/timelineDayJump.ts Simplifies day-jump moment selection to “earliest set start”.
src/lib/timelineDayJump.test.ts Updates tests to avoid removed fixtures and match new logic.
src/lib/timelineCalculator.test.ts Removes shared fixtures and inlines test data builders.
src/hooks/useTimelineUrlState.ts Enables structural sharing to reduce recomputation on scrollTo writes.
src/hooks/useTimelineScrollSync.ts Adds jumpTo API and enables structural sharing in useSearch.
src/hooks/useActiveTimelineDay.ts Removes active-day tracking hook (no longer used).
src/tests/fixtures.ts Removes generic schedule/stage fixture helpers.
CLAUDE.md Updates agent workflow instruction wording.

Comment thread src/test/setup.ts
Comment thread tests/e2e/timeline-day-toolbar.spec.ts Outdated
Comment thread tests/e2e/timeline-day-toolbar.spec.ts Outdated
Comment thread tests/e2e/timeline-day-toolbar.spec.ts Outdated
Comment thread tests/e2e/timeline-day-toolbar.spec.ts Outdated
Comment thread tests/e2e/timeline-overview.spec.ts Outdated
Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineOverview.tsx Outdated
Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx Outdated
- Restore the TZ=UTC test pin dropped during the rebase; the
  UTC-fallback test in timeUtils depended on it and was silently
  passing only because this sandbox's host clock is already UTC.
- Replace the racy immediate isVisible() check in the day-toolbar and
  overview e2e specs with a waitFor-based skip, so a still-loading
  schedule doesn't get misread as "not revealed" and skipped green.
- Add aria-expanded to the overview toggle button.
- Throttle the overview's scroll/resize sync to one state update per
  animation frame instead of one per native scroll event.
- Handle pointercancel the same as pointerup when dragging the
  viewport window, so an interrupted gesture can't leave window
  listeners and drag state stuck.
- Give the viewport window real slider keyboard semantics
  (tabIndex, aria-valuemin/max, Arrow/Home/End) instead of a
  role="slider" that wasn't actually operable via keyboard.

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 24 out of 24 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (3)

src/test/setup.ts:3

  • Removing the TZ pin makes date-fns fallbacks depend on the machine timezone (e.g. getFestivalDayKey() uses format(date) when no timezone is passed, and timeUtils.test asserts a UTC fallback). This will make unit tests fail or become non-deterministic on non-UTC machines.
import "@testing-library/jest-dom/vitest";
import { vi } from "vitest";

tests/e2e/timeline-day-toolbar.spec.ts:23

  • This locator matches the new "Show/Hide overview" toggle as well as the day-jump buttons, so label assertions/clicks will be wrong (e.g. "Show overview" doesn't match the weekday/date regex). Scope the locator to the day-jump buttons’ data-testid prefix instead.

test.describe("Timeline day-jump toolbar", () => {
  test("renders one sticky button per festival day, labeled weekday + date", async ({
    page,
  }) => {

src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx:46

  • The viewport window declares role="slider" but isn’t focusable and has no keyboard interactions, which makes it inaccessible (and the ARIA semantics incorrect). Either remove the slider role or make it a real slider by adding tabIndex, aria-valuemin/max, and ArrowLeft/ArrowRight handling.
      data-testid="timeline-overview-viewport"
      role="slider"
      tabIndex={0}
      aria-label="Visible timeline range"
      aria-valuemin={0}

Comment thread tests/e2e/timeline-day-toolbar.spec.ts Outdated
Comment thread tests/e2e/timeline-day-toolbar.spec.ts
Comment thread tests/e2e/timeline-day-toolbar.spec.ts Outdated
Comment thread tests/e2e/timeline-day-toolbar.spec.ts Outdated
The day-jump-toolbar feature (#202) was substantially reworked after this
branch's own commits stopped: active-day tracking (useActiveTimelineDay),
radiogroup/radio a11y semantics, a "modal opening" day-jump target instead
of the global-earliest set, a restyled weekday/date rail, a repositioned
StageLabels column, and global router structuralSharing. This branch's
diff-based rebase preserved this branch's own (now-stale) versions of those
files, which would have regressed all of the above on merge.

Restore every file this PR doesn't need to touch to main's exact content,
and re-wire only the overview mini-map on top of it:
- TimelineToolbar now nests the day radiogroup and appends the overview
  toggle as a sibling, instead of replacing the toolbar's semantics.
- TimelineContainer keeps main's useActiveTimelineDay + StageLabels wiring
  and drives both the day buttons (start-aligned) and the overview map
  (center-aligned) through the same jumpToTimelineMoment call.
- Dropped the now-unnecessary useTimelineScrollSync.jumpTo/structuralSharing
  additions and the standalone getFestivalDayShortLabel helper.
- e2e specs for both the day toolbar and the overview map now match main's
  "fail loudly instead of silently skipping" convention.
- Pin the overview toggle outside the day rail's overflow-x-auto scroller
  (only the radiogroup scrolls now) so it stays visible on festivals with
  enough days to overflow the toolbar, instead of scrolling out of reach.
- Restyle it as a ghost button with a divider, icon-first and collapsing to
  icon-only below sm, matching the rail's minimal look instead of standing
  out as an outlined pill.
- Cap the mini-map's height with vertical scroll (max-h-36 on mobile,
  max-h-48 from sm) so festivals with many stages don't blow up the map's
  footprint, especially on small screens.
Revert the max-height/overflow-y-auto added for mobile sizing; the map
should always render in full rather than clip and scroll.
Cap the map at a fixed 64px height with per-stage row height derived
from stage count, instead of stacked h-2 rows that grew unbounded with
more stages (the actual cause of the oversized mobile map). Draw each
day's weekday/day-of-month label inside its boundary line, matching
the compact look from the #121 prototype.
Match the #121 prototype: unvoted sets in the mini-map now use each
stage's own color at low opacity instead of a flat neutral tone, so
the map reads as colorful before any voting happens. Voted sets still
override with the vote color at full opacity.
The day-jump row can overflow on mobile with no indication there's
more to scroll to, and the active day could end up scrolled out of
view entirely. Auto-scroll the active day pill into view (centered)
whenever it changes, and mask-fade whichever edge still has more
days to scroll toward.

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.

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Address remaining Copilot review comment on PR #203: the outer div
grouping the day radiogroup and overview toggle had no role/label of
its own, so assistive tech couldn't discover it as a unit.
Comment thread src/lib/timelineOverviewGeometry.test.ts
…iant

The existing tests check each geometry function in isolation with
hand-picked numbers that already assume they agree on one offset<->percent
scale. Add two tests that compose the functions instead: a set starting
exactly at a day boundary must land at the same leftPercent as that
boundary, and a clicked fraction must round-trip through offset/time
conversions back to the same percent.
calculateOverviewSetBlocks, calculateDayBoundaries,
calculateOverviewViewport, and fractionToOffset now take a single
named-fields object instead of positional args, so call sites read
without needing to check the function signature (especially
calculateDayBoundaries, which had four same-shaped positional params).
offsetToPercent stays positional — it's a private two-arg helper only
called internally within this module.
Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx Outdated
Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineOverview.tsx Outdated
The overview viewport window already has role="slider" +
aria-label="Visible timeline range", which uniquely identifies it -
the data-testid was redundant. Point the e2e drag test at the a11y
role/name instead.
Both TimelineOverview and TimelineToolbar buried a rAF-throttled
scroll/resize-tracking useEffect in their component body, making it
hard to tell what the component does without reading the whole
effect. Pull each into its own hook named for what it returns:
useTimelineViewportSize (the overview's viewport window) and
useScrollEdgeFade (the day-row's overflow fade) - so the component
body reads as a short list of "what this does" instead of inline
implementation.

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 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (5)

src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineOverview.tsx:137

  • If rowHeight is computed as a fractional/very small value (e.g. on festivals with many stages), height={rowHeight - 1} can become 0 or negative, which results in invalid layout (and rows disappearing). Use rowHeight directly (or clamp) for the row height.
    src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx:1
  • This component adds global window pointer event listeners but doesn't guarantee cleanup if the overview unmounts mid-drag (e.g. user collapses the overview while dragging). That can leave stale listeners running and mutating scrollLeft after unmount. Consider using an AbortController (and aborting on unmount) to ensure listeners are always cleaned up.
import { useRef } from "react";

src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx:38

  • Track an AbortController for the active drag and abort it on unmount to ensure any in-flight global listeners are removed even if the component is collapsed/unmounted mid-drag.
  const dragStateRef = useRef<DragState | null>(null);

src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx:66

  • Use the per-drag AbortController signal when registering global listeners so they can be torn down reliably (via abort on pointer-up/cancel or unmount).
    dragStateRef.current = {
      startClientX: event.clientX,
      startScrollLeft: container.scrollLeft,
      mapWidthPx: map.getBoundingClientRect().width,
    };

src/pages/EditionView/tabs/ScheduleTab/horizontal/OverviewViewportWindow.tsx:94

  • handlePointerUp can rely on aborting the active drag's AbortController to remove all listeners that were registered with its signal (and clear refs). This avoids leaving listeners attached if the drag ends via cancel/unmount.
    dragStateRef.current = null;
    window.removeEventListener("pointermove", handlePointerMove);
    window.removeEventListener("pointerup", handlePointerUp);
    window.removeEventListener("pointercancel", handlePointerUp);
  }

Comment thread src/pages/EditionView/tabs/ScheduleTab/horizontal/TimelineOverview.tsx Outdated
useTimelineViewportSize and useScrollEdgeFade are only used by
TimelineOverview and TimelineToolbar respectively - move them out of
the general src/hooks/ folder and into the same directory as their
components.
Per the CSS overflow spec, setting overflow-x to a value other than
visible while overflow-y stays visible forces overflow-y to compute
as auto too - so any sub-pixel vertical overflow in the day-jump row
(focus rings, the active-day underline) triggered a real vertical
scrollbar right at its edge, next to the overview toggle. Pair
overflow-x-auto with overflow-y-hidden, same as the main timeline
scroll container already does.
Math.max(3, ...) kept rowHeight pinned at 3px once a festival had 15+
stages, so 3px * stageCount exceeded the map's available height and
the last few stage rows were pushed past the overflow-hidden bound -
silently invisible rather than just thinner. Compute rowHeight as a
plain fraction of the available height so every stage always fits,
even if rows get sub-pixel thin at high stage counts.
@chiptus
chiptus merged commit 40adeac into main Jul 20, 2026
10 checks passed
@chiptus
chiptus deleted the claude/188-197-overview-minimap branch July 20, 2026 19:23
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: overview mini-map — see the whole edition, jump anywhere

3 participants