feat: import events from an .ics calendar file

Merged
AR
27m ago
+754-1

What

Upload an .ics export (Google, Apple, Outlook), review the events, and save them as busy time that the planner, capacity checks, conflict detection and morning brief already respect.

How it works

  • Parser (backend/app/services/ics_import.py, no new dependencies): line folding, UTC / TZID / floating times, all-day events, DURATION, RRULE (validated against the planner's expander; date-only UNTIL normalised) and EXDATE. Past, cancelled, invalid and duplicate events are skipped and counted.
  • API (/api/import/calendar): parse (writes nothing), save (one batch, dedupes by calendar UID via the existing events.external_id), undo.
  • UI: new /import/calendar page and a Calendar nav link, with a review step and a 10 second undo.
  • No migration needed; events are stored with source='ics'.

Testing

  • backend/tests/test_ics_import.py (10 tests, including an HTTP parse, save and undo round trip) and frontend/tests/ics.test.ts (4 tests).
  • Full backend suite on Python 3.12: 244 passed, 3 skipped.
  • Checked in the browser against the running app: parse, save, duplicate re-import ("1 was already imported"), undo, and the imported event appearing on Today with a reminder.

Notes

  • Import is manual (re-export and upload to refresh). Google OAuth sync could build on the same parser later.
  • Separate from this PR: pyproject.toml says Python >=3.9 but the backend needs 3.10+ (X | None annotations).

šŸ¤– Generated with Claude Code


Summary by cubic

Adds a manual .ics calendar import (Google, Apple, Outlook) so imported events count as busy time for the planner, capacity checks, conflict detection, and the morning brief.

  • New /api/import/calendar endpoints: parse (writes nothing), save (one batch, deduped by calendar UID), and undo (available for 10 seconds after saving).
  • Parser is dependency-free and handles line folding, UTC/TZID/floating times, all-day events, DURATION, validated RRULE (date-only UNTIL normalized, EXDATE applied), and nested VALARM blocks.
  • Past, cancelled, invalid, and duplicate events are skipped and reported in the review UI.
  • New /import/calendar page with a review step to uncheck or rename events before saving, plus a Calendar nav link.
  • No migration needed; events are stored with source='ics'.
  • Import is manual — re-export and upload to refresh. Google OAuth sync could reuse the parser later.