---
title: Calendar Quick Add
---

# Calendar Quick Add

## What it is

A tab inside the Quick Launch floating composer (Ctrl+Space → **Calendar** tab) for creating Google Calendar events from a single natural-language line. Type something like _"Lunch with Sarah tomorrow at noon for 90 minutes"_ into the Item field, press Ctrl+Enter, and Omniscio creates the event on your Google Calendar — your **primary** by default, or **another calendar you pick** (see "Pick a calendar" below). Type a time zone with the time (_"3pm EST"_) and it's **honored**, not ignored (see "Time zones" below).

The tab is **pinned by default** alongside Session / Alarm / Task / Scratchpad / Drip / Bookmark. Existing installs get the new tab automatically via a one-shot startup migration that appends `calendar` to `quickLaunchPinnedActionIds`. The tab is **gated on `calendarEnabled`** — if Google Calendar isn't connected yet, the tab is hidden from the strip until you authorize at **Settings → Google Integrations**.

What it looks like:

- An **Event** text input in the Quick Launch body — inline recurrence is extracted from the same line (the legacy free-text Repeat _field_ was retired 2026-05-24). It even understands a **dangling "every"** typed after the day (_"Kathleen Tuesday 10am every"_ → repeats weekly on Tuesday) rather than leaving the stray word in the title.
- Below the preview, a small **Repeat** checkbox + Daily/Weekly/Monthly/Yearly picker — a **manual fallback** to force (or remove) recurrence when the typed text alone didn't imply one (added 2026-07-06). It mirrors any repeat the text already implied, so you only reach for it when you need to.
- Below the input, a SnoozePalette-style **preview list** of 1–2 plausible interpretations:
  - If you gave an **exact time** (_"Birthday tomorrow at 2pm"_), the preview is a **single** timed row — no all-day alternative on the same date and no shorter-duration look-alike row, both of which would have rendered identically to the 1-hour default and read as duplicates.
  - If you gave a **date only** (_"Friday birthday"_), the preview shows **two** rows — the all-day interpretation and a 9 AM timed alternative — so you can disambiguate between "the whole day" and "a morning event on that day".
  - If you gave a **"…of the month" phrase without "every"** (_"Team meeting first Monday of the month"_), the preview shows **two** rows — a **repeating** monthly interpretation and a **one-time** interpretation — both on the upcoming occurrence, so you pick whether you meant a recurring meeting or a single event. (Add "every" — _"first Monday of every month"_ — to get just the repeating row.)
  - If you gave a **weekday and a date that disagree** (_"Mon the 21st"_ when the 21st is actually a Sunday), the preview shows **two** rows — the 21st **and** the next Monday — so you pick which you meant. When they agree, it's a single date.
- Each row reads as **Title — date and time** spelled out in full (e.g. _Monday, June 29 at 12:00 PM_; an all-day event shows the date with no time), with the date/time hugging the title rather than pinned to the right edge (the legacy left-right-spread layout was retired 2026-05-25). The recurrence summary, when present, sits on a second line beneath.
- When the content needs more room — several interpretations, and/or the **Repeat** row and **Calendar picker** being shown — everything renders **inside the Control Space's one fixed-size window**: content taller than the window **scrolls**, with the Cancel/Create buttons pinned below the scroll area so they are never pushed off. (The window itself never grows or shrinks — resizing it live used to flash a white strip for a frame on Windows, so the resize was removed; entrance-animation contract I21.) The suggestion list can't be squeezed, so a row is never cut off — it scrolls instead.
- A small sparkle pill at the top of the preview reads **"AI is thinking…"** only while the Haiku fallback is actually running, and **"AI fallback used"** on rows produced by the LLM. With the regex layer extensions from Phase 2 you should rarely see this pill — the goal is **<1%** of inputs.
- Click any row to pick the interpretation and submit. **Ctrl+Enter** picks the selected row. **Esc** dismisses the whole Quick Launch window.
- The microphone button feeds the Event field. Inline recurrence in the transcript (_"every Tuesday at 3pm"_) flows through the same parser path and shows up on the preview row's recurrence summary.

After a successful create, the floating composer **closes immediately** and a small toast appears in the bottom-left of the main Omniscio window (where Omniscio's other notifications live) — _"Added to Google Calendar: \<event title\>"_ — with two compact links:

- **Open** — opens the event in your browser.
- **Undo** — deletes the event you just created (one-click, no re-prompt), then confirms it was removed.

The toast auto-dismisses after a few seconds. (Changed 2026-06-23 — previously the composer showed a larger in-window pane that lingered for 4 seconds.) Note: if you've turned off the **Other notifications** toast category in Settings → Notifications, this confirmation won't appear.

## Where to find it

The tab lives inside Quick Launch, the floating composer you open with Ctrl+Space; if the tab is pinned and Google is connected, **Calendar** is one of the entries in the strip along its top, and clicking it swaps the body for the Event field. The tab itself is switched on by connecting Google in **Settings → Google Integrations**, is pinned or unpinned in the Quick Launch card in Settings, and has no settings card of its own.

There is no dedicated settings card for Calendar Quick Add. A few things you might look for and where they actually live:

- **The tab pinning** — Settings → Quick Launch (the tabs card every tab uses). Calendar is pinned by default; uncheck to hide it.
- **The default calendar** — no card; it's set automatically to whatever you last picked in the tab's calendar dropdown (stored as `calendarQuickAddDefaultCalendarId`). Change it just by picking a different calendar next time.
- **The $0.10/day LLM cap** — not user-configurable. Tracked in the shared `api_cost_log` table under the source label `calendar-quick-add`.
- **The Google connection** — Settings → Google Integrations. Same OAuth as Calendar / Drive / Sheets / Gmail. If revoked, the Calendar tab is hidden from the Quick Launch strip until reconnected.

## How it behaves

### Open the tab

Press Ctrl+Space (or your rebind) to open Quick Launch. If you have the Calendar tab pinned and Google is connected, the **Calendar** tab is one of the entries in the top strip. Click it or press Ctrl+Tab to cycle.

### Type a prompt

Drop a natural-language description of the event into the Event field. The parser understands:

- **Relative date phrases** — _today_, _tomorrow_, _tonight_, _yesterday_, _next Friday_, _in 3 weeks_, plus slang anchors _tonite_, _tmrw_, _tmr_, _2morrow_, etc.
- **Explicit dates in just about any common form** — month names (_June 5_, _Dec 25_, _21 June_, _21st of June_), numeric (_6/5_, _6/21/2026_), dashed / dotted with a year (_6-21-2026_, _6.21.2026_), ISO (_2026-06-21_), a bare day-of-month (_the 21st_, _on the 1st_), and a weekday paired with a date (_Sun 21st_, _Sunday the 21st_).
- **The date and time can sit anywhere in the line, in any order** — the parser finds them wherever they are and uses the leftover text as the title, so _"6/21 12pm vr event"_, _"dentist June 5"_, _"meeting at 3 today"_, _"birthday tomorrow at 10am"_, and time-before-day phrasings like _"John 1pm tomorrow"_, _"call mom 9am friday"_, and _"mon 10am standup"_ all parse cleanly — the date never has to come last, and neither the date nor the time ever leaks into the title.
- **"next \<weekday\>" skips to next week** — _"next Sunday"_ means the Sunday of _next_ week, not the one that may be only a day or two away. (Plain _"Sunday"_ or _"this Sunday"_ still means the nearest upcoming Sunday.)
- **When a weekday and a date disagree, you choose** — type _"Mon the 21st"_ when the 21st is actually a Sunday and you get **two** rows — the 21st **and** the next Monday — so you pick which you meant. When they agree (_"Sun 21st"_, the 21st is a Sunday) it's a single date.
- **A lone number is never a date** — _"invoice 4521"_, _"room 1530"_, and _"sprint 2-3 planning"_ won't accidentally book an event; an explicit date needs a month, a slash/ISO form, an ordinal (_the 21st_), or a weekday to be recognized.
- **Times** — _at 3pm_, _3:30 PM_, _15:00_, _noon_, _midnight_, plus compact / shorthand entry: _805p_ (8:05 PM), _8p_, _1230a_, _12p_, _845a_, and sloppy variants where the separator is fat-fingered (_8.05p_, _8;05pm_) or a minute digit is dropped (_8:5pm_). A compact time with no separator must carry its am/pm letter, so a bare room or extension number (_"room 1530"_) is never silently read as a time.
- **Time ranges** — _8-9p_, _8:05-9:30pm_, _8 to 9pm_ set the event's start **and** end in one line; a meridiem on either side carries to the other (_8-9pm_ → both PM), and this still works with a day-of-week trailing the range (_"block 2-4pm thursday"_ → Thursday 2 PM–4 PM, fixed 2026-07-04 — the bare "2" used to drop out of the range and strand in the title).
- **Everything the Snooze box understands, this box understands too** — Calendar Quick Add's date/time parsing shares one engine with session snooze (see [Snooze a session](snooze-a-session.md)), so business days (_"3 business days"_), ordinal days (_"the 15th"_, _"25th of march"_), bare months (_"june"_, _"early august"_), week-part phrases (_"next week friday"_, _"monday week"_), "same time" phrases, and end-of-period phrases (_"eom"_, _"eoy"_, _"cob"_) all resolve here as well — any grammar the shared engine learns, Calendar Quick Add inherits automatically, no separate work needed.
- **Inline duration** — _for 90 minutes_, _for 1 hour_, _for 2h_.
- **Inline recurrence** — _every Tuesday_, _every weekday_, _every other week_, _yearly_. A **dangling "every"** typed after the day also works: _"Kathleen Tuesday 10am every"_ or _"standup Friday 9am every other"_ read as _every Tuesday_ / _every other Friday_ (weekly / biweekly on that day). With no weekday to anchor it, the stray "every" is simply cleaned out of the title — use the **Repeat** box (below) to force a repeat in that case.
- **"…of the month" recurrence** — _first Monday of the month_, _last Friday of the month_ (a specific weekday each month), and _first of the month_, _the 15th of the month_, _last day of the month_ (a specific date each month). These also accept _"of every month"_. Typed **without** "every" the phrase is ambiguous, so the preview offers a **repeating row and a one-time row** on the upcoming occurrence — pick which you meant (see below).
- **Approximate / named / spelled times** — _around 1_, _3ish_, _early morning_, _late afternoon_, _after lunch_, _first thing_, _half past three_, _quarter to five_, _two pm_, plus meal context (_dinner at 7_ → 7 PM) and _in an hour_ / _in 15_.
- **Typos, shorthand & reschedules** — common day misspellings (_fryday_, _tomorow_, _tuseday_) resolve to the right day; _friday's deadline_, _all day friday_, and _move dentist from tue to fri_ read cleanly.
- **Time zones are honored** — write a zone with the time (_3pm EST_, _9am PST_, _noon eastern time_, _14:00 UTC_) and Omniscio books the event at that clock **in that zone** and labels it accordingly, so _"9am EST"_ lands at 9:00 Eastern even when your computer is on Pacific time. It's daylight-saving-aware — a summer _"EST"_ resolves to Eastern **Daylight** Time, exactly as Google shows it. Recognized zones are the US zones (ET/CT/MT/PT, standard or daylight) plus UTC/GMT; leave the zone off and the event uses your computer's local zone as before, and an unrecognized/exotic zone falls through to the AI parser.
- **Holidays** — _Christmas_, _Thanksgiving_, _Memorial Day_, _Halloween_, _New Year's_, _July 4th_ … resolve to the right date (rolling to next year once it has passed). Easter and other moving/lunar holidays use the AI fallback.
- **Relative dates** — _two weeks from friday_, _a week from today_, _end of the month_, _end of next week_.
- **Multi-day repeats & ordinals** — _mon wed fri 6am_, _tue and thu_, _mwf_, _every other tuesday_ become recurring schedules; an ordinal that names a thing (_10th anniversary june 1_) keeps the ordinal in the title and uses the real date.

Anything left over after stripping date/time/duration/recurrence becomes the event title.

Examples:

| You type                                  | Parses to                                                                                                                 |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `Lunch tomorrow at noon`                  | One row: 1-hour timed event tomorrow at 12:00 (time given → no all-day alternative)                                       |
| `Birthday tomorrow at 2pm`                | One row: 1-hour timed event tomorrow at 14:00                                                                             |
| `John 1pm tomorrow`                       | One row: 1-hour timed event tomorrow at 13:00, title "John" (time before the day still parses)                            |
| `call mom 9am friday`                     | One row: timed event Friday at 09:00, title "Call mom"                                                                    |
| `Dinner 805p`                             | One row: 1-hour timed event today at 20:05 (compact time → timed, never an all-day row)                                   |
| `Thursday 7p Holly`                       | One row: timed event next Thursday at 19:00, title "Holly" (single-letter time parses even with the title last)           |
| `Meeting 8-9p`                            | One row: timed event today 20:00–21:00 (range sets both start and end)                                                    |
| `block 2-4pm thursday`                    | One row: timed event Thursday 14:00–16:00, title "Block" (range + trailing day-of-week)                                   |
| `Friday birthday`                         | Two rows: all-day on Friday **or** a 9 AM timed alternative (no time given → pick which one you meant)                    |
| `Standup every weekday at 9`              | Recurring weekly Mo-Fr, 1-hour event at 09:00                                                                             |
| `Kathleen Tuesday 10am every`             | One row: repeats **weekly on Tuesday** at 10:00, title "Kathleen" (dangling "every" + a weekday → weekly)                 |
| `Standup Friday 9am every other`          | One row: repeats **every other Friday** at 09:00, title "Standup" ("every other" → biweekly)                              |
| `Dentist June 5`                          | All-day event on June 5 (no time given)                                                                                   |
| `6/21 12pm vr event`                      | One row: timed event on June 21 at 12:00, title "Vr event" (date found mid-line, stripped from title)                     |
| `dinner at 7 friday`                      | One row: Friday at 19:00 (evening context → PM), title "Dinner"                                                           |
| `christmas dinner`                        | All-day event on Dec 25, title "Dinner"                                                                                   |
| `gym mon wed fri 6am`                     | Recurring Mon/Wed/Fri at 06:00, title "Gym"                                                                               |
| `10th anniversary june 1`                 | All-day on June 1, title "10th anniversary" (the ordinal isn't read as the date)                                          |
| `Dentist 2026-06-21`                      | All-day event on June 21 (ISO date)                                                                                       |
| `Pay rent the 1st`                        | All-day event on the next 1st of the month                                                                                |
| `Mon the 21st` (21st is a Sunday)         | Two rows: the 21st **or** the next Monday — pick which you meant                                                          |
| `next Sunday`                             | All-day on the Sunday of next week (skips the imminent one)                                                               |
| `Gym in 3 weeks for 1 hour at 6pm`        | Single 1-hour timed event 3 weeks from today at 18:00                                                                     |
| `Standup for 3 weeks every Monday at 9am` | Recurring weekly Monday at 09:00, COUNT=3                                                                                 |
| `Team meeting first Monday of the month`  | Two rows: **repeating** monthly on the first Monday **or** a **one-time** event — both on the next first-Monday           |
| `Rent first of the month`                 | Two rows: repeating on the 1st **or** a one-time event on the next 1st                                                    |
| `Standup first Monday of every month`     | One row: repeating monthly on the first Monday ("every" → unambiguously recurring)                                        |
| `Standup 9am EST` (on a Pacific computer) | One row: timed event at 9:00 **Eastern**, tagged Eastern Time — the typed zone is honored, not shifted to your local time |

### Pick from the preview

When the parser produces multiple plausible interpretations, the preview list shows them as clickable rows. Each row shows the event title, the date/time, and (when applicable) a one-line recurrence summary like _"Every Mon/Wed/Fri, 10 times"_.

- Click a row to select + submit immediately.
- Ctrl+Enter submits the first (top-ranked) row.
- The selected row gets a left-accent stripe so you can tell which one Ctrl+Enter will fire.

If nothing parses (typed too much prose, or just typed nonsense), Omniscio silently falls back to a Haiku LLM call to try again — capped at $0.10/day across all your Quick Add usage. While the Haiku call is in flight the small **"AI is thinking…"** pill is visible above the preview; you'll know that this particular parse took longer because the regex missed, not because the feature is slow. If the daily cap is hit, you'll see _"AI fallback unavailable (daily cap reached). Try simpler date phrasing…"_ instead of the LLM trying.

### Make it repeat (the Repeat box)

Most of the time you just type the repeat into the line — _"every Tuesday"_, _"yearly"_, _"every other week"_, or even a dangling _"…every"_ after the day. When the text alone didn't capture it (say you typed an exact date, or a phrase the parser didn't read as recurring), use the **Repeat** checkbox that sits below the preview once an event is parsed:

- Tick **Repeat** and choose **Daily / Weekly / Monthly / Yearly**. Weekly repeats on the event's own weekday.
- If the text already implied a repeat, the box is **already ticked** and shows that frequency — so the control and the preview never disagree. Change the frequency to override it, or untick to drop the repeat entirely.
- Leave it untouched and nothing changes — the event repeats exactly as the text implied (or not at all).

The Repeat box only forces the recurrence rule; the date, time, title, and calendar all still come from what you typed.

### Pick a calendar

By default a Quick Add event lands on your **primary** Google Calendar. If your Google account has **more than one calendar you can write to**, a small **calendar dropdown** appears below the preview list — choose the one you want and press Create. Two conveniences:

- **Your pick is remembered.** The calendar you choose becomes the default for the next Quick Add, so a one-time switch sticks until you change it again (including switching back to primary).
- **Only writable calendars are offered.** Calendars you can only view — a shared holidays feed, a coworker's calendar shared read-only — are filtered out, because creating an event on one would just be rejected by Google.

If you have exactly one writable calendar (the common case), there's no dropdown — it just uses it, no extra click.

### Time zones

Write a time zone right in the line — _"Standup 9am EST"_, _"call 2pm PST"_, _"sync at noon eastern time"_, _"deploy 14:00 UTC"_ — and Omniscio creates the event **at that clock in that zone**, then tags the event with it so Google shows it correctly. So _"9am EST"_ is 9:00 AM Eastern no matter what time zone your own computer is set to.

- It's **daylight-saving-aware**: a _"9am EST"_ in July resolves to 9:00 Eastern **Daylight** Time (summer Eastern), which is how Google itself labels it — you don't have to remember EST-vs-EDT.
- Recognized zones are the **US zones** (Eastern / Central / Mountain / Pacific, written as ET/CT/MT/PT or the full "eastern time" etc.) plus **UTC/GMT**.
- **No zone typed?** The event uses your computer's own time zone, exactly as before — nothing changes for the common case.
- A zone Omniscio doesn't recognize (something outside the set above) falls through to the AI parser rather than guessing.

### Defaults

- **All-day vs timed**: If you didn't give a time, Omniscio creates an all-day event. If you gave a time but no duration, the event is **1 hour** long.
- **Calendar**: Your **primary** Google Calendar by default. If you have more than one calendar you can write to, a **calendar picker** appears below the preview — pick any writable calendar and your choice is **remembered as the new default** next time. (Read-only calendars — a shared holidays feed, someone else's calendar you can only view — are never offered, since a create there would just fail.)
- **Time zone**: If you **type a zone** (_EST_, _PST_, _eastern time_, _UTC_…), the event is booked at that clock in that zone. If you don't, it uses your computer's local zone (whatever your OS reports).

### Validation

- An empty Item field rejects with a red border + tooltip _"Type something to create an event."_
- An Item field that produces zero parseable interpretations rejects similarly — but tells you the LLM fallback was tried (or that the daily cap blocked it).

### What happens if Google Calendar disconnects?

Your Google sign-in can lapse — desktop OAuth refresh tokens expire after about six months idle, or sooner if you change your Google password or manually revoke access — or the stored connection can go missing entirely (for example after a system or credential reset). Either way, when you click Create, Omniscio does **not** dump the raw error. Instead the preview area swaps to a plain-language message — _"Google session has expired"_ when the sign-in lapsed, or _"Not connected to Google"_ when it's gone — each with a hint to reconnect, and a **Reconnect Google** button right there in the pane. Click it, finish the standard Google sign-in popup (the same one Drive / Sheets / Gmail use — all four share one connection), and Omniscio **automatically re-creates the event you already described** — no re-typing the prompt, no clicking Create again. If the reconnect itself is cancelled or fails, the button stays so you can try again. Non-auth failures (e.g. a network blip) show the friendly message without the reconnect button, since reconnecting wouldn't fix them.

## Related

- [quick-launch-modal.md](quick-launch-modal.md) — the parent feature. Calendar is one tab inside Quick Launch; everything about the hotkey, voice mic, project chip suppression, and tab strip behavior lives there.
- [google-integrations.md](google-integrations.md) — the OAuth connection that gates this tab. If `calendarEnabled` is false, the tab is hidden.
- [alarms.md](alarms.md) — the closest sibling tab in shape: natural-language input, regex-first parser, Haiku fallback, daily cap. Calendar Quick Add reuses the same orchestration pattern.

The reasoning behind the parser, the preview and the calendar picker, plus the code map underneath the tab, is on [part 2](calendar-quick-add-part-2.md).
