Expected behavior for displaying events across calendar views, including recurring event expansion and progressive loading.
Eventky renders events in multiple views (month, week, agenda) with smart handling of recurring events, date ranges, and performance optimization through progressive loading.
- Displays events within the visible month
- Shows all-day events at the top
- Time-based events in grid cells
- No progressive loading (single month scope)
- Displays events for the current week (Sunday-Saturday)
- Hour-by-hour timeline layout
- Shows event duration visually
- No progressive loading (single week scope)
- Initial Range: 365 days (1 year) from current date
- Progressive Loading: Load More button adds 1 additional year
- Maximum Range: 3 years total
- Default Filter: On
/eventspage without explicit filters, shows upcoming events only (today + 365 days)
- Date Range: Events generated up to 1 year (365 days) ahead from view start date
- Occurrence Limit: Maximum 1000 occurrences per recurring event
- Whichever comes first: Generation stops at 1 year OR 1000 occurrences
Events without COUNT or UNTIL are marked as indefinite:
- Symbol: Infinity symbol (∞) displayed in UI
- Tooltip: "Event repeats indefinitely" explanation
- Locations:
- Recurrence rule labels (e.g., "Weekly on Mon, Wed, Fri ∞")
- Event creation preview
- Occurrence selectors
- Initial Display: 50 occurrences
- Progressive Loading: Load More button adds 50 more occurrences at a time
- Maximum Display: 1000 occurrences (1 year limit)
- Current Instance: Selected instance highlighted and scrolled into view
- Navigation: Previous/Next buttons for moving between occurrences
- Occurrences as Separate Events: Each occurrence rendered independently
- Date-Aware Filtering: Only occurrences within view's date range displayed
- Instance Selection: Clicking occurrence navigates to detail page with
?instance=query param
- Color-Coded Occurrence Chips:
- Green border: User accepted this instance
- Red border: User declined this instance
- Yellow border: User is tentative for this instance
- Default (blue): No response or needs action
- RSVP without
recurrence_idapplies to all instances - Instance-specific RSVPs override general RSVP for that date
- User can accept general event but decline specific instances
view: Calendar view mode (month|week|agenda)date: Currently displayed date (ISO formatyyyy-MM-dd)instance: For recurring events, specific occurrence date (ISO datetime)
- View mode and date changes update URL
- Back/forward navigation works correctly
- Shareable URLs for specific dates/instances
/event/:authorId/:eventId- Event detail (first/next occurrence for recurring)/event/:authorId/:eventId?instance=2025-06-15T10:00:00Z- Specific occurrence
Events respect timezone information throughout the system:
- Event Storage: Times stored in event's original timezone (
dtstart+dtstart_tzid) - Display: Times shown in user's local timezone by default
- Recurring Events: Wall-clock time preserved across DST transitions
- Calendar Views: Events grouped by date in their event timezone
- A 10:00 AM meeting in New York shows at correct local time for all viewers
- DST transitions don't shift event times
- Events appear on the correct day regardless of viewer's timezone
For complete timezone documentation including DST handling, edge cases, and 419 IANA timezone support, see TIMEZONE_HANDLING.md
- Recurring events returned by backend regardless of date range
- Frontend filters occurrences to match view's date range
- Prevents duplicate events from backend
- Occurrences generated on-demand per view
- Memoized to prevent recalculation on re-renders
- Limited to visible date range + buffer
- Manual Load More buttons (no infinite scroll)
- User-controlled data fetching
- Clear indication of total range displayed
- Event cards: "Mon, Jan 15, 2025"
- Agenda headers: "Monday, January 15, 2025"
- Month view: Day number only
- Occurrence chips: "Mon, Jan 15, 2025 at 10:00"
- All times displayed in 24-hour format
- Consistent across all views
- Human-readable RRULE parsing
- Examples:
FREQ=DAILY→ "Daily"FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR→ "Every 2 weeks on Mon, Wed, Fri"FREQ=MONTHLY;BYMONTHDAY=21→ "Monthly on day 21"FREQ=YEARLY;COUNT=5→ "Yearly (5 times)"FREQ=WEEKLY(no COUNT/UNTIL) → "Weekly ∞"
hooks/use-calendar-view.ts- Date range calculation, occurrence expansion, calendar filteringlib/datetime/rrule-display.ts- RRULE parsing, indefinite recurrence detection, label formattingcomponents/event/detail/datetime-recurrence.tsx- Occurrence selector with progressive loadingcomponents/calendar/calendar-view/calendar-agenda-view.tsx- Agenda view with Load Moreapp/events/page.tsx- Default 365-day filter for agenda view
RRULE: FREQ=WEEKLY;BYDAY=TU,TH
Label: "Weekly on Tue, Thu ∞"
Display: Shows 52 occurrences (1 year), Load More available
Tooltip: "Event repeats indefinitely"
RRULE: FREQ=MONTHLY;BYMONTHDAY=15;UNTIL=20251231
Label: "Monthly on day 15 until Dec 31, 2025"
Display: Shows 12 occurrences, no Load More (end reached)
Tooltip: None (finite recurrence)
RRULE: FREQ=YEARLY;COUNT=3
Label: "Yearly (3 times)"
Display: Shows 3 occurrences total
Tooltip: None (finite recurrence)
General RSVP: Accepted Instance-specific: Declined 2nd occurrence (June 15) Display:
- First occurrence: Green border (accepted)
- June 15 occurrence: Red border (declined, overrides general)
- Other occurrences: Green border (general acceptance applies)