Skip to content

Add calendar month/week views to EventsWidget#40

Merged
alecvdp merged 1 commit intomainfrom
cyrus/irl-28-calendar-monthweek-view-currently-only-a-linear-agenda
Apr 19, 2026
Merged

Add calendar month/week views to EventsWidget#40
alecvdp merged 1 commit intomainfrom
cyrus/irl-28-calendar-monthweek-view-currently-only-a-linear-agenda

Conversation

@alecvdp
Copy link
Copy Markdown
Owner

@alecvdp alecvdp commented Apr 19, 2026

Assignee: @alecvdp (alecvdpoel)

Summary

Adds Month and Week calendar views to the EventsWidget, complementing the existing Agenda view. Users can now answer "is this weekend full?" at a glance instead of scrolling a linear list.

What changed

  • View toggle — segmented control (Agenda / Week / Month) at the top of the Calendar & Agenda widget
  • Month view — hand-rolled CSS-grid calendar (7 columns, S–S headers) with:
    • Event/task indicator dots per day (orange for events, blue for tasks)
    • Today highlight with accent border
    • Click any day to expand an inline detail panel showing that day's items
    • Prev/next month navigation; click the month label to jump back to today
  • Week view — 7-day column layout with:
    • Item count badges per day
    • Same today highlight and click-to-expand behavior
    • Prev/next week navigation
  • No new dependencies — pure CSS grid, uses existing Herstel design tokens
  • Reuses existing data source — same combined agendaItems (events + incomplete tasks with due dates)

Files changed

File Change
CalendarMonthView.tsx + .module.css New — month grid component
CalendarWeekView.tsx + .module.css New — week grid component
EventsWidget.tsx View state, toggle, navigation, type export
EventsWidget.module.css View toggle + nav control styles

Testing

  • All 31 existing tests pass
  • TypeScript and ESLint clean
  • Visually verified in browser: agenda view unchanged, month view renders correctly with event dots, week view shows item counts, day selection expands inline detail, navigation works in both directions, today highlighting accurate

Resolves IRL-28


Tip: I will respond to comments that @ mention @cyrusagent on this PR. You can also submit a review with all your feedback at once, and I will automatically wake up to address each comment.


Open in Devin Review

Toggle between Agenda (existing), Week, and Month views using a
segmented control. Month view is a hand-rolled CSS-grid calendar
showing event/task dots per day. Week view shows 7-day columns with
item count badges. Both views support prev/next navigation and
click-to-expand day detail inline — no modal needed.

Reuses the existing combined event+task data source (agendaItems).
Styled with Herstel design tokens; no new dependencies.

Resolves IRL-28
Copilot AI review requested due to automatic review settings April 19, 2026 04:17
@linear
Copy link
Copy Markdown

linear Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +148 to +155
const navigateMonth = (delta: number) => {
setViewDate((prev) => {
const next = new Date(prev);
next.setMonth(next.getMonth() + delta);
return next;
});
setSelectedDate(null);
};
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.

🔴 navigateMonth skips months when viewDate day > 28 due to JS Date overflow

When viewDate has a day component of 29, 30, or 31, navigateMonth uses setMonth() which causes JavaScript's Date to overflow into the next month. For example, if viewDate is May 31 and the user navigates forward, setMonth(5) creates "June 31" which JS rolls to July 1 — skipping June entirely.

This is easily triggered because viewDate is shared across week and month views. A user in week view can navigate until viewDate lands on the 31st (e.g., May 31), switch to month view, then navigate forward — the calendar jumps two months. Backward navigation has the same problem (e.g., from March 31 → "February 31" → March 3).

Concrete reproduction steps
  1. Open widget on any date (e.g. April 19)
  2. Switch to Week view
  3. Navigate forward ~2 weeks so viewDate becomes e.g. May 3, then keep going to May 31
  4. Switch to Month view (shows May)
  5. Click forward arrow → expects June, lands on July
Suggested change
const navigateMonth = (delta: number) => {
setViewDate((prev) => {
const next = new Date(prev);
next.setMonth(next.getMonth() + delta);
return next;
});
setSelectedDate(null);
};
const navigateMonth = (delta: number) => {
setViewDate((prev) => {
const next = new Date(prev);
next.setDate(1);
next.setMonth(next.getMonth() + delta);
return next;
});
setSelectedDate(null);
};
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds week/month calendar visualizations to the existing EventsWidget so users can scan upcoming events/tasks by day, while keeping the existing agenda list view intact.

Changes:

  • Added a segmented view toggle (Agenda / Week / Month) and navigation controls to EventsWidget.
  • Introduced new CalendarMonthView and CalendarWeekView components (CSS-grid based) with day selection and inline detail panels.
  • Added new styling for the toggle/navigation and both calendar views.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/components/EventsWidget.tsx Adds view state, toggle UI, and week/month navigation wiring.
src/components/EventsWidget.module.css Styles for the view toggle and navigation controls.
src/components/CalendarMonthView.tsx New month grid view with item dots and day detail panel.
src/components/CalendarMonthView.module.css Styling for month grid, day states, and detail panel.
src/components/CalendarWeekView.tsx New week grid view with per-day item counts and day detail panel.
src/components/CalendarWeekView.module.css Styling for week grid columns, today/selected states, and detail panel.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +3
import { useMemo } from "react";
import styles from "./CalendarMonthView.module.css";
import type { AgendaItem } from "./EventsWidget";
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

CalendarMonthView uses React hooks (useMemo) but the file is missing a "use client" directive. In Next.js App Router this will be treated as a Server Component and cannot be imported/used by the client-side EventsWidget, causing a build/runtime error. Add "use client" at the top of this file (before imports).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
import { useMemo } from "react";
import styles from "./CalendarWeekView.module.css";
import type { AgendaItem } from "./EventsWidget";
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

CalendarWeekView uses React hooks (useMemo) but the file is missing a "use client" directive. In Next.js App Router this will be treated as a Server Component and cannot be imported/used by the client-side EventsWidget, causing a build/runtime error. Add "use client" at the top of this file (before imports).

Copilot uses AI. Check for mistakes.

const navigateMonth = (delta: number) => {
setViewDate((prev) => {
const next = new Date(prev);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

navigateMonth mutates the month via Date#setMonth while keeping the current day-of-month. When viewDate is on the 29th/30th/31st, navigating to a shorter month can overflow into the following month (e.g., Jan 31 + 1 month -> Mar 3), causing the month view to skip/land on the wrong month. Consider normalizing viewDate to a safe day (e.g., setDate(1)) before shifting months, or store an explicit (year, month) anchor for month navigation.

Suggested change
const next = new Date(prev);
const next = new Date(prev);
next.setDate(1);

Copilot uses AI. Check for mistakes.
Comment on lines +203 to +241
<button
key={key}
type="button"
className={`${styles.viewBtn} ${view === key ? styles.viewBtnActive : ""}`}
onClick={() => {
setView(key);
setSelectedDate(null);
}}
title={label}
>
<Icon size={14} />
</button>
))}
</div>

{view !== "agenda" && (
<div className={styles.navControls}>
<button
type="button"
className={`btn-icon ${styles.navBtn}`}
onClick={() => (view === "month" ? navigateMonth(-1) : navigateWeek(-1))}
>
<ChevronLeft size={14} />
</button>
<button
type="button"
className={styles.navLabel}
onClick={goToToday}
title="Go to today"
>
{getNavLabel()}
</button>
<button
type="button"
className={`btn-icon ${styles.navBtn}`}
onClick={() => (view === "month" ? navigateMonth(1) : navigateWeek(1))}
>
<ChevronRight size={14} />
</button>
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

The view toggle and month/week navigation use icon-only elements without an accessible name. Relying on title (or having no label at all on the chevrons) is not reliably announced by screen readers. Add an aria-label (or visually hidden text) for each button (e.g., "Agenda view", "Next month", "Previous week").

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +65
function sameDay(a: Date, b: Date): boolean {
return (
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate()
);
}
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

sameDay is now duplicated in EventsWidget, CalendarMonthView, and CalendarWeekView. To reduce drift/inconsistency, consider extracting a shared helper (e.g., in src/lib/dates.ts or a small local util) and reusing it across the three components.

Copilot uses AI. Check for mistakes.
@alecvdp alecvdp merged commit 8d5ece3 into main Apr 19, 2026
7 checks passed
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.

2 participants