Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added an Agenda view to the calendar - a chronological, date-grouped list of upcoming events and scheduled tasks, selectable alongside Day/Week/Month/Year (#88)
- Create a multi-day all-day event by dragging the mouse across multiple days on the all-day row, like Google Calendar; the New Event modal opens pre-filled as an all-day event spanning the selected days (#79)
- Added `.github/copilot-instructions.md` with repository-wide guidance for GitHub Copilot's coding agent (setup, commands, architecture, SAAS/open-source separation, code-style conventions)
- Show the application version in a footer on every page, linking to the project's GitHub page (#111)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-22
52 changes: 52 additions & 0 deletions openspec/changes/archive/2026-06-22-issue-88-agenda-view/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Context

The calendar header (`src/components/calendar/Calendar.tsx`) switches between view components (`DayView`, `WeekView`, `MonthView`, `MultiMonthView`) based on the `view` value from `useViewStore` (`src/store/calendar.ts`). Each view component is a thin wrapper around a `FullCalendar` instance that:
- reads merged events+tasks via `useCalendarStore().getAllCalendarItems(start, end)`,
- filters to enabled feeds (and always-included `feedId === "tasks"`),
- formats each item into FullCalendar's event shape (color from feed/task, `classNames`, `extendedProps`),
- opens an `EventQuickView` popover on click.

The `CalendarView` type already includes `"agenda"` (`src/types/calendar.ts:83`), but `Calendar.tsx` only handles `day`/`week`/`month` and defaults everything else (including `agenda`) to `MultiMonthView`.

## Goals / Non-Goals

Goals:
- A selectable Agenda view that lists upcoming events + scheduled tasks chronologically, grouped by day, reusing existing data and filtering.
- Match existing conventions: time format from user settings, feed-enabled filtering, click → quick view, week-start day.

Non-Goals (kept out to limit blast radius; the issue/image do not require them):
- No inline "Add task" button or "Refresh all tasks" footer from the Motion screenshot (those are separate features; the issue's core ask is the agenda list itself).
- No drag/drop or resize in the agenda (a list view is not a drag surface; FullCalendar's list view is read-oriented).
- No new persisted setting; reuse the existing localStorage-backed `view` state.

## Decisions

### Decision: Use FullCalendar `listPlugin` (`listWeek`)
FullCalendar already powers every other view and ships an official `@fullcalendar/list` plugin that renders exactly this UI (a date-grouped list with a "no events" empty state). Using it keeps the new view consistent with the others (same event objects, same `eventClick`, same time formatting) and avoids hand-rolling a list renderer. We pin it to the same `6.1.x` version as the other `@fullcalendar/*` packages already in `package.json`.

- `initialView="listWeek"` so the agenda shows a rolling week, consistent with the Week view and with the header's week-based prev/next stepping.
- `listDayFormat` / `listDaySideFormat` and `eventTimeFormat` honor the user's 12h/24h `timeFormat` setting.
- `noEventsContent` provides the empty-state message.

### Decision: Extract a pure formatting helper for testability
FullCalendar renders in a browser DOM and is awkward to unit-test in the Node/jsdom Jest env used here. To get real TDD coverage of the load-bearing logic, the item-shaping + feed-filtering logic lives in a pure function `formatAgendaItems(items, feeds)` in `src/lib/calendar-agenda.ts`, mirroring the `handleDatesSet` mapping the other views do inline. `AgendaView` calls this helper, and the unit tests assert on its output (filtering disabled feeds, always including tasks, color resolution, `extendedProps`, sort order). This is the same testing seam used by `src/lib/calendar-selection.ts` (issue #79).

### Decision: Week-step navigation for the agenda
The header's `handlePrevWeek`/`handleNextWeek` step by month for `month`/`multiMonth` and by day/week otherwise. The agenda spans a week (`listWeek`), so it falls into the existing "else" (7-day) branch automatically - we confirm `agenda` is not routed into the month-stepping branch. No new navigation code needed beyond ensuring the branch condition stays correct.

### Decision: Open-source, no SAAS gating
This is a core UI affordance with no premium dimension, so it ships in the shared (non-`.saas`/`.open`) component path like the other views.

## Risks / Trade-offs

- **New dependency**: adds `@fullcalendar/list`. Mitigation: it is a first-party FullCalendar package at the same version as packages already present; minimal supply-chain risk and no transitive bloat beyond the FullCalendar core already installed.
- **List view is read-oriented**: no drag/drop/resize. Acceptable - the feature request is a read/scan view; editing remains available by clicking an item (quick view → edit), consistent with how a click works in other views.
- **Quick-view positioning**: the agenda reuses `EventQuickView` anchored to the clicked element, exactly as the other views do, so behavior is consistent.

## Migration Plan

None. Additive UI only - no data migration, no schema change. Existing persisted `view` values are unaffected; a user whose stored view was already `"agenda"` (previously rendering the Year view) will now correctly see the agenda.

## Open Questions

None blocking. The Motion screenshot's extra chrome ("Add task", "Refresh all tasks", per-task checkboxes) is intentionally out of scope for this change and can be layered on later as separate enhancements.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Why

FluidCalendar offers Day, Week, Month, and Year views, but no way to see what is coming up as a single chronological list (issue #88). The requested "Agenda View" (see the Motion reference image in the issue) is a date-grouped, scrollable list of upcoming events and scheduled tasks - useful for quickly scanning the day/week ahead without scanning a time grid. The `"agenda"` value is already declared in the `CalendarView` type union but no view renders it; selecting it today falls through to the Year view.

## What Changes

- Add an `AgendaView` calendar view that renders calendar events and scheduled tasks as a chronological, date-grouped list using FullCalendar's `listPlugin` (`listWeek`), matching the data, feed-filtering, click-to-quick-view, and time-format behavior of the existing Day/Week/Month views.
- Add an **Agenda** button to the calendar header view switcher, alongside Day / Week / Month / Year.
- Wire the existing `"agenda"` `CalendarView` value through `Calendar.tsx` so it renders `AgendaView` instead of falling through to the Year (`multiMonth`) view.
- Make the header prev/next navigation step by a week when the Agenda view is active (the agenda spans a week, like the Week view).
- Show an empty-state message when the agenda range has no events or tasks.

## Capabilities

### New Capabilities
- `calendar-agenda-view`: A chronological, date-grouped list view of upcoming calendar events and scheduled tasks, selectable from the calendar header.

### Modified Capabilities
<!-- None: no existing spec covers the calendar view switcher or per-view rendering. -->

## Impact

- Code: new `src/components/calendar/AgendaView.tsx`; `src/components/calendar/Calendar.tsx` (add the Agenda button + render branch + week-stepping for agenda); `package.json` (add `@fullcalendar/list` at the same 6.1.x version as the other FullCalendar packages); `CHANGELOG.md`.
- New helper `src/lib/calendar-agenda.ts` (with unit tests) for the pure formatting/filtering logic the view uses, so the behavior is testable without rendering FullCalendar.
- No API, schema, store, or SAAS-gating changes. The view reuses the existing `getAllCalendarItems()` data source and feed-enabled filtering. Open-source feature (no premium gating).
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## ADDED Requirements

### Requirement: Agenda view lists upcoming events and tasks chronologically

The calendar SHALL provide an "Agenda" view that displays calendar events and scheduled tasks as a chronological, date-grouped list for a rolling one-week range. The Agenda view SHALL be selectable from the calendar header view switcher alongside Day, Week, Month, and Year, and selecting it SHALL render the agenda list (not the Year view). The list SHALL include the same items the other views show for the active range: events from enabled feeds plus scheduled tasks, with each task or event opening its quick view on click.

#### Scenario: Selecting the Agenda view
- **WHEN** the user clicks the "Agenda" button in the calendar header
- **THEN** the calendar content area shows a chronological, date-grouped list of events and scheduled tasks for the active week (not the Year/multi-month grid)

#### Scenario: Agenda shows enabled-feed events and tasks
- **WHEN** the Agenda view is active for a range that contains events from an enabled feed and one or more scheduled tasks
- **THEN** those events and tasks appear in the list, sorted by start time, each labeled with its time and colored by its feed or task color

#### Scenario: Disabled feeds are excluded
- **WHEN** a feed is disabled
- **THEN** that feed's events do NOT appear in the Agenda list, while scheduled tasks (feed id `tasks`) continue to appear

#### Scenario: Empty range shows an empty state
- **WHEN** the Agenda view is active for a range with no events and no scheduled tasks
- **THEN** the list shows an empty-state message rather than an empty/blank area

#### Scenario: Time format follows user settings
- **WHEN** the user's time-format setting is 12-hour (or 24-hour)
- **THEN** the times shown in the Agenda list are rendered in that format

#### Scenario: Navigation steps by week
- **WHEN** the Agenda view is active and the user clicks the header next/previous navigation
- **THEN** the agenda range advances or retreats by one week (consistent with the Week view), not by a month

#### Scenario: Deleting a recurring occurrence does not delete the whole series
- **WHEN** the user deletes a recurring event occurrence from the Agenda quick view that is an expanded instance (not the recurring master)
- **THEN** only that occurrence is deleted (delete mode `single`), and the rest of the series is left intact; only deleting the recurring master deletes the series

#### Scenario: The global "new event" command works while the Agenda view is active
- **WHEN** the user triggers the calendar create-event command (or shortcut) while the Agenda view is active
- **THEN** the New Event modal opens (the Agenda view honors the shared event-modal store, like the other calendar views), rather than the command silently doing nothing
22 changes: 22 additions & 0 deletions openspec/changes/archive/2026-06-22-issue-88-agenda-view/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## 1. Pure agenda-formatting helper (TDD)

- [x] 1.1 Write failing unit tests in `src/__tests__/calendar-agenda.test.ts` for `formatAgendaItems(items, feeds)` covering: includes items whose `feedId === "tasks"` even when no matching feed; excludes items whose feed is disabled or missing; includes items whose feed is enabled; resolves background/border color from task color (tasks) or feed color (events) with sensible fallbacks; preserves `extendedProps` (isTask, status, priority, isRecurring); tags task/event classNames; sorts the result by start time ascending
- [x] 1.2 Implement `src/lib/calendar-agenda.ts` with `formatAgendaItems` to make the tests pass (mirroring the existing `handleDatesSet` mapping used by the other views)

## 2. AgendaView component

- [x] 2.1 Add `@fullcalendar/list` to `package.json` at the same `6.1.x` version as the other `@fullcalendar/*` packages
- [x] 2.2 Create `src/components/calendar/AgendaView.tsx` using `listPlugin` (`initialView="listWeek"`), reusing `getAllCalendarItems` + `formatAgendaItems`, feed/task data loading, `EventQuickView` on click, `noEventsContent` empty state, and time formats from user settings (mirroring `DayView.tsx` patterns)
- [x] 2.3 Add `resolveEventDeleteMode` (TDD) so deleting a recurring occurrence from the agenda quick view deletes only that occurrence (`single`) and never escalates an expanded instance to a whole-series delete; only the recurring master deletes as `series` (fixes Codex review finding)
- [x] 2.4 Wire the shared `useEventModalStore` into AgendaView (open state + defaultDate/defaultEndDate + clear on close) so the global `calendar.new-event` command/shortcut opens the New Event modal while the agenda is active, matching Day/Week/Month/Year (fixes Codex review finding)

## 3. Wire into the calendar header

- [x] 3.1 Add an "Agenda" button to the view switcher in `src/components/calendar/Calendar.tsx`
- [x] 3.2 Add a render branch so `view === "agenda"` renders `AgendaView` (not the `multiMonth` fallthrough)
- [x] 3.3 Confirm prev/next header navigation steps by week (not month) when the agenda view is active (agenda falls into the existing 7-day `else` branch in `handlePrev/NextWeek`)

## 4. Verify + document

- [x] 4.1 Local gate green: `npm run test:unit` (new `calendar-agenda` suite passes; 2 pre-existing unrelated `google-*` suites fail identically on `origin/main`), `npm run type-check` clean, `npm run lint` clean
- [x] 4.2 Add a `CHANGELOG.md` entry under `[unreleased]`
41 changes: 41 additions & 0 deletions openspec/specs/calendar-agenda-view/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# calendar-agenda-view Specification

## Purpose
TBD - created by archiving change issue-88-agenda-view. Update Purpose after archive.
## Requirements
### Requirement: Agenda view lists upcoming events and tasks chronologically

The calendar SHALL provide an "Agenda" view that displays calendar events and scheduled tasks as a chronological, date-grouped list for a rolling one-week range. The Agenda view SHALL be selectable from the calendar header view switcher alongside Day, Week, Month, and Year, and selecting it SHALL render the agenda list (not the Year view). The list SHALL include the same items the other views show for the active range: events from enabled feeds plus scheduled tasks, with each task or event opening its quick view on click.

#### Scenario: Selecting the Agenda view
- **WHEN** the user clicks the "Agenda" button in the calendar header
- **THEN** the calendar content area shows a chronological, date-grouped list of events and scheduled tasks for the active week (not the Year/multi-month grid)

#### Scenario: Agenda shows enabled-feed events and tasks
- **WHEN** the Agenda view is active for a range that contains events from an enabled feed and one or more scheduled tasks
- **THEN** those events and tasks appear in the list, sorted by start time, each labeled with its time and colored by its feed or task color

#### Scenario: Disabled feeds are excluded
- **WHEN** a feed is disabled
- **THEN** that feed's events do NOT appear in the Agenda list, while scheduled tasks (feed id `tasks`) continue to appear

#### Scenario: Empty range shows an empty state
- **WHEN** the Agenda view is active for a range with no events and no scheduled tasks
- **THEN** the list shows an empty-state message rather than an empty/blank area

#### Scenario: Time format follows user settings
- **WHEN** the user's time-format setting is 12-hour (or 24-hour)
- **THEN** the times shown in the Agenda list are rendered in that format

#### Scenario: Navigation steps by week
- **WHEN** the Agenda view is active and the user clicks the header next/previous navigation
- **THEN** the agenda range advances or retreats by one week (consistent with the Week view), not by a month

#### Scenario: Deleting a recurring occurrence does not delete the whole series
- **WHEN** the user deletes a recurring event occurrence from the Agenda quick view that is an expanded instance (not the recurring master)
- **THEN** only that occurrence is deleted (delete mode `single`), and the rest of the series is left intact; only deleting the recurring master deletes the series

#### Scenario: The global "new event" command works while the Agenda view is active
- **WHEN** the user triggers the calendar create-event command (or shortcut) while the Agenda view is active
- **THEN** the New Event modal opens (the Agenda view honors the shared event-modal store, like the other calendar views), rather than the command silently doing nothing

10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/list": "^6.1.15",
"@fullcalendar/multimonth": "^6.1.15",
"@fullcalendar/react": "^6.1.15",
"@fullcalendar/timegrid": "^6.1.15",
Expand Down
Loading