---
title: Recurring Special Events
---

# Recurring Special Events

## What it is

A tab inside the **Google Calendar** panel where you add a yearly "special event"
— a person's birthday, an anniversary, or a holiday — and Omniscio turns it into a
set of **Google Calendar** entries: the day itself, plus whichever advance
reminders you pick (on the day, a day before, a week before, a month before, or
*any custom number of days* before). It replaces the manual
"Birthday/Anniversary Reminder Creation Tool" spreadsheet: instead of copying
formulas and pasting dates, you fill a short form and the calendar events are
created for you.

It writes to the **same Google Calendar** that Omniscio's Calendar integration
already connects to (one OAuth grant covers Gmail, Calendar, Drive, Sheets). If
Google isn't connected yet, the panel shows a "Connect Google Calendar in
Settings" prompt instead of the form.

> Add birthdays, anniversaries, and holidays once — Omniscio creates the Google
> Calendar events and lead-time reminders for them, every year, automatically.

**Status:** shipped — on by default for anyone with **Google Calendar enabled**
(`calendarEnabled`), desktop-only. It surfaces as a **third tab ("Recurring
Events") inside the Google Calendar panel** — open Google Calendar in the sidebar,
then switch to the Recurring Events tab (the calendar grid stays visible in the
main pane for context). The tab appears whenever Calendar is enabled; if Google
isn't connected yet it shows a "Connect Google Calendar" prompt.

## Where to find it

Open Google Calendar from the sidebar, then switch to its third tab, Recurring Events. The calendar grid stays visible beside the form so you keep your context, and everything you save there appears on the same calendar Omniscio already connects to.

The tab is there whenever Google Calendar is enabled. If Google has not been connected yet, it shows a prompt to connect it instead of the form.

## How it behaves

### The three kinds of event

1. **Fixed date** — the same calendar date every year (e.g. *June 13*). Best for
   birthdays and anniversaries.
2. **Nth weekday of a month** — a date that moves but follows a rule (e.g. *the
   3rd Sunday of May*). "Last" is supported (e.g. the last Monday of May).
3. **Holiday** — pick from a built-in catalog whose recurrence rules are baked
   into the software: New Year's, MLK Day, Valentine's, Presidents' Day, St.
   Patrick's, Good Friday, Easter, Mother's Day, Memorial Day, Father's Day,
   Juneteenth, Independence Day, Labor Day, Halloween, Veterans Day, Thanksgiving,
   Christmas Eve/Day, New Year's Eve. Easter (and Good Friday) are computed with
   the Gregorian Computus algorithm; the rest are fixed dates or Nth-weekday
   rules.

For each event you also choose all-day (default) or a specific time, and the time
zone the reminders are anchored in.

### Lead-time reminders

Tick any combination of: **On the day**, **1 day before**, **1 week before**,
**1 month before**, and a **custom "N days before"**. Each one becomes its own
visible calendar entry titled like "Mom's Birthday — 1 week before", so you see
the heads-up on your calendar (not just a silent popup). At least one reminder is
required.

### Why "N days before a holiday" is now reliable

This is the feature's whole reason for existing. In a spreadsheet you can't
reliably say "10 days before Mother's Day", because Mother's Day *moves* every
year (2nd Sunday of May) and a fixed cell can't track it. Omniscio resolves the
holiday's **exact date in software for each year** and then subtracts your offset,
so the reminder lands on the correct day every year.

### How the calendar events are created (behind the scenes)

- **Fixed-date events** (and their lead reminders) always land on the same
  month/day each year, so each is created as a **single yearly-recurring Google
  Calendar event** using an `RRULE:FREQ=YEARLY` rule. One event, recurs forever,
  no maintenance.
- **Floating events** (Nth-weekday and holidays): the day-of entry of an
  Nth-weekday or fixed holiday is also a single recurring event
  (`BYDAY=3SU`, `BYMONTHDAY=…`). But a *lead reminder* on a floating date (e.g.
  10 days before Mother's Day) can't be expressed as a clean recurring rule, so
  Omniscio **materializes** it: it computes the exact date for each of the next
  several years and creates one calendar entry per year. Computed holidays like
  Easter are materialized the same way.
- The materialized horizon is **topped up** each time you open the panel, so the
  reminders never quietly run out. Creation is **idempotent** (a stable id is
  derived per event+reminder+year), so topping up never double-books.
- Editing an event removes its old calendar entries and recreates them; deleting
  an event removes them from Google Calendar too.

### Drive it from the CLI

Special events are also reachable over the AMC control server (`127.0.0.1:19519`,
bearer-auth) so an agent or a `curl` script can manage them headlessly — the same
five operations the panel exposes:

| Method | Path | Does |
| --- | --- | --- |
| `GET` | `/special-events` | List active special events |
| `GET` | `/special-events/status` | Is Google Calendar connected (and which account) |
| `POST` | `/special-events` | Create one (+ its calendar events) → 201 `{ event, calendarEventsCreated }` |
| `PATCH` | `/special-events/:id` | Replace an event's calendar events from a new spec |
| `DELETE` | `/special-events/:id` | Remove an event + every calendar event it created |

- **Calendar-gated.** Every route returns **403 `{ disabled: true }`** until
  **Google Calendar is enabled** (`calendarEnabled`) — the same gate as the panel,
  so once you can see the tab, the CLI works.
- **Google must be connected.** Create/update return **409** with a plain message
  when it isn't — connect Google Calendar in Settings first.
- **Apply immediately** (bearer + the shared 10/min mutation budget) — a special
  event is a personal-account Google Calendar write, like `/google/calendar/*`.
- **Create is not idempotent** — a blind retry re-books a duplicate. If you didn't
  see the response, `GET /special-events` and delete the extra rather than retrying.

The request body matches the panel's form (title, kind, month/day or
nthWeek/weekday or holidayId, allDay + timeLocal, timeZone, leadReminders, …). Full
per-route detail + curl recipes live in the `omniscio-control` skill's
`special-events.md` spoke.

### Limits / notes

- Desktop-only for now (creating events from a phone is gated off).
- Writes to your **primary** Google Calendar.
- A Feb 29 birthday recurs on Feb 29 only in leap years (Google's own behavior).
- The holiday catalog is US-centric in this first release; more can be added to
  `holiday-catalog.ts` without touching anything else.

## For agents

### Where it lives in the code

- Shared, pure recurrence logic (no runtime deps, fully unit-tested):
  `src/shared/special-events/` — `types.ts`, `date-math.ts` (Nth-weekday, month
  subtraction, Easter/Computus), `holiday-catalog.ts`, and `occurrences.ts`
  (`buildCalendarEventSpecs`, which decides recurring-vs-materialized).
- Backend service: `src/main/services/special-events/special-events-service.ts`
  (orchestrates the DB + Google Calendar via the existing
  `calendar-api-service`), DB in `src/main/db/queries-special-events.ts`
  (`special_events` table), IPC in `src/main/ipc/special-events-handlers.ts`.
- Renderer view: `src/renderer/src/features/recurring-events/` (RecurringEventsView),
  mounted as the "Recurring Events" tab of the Google Calendar panel in
  `src/renderer/src/features/calendar/CalendarPanelTabs.tsx` (shown when
  `calendarEnabled` is on + Electron). The main-pane calendar grid stays
  visible via `calendarAgendaTab` in `Dashboard.tsx`.
- IPC channels `special-events:list | create | update | delete | status` +
  the `special-events/changed` refetch push.
- CLI control-server routes:
  `src/main/services/cli/cli-server-special-events-routes.ts`
  (`registerSpecialEventsRoutes`), wired in `register-cli-routes.ts`; the three
  mutations are `CLI_PARITY_ROUTES` entries (promoted from `deferred-no-route`).

## Related

Nothing else in the library is linked from this page, so the way onward is [INDEX.md](INDEX.md), the library index.
