---
title: Alarm Quick Add
---

# Alarm Quick Add

## What it is

A tab inside the Quick Launch floating composer (Ctrl+Space → **Alarm** tab) for creating an Omniscio alarm from a single natural-language line. Type _"every weekday at 7:30 AM stand-up"_ or _"first Monday of the month at 9am"_ into the textarea, press Ctrl+Enter, and Omniscio saves the alarm and schedules its next fire — without ever leaving the floating composer.

This is a **text-only** tab. There is no Label / Time / Recurrence / Date / weekday-chip form here — the textarea is the entire input surface. The structured editor still exists in the Alarms virtual project's Pane 3 ([alarms.md](alarms.md)); Quick Launch is the "I just want to create one and get back to what I was doing" path.

The tab is **gated on `alarmsEnabled`**. With Alarms disabled at **Settings → Notifications → Alarms → Enable Alarms**, the tab is hidden from the Quick Launch strip the same way Calendar Quick Add is hidden when `calendarEnabled` is false.

What it looks like:

- A label _"Describe the alarm"_ over a 2-row textarea (500-character cap) with a placeholder _"Try 'every weekday at 7:30 AM' or 'first Monday of the month at 9am'"_.
- A fixed-height **preview row** below the textarea (`role="status"`, `aria-live="polite"`) — shows a one-line summary of what the parser inferred as soon as it lands. The slot is always present so the modal doesn't jump on each parse.
- Footer: **Cancel** + **Save alarm** (the latter Ctrl+Enter from anywhere in the tab). Save is disabled until the parser produces something mappable.

After a successful save the tab closes via the Quick Launch's normal exit animation — there is no in-tab confirmation pane, no Undo button (alarms are cheap to delete from the Alarms virtual project's sidebar if you change your mind).

## Where to find it

### How to use it

#### Open the tab

Press Ctrl+Space (or your rebind) to open Quick Launch. If you have the Alarm tab pinned and Alarms are enabled, **Alarm** is one of the entries in the top strip. Click it or press Ctrl+Tab to cycle.

#### Type a phrase

The parser understands the same vocabulary the Calendar tab handles — the two tabs share the same backend (see "How it works" below). The phrasings that work:

- **One-off times** — _"at 8pm"_, _"tomorrow at 9:30am"_, _"June 5 at 7am"_, _"2025-12-25 at 7am"_, plus compact shorthand: _"805p"_ (8:05 PM), _"8p"_, _"1230a"_, _"tomorrow 805p"_. Compact times are expanded to `H:MM AM/PM` before the parser runs, so they resolve on the free offline path without an AI call.
- **Recurring (legacy 5-value enum)** — _"every day at 7am"_, _"every weekday at 7:30 AM"_, _"weekends at 10am"_, _"every Monday at 9am"_, _"Mon Wed Fri at 6:45pm"_.
- **Recurring (rich RRULE escape hatch)** — _"every 3 weeks at 9am"_, _"first Monday of the month at 9am"_, _"every other Tuesday at 2pm"_, _"every 6 weeks on Friday at 5pm"_.

The first category gets created with `rrule = null` and rides the legacy 14-day day-by-day evaluator (proven path, no rrule library needed). The third category is stored with a full `RRULE:` string and evaluated by the `rrule` npm package on every fire-time recompute (DST-safe wall-clock recomposition). The collapse decision is automatic — the shared mapper detects "this rule reduces to DAILY / weekdays / weekends / a flat day-of-week set" and downgrades it; anything richer stays rrule.

If you don't type a time, Omniscio assumes **9:00 AM** for the recurrence you described (the parser surfaces an all-day row + a `default-morning` 9:00 AM row; the mapper picks the 9 AM one).

#### Save

Press **Ctrl+Enter** (or click **Save alarm**) to commit. The Save button is disabled while the parser is still running or when nothing mappable was found — the title tooltip explains which.

If the create fails (validation, IPC error, or Omniscio rejected an unsupported RRULE shape like `FREQ=MINUTELY`), the error message shows inline above the footer and the textarea stays editable so you can adjust and retry.

#### Status messages (what the preview row tells you)

The preview row shows one of:

| State                                 | Text                                                                                                               |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Empty textarea                        | _"Start typing a phrase like 'every weekday at 7:30 AM'"_                                                          |
| Parsing in flight, nothing mapped yet | spinner + _"Parsing…"_                                                                                             |
| Mapped successfully                   | the one-line summary (e.g. _"every weekday at 07:30 — stand-up"_)                                                  |
| Parser returned nothing               | _"Couldn't understand that. Try a phrase like 'daily at 7:30am' or 'first Monday at 9am'."_                        |
| LLM fallback hit the $0.10/day cap    | _"AI fallback unavailable (daily cap reached). Try simpler phrasing like 'daily at 9am' or 'weekdays at 7:30am'."_ |
| LLM call failed (network/parse error) | _"Couldn't interpret that. Try simpler phrasing like 'every weekday 9am'."_                                        |

The cap text matters: this tab shares the same $0.10/day spend cap as Calendar Quick Add (label `'calendar-quick-add'`). If you've blown through it on the calendar side today, the alarm tab feels it too — but the free regex path keeps working for anything formulaic. The cap resets at midnight local time.

## How it behaves

### Defaults

- **Time**: If your phrase carries no time, Omniscio assumes 09:00 local.
- **Recurrence**: Whatever the parser emits — one-off if nothing recurring is detected, legacy enum if it collapses, rrule otherwise.
- **Sound / Snooze / Assertiveness / Foreground / Pierce Focus Mode**: All five are `null` (inherit the global default at fire time — see [alarms.md § Per-alarm overrides](alarms.md)). The Quick Launch tab never sets per-alarm overrides; refine those from the Alarms virtual project's Pane 3 editor afterwards.
- **Folder**: Always Uncategorized. Move into a folder later from the sidebar drag-target or the Pane 3 dropdown.

### Editing a Quick-Add-created alarm afterwards

In the Alarms virtual project's Pane 3 editor:

- **Legacy alarms** (created with `rrule = null`) edit normally — full control over recurrence type, weekday chips, etc.
- **Rrule alarms** show the recurrence label **read-only** with a hint to "edit via Quick Add text". The 5-value dropdown is deliberately hidden for those — coercing a rich RRULE into the legacy enum would silently lose its shape (e.g. an _"every 3 weeks"_ alarm becoming a plain _"daily"_). To change the recurrence on a rich rrule alarm, delete it and recreate with a new Quick Add phrase.

Every other field on a rrule alarm (label, sound, snooze, foreground, etc.) edits the same way as a legacy alarm.

## For agents

### How it works (for repo-aware readers)

- **Tab component**: [src/renderer/src/features/quick-launch/QuickLaunchAlarmTab.tsx](../../src/renderer/src/features/quick-launch/QuickLaunchAlarmTab.tsx) — owns the textarea state, the 300ms debounced parse, the preview slot, and the Save submit. **No `rrule` import** — keeps the renderer bundle clean.
- **Action registry**: declared in [src/shared/quick-launch-actions.ts](../../src/shared/quick-launch-actions.ts) with `id: 'alarm'`, `enabledSelector: (s) => s.alarmsEnabled`. Pinned by default. Component map in [src/renderer/src/features/quick-launch/quick-launch-action-components.ts](../../src/renderer/src/features/quick-launch/quick-launch-action-components.ts).
- **Pure-shared mapper**: [src/shared/alarm-quick-add.ts](../../src/shared/alarm-quick-add.ts) — `mapInterpretationToAlarm(interp, now?)` and `summarizeAlarmInterpretation(interp)`. Maps a `QuickAddInterpretation` to an `AlarmInputShape`. Performs the RRULE→legacy collapse: DAILY ⇒ `'daily'`, weekday set ⇒ `'weekdays'`, SA/SU ⇒ `'weekends'`, anything else stays as `rrule`+`rruleLabel`. Returns `null` for all-day interpretations with no rrule (no fire-time anchor). No Electron dependency — runs identically in the renderer for preview and in tests.
- **Parse channel**: reuses **`CALENDAR_PARSE_QUICK_ADD`** as-is. The alarm tab is one of the parse channel's two consumers (the other being [QuickLaunchCalendarTab.tsx](../../src/renderer/src/features/quick-launch/QuickLaunchCalendarTab.tsx)). No new IPC was added for the alarm side — the cost-cap label, the regex parser, the Haiku LLM fallback, and the OpenRouter `json_object` mode all live in [src/main/services/google/calendar-ai-service.ts](../../src/main/services/google/calendar-ai-service.ts) `quickAddParse()` and are shared.
- **Create channel**: existing `ALARMS_CREATE` handler. The Quick Launch tab adds **no new backend write paths**; it front-ends the same insert pipeline `QuickAddAlarmModal` already uses. Schema lives in [src/shared/ipc-schemas.ts](../../src/shared/ipc-schemas.ts) `alarmCreateSchema` — `rrule` and `rruleLabel` are `.nullable().optional()` so the existing manual-form callers compile unchanged.
- **RRULE evaluation**: [src/main/services/alarm/alarm-rrule-eval.ts](../../src/main/services/alarm/alarm-rrule-eval.ts) `nextRruleFireAt(rrule, dtstartLocal, now)` — builds dtstart from the stored wall-clock, computes the next occurrence in wall-clock space, localizes at the end (DST-safe across spring-forward and fall-back). Returns `null` when the rrule is exhausted (COUNT reached, UNTIL passed). `validateAlarmRrule(rrule)` rejects unsupported shapes — `FREQ` must be DAILY/WEEKLY/MONTHLY/YEARLY (sub-daily like MINUTELY/SECONDLY/HOURLY is rejected at the create boundary so we never store a rule we can't fire correctly).
- **Scheduler branch**: [src/main/services/alarm/alarm-recurrence-utils.ts](../../src/main/services/alarm/alarm-recurrence-utils.ts) `computeNextFireAt` checks `if (alarm.rrule)` first and delegates to `nextRruleFireAt`; otherwise falls through to the legacy 14-day day-by-day loop. The `rrule != null` discriminator is the single branch point.
- **Exhaustion handling**: [src/main/services/alarm/alarm-service.ts](../../src/main/services/alarm/alarm-service.ts) `advanceSchedule` soft-deletes rrule alarms when `computeNextFireAt` returns null (treats exhaustion the same as a one-off firing — phone-alarm semantics). Legacy alarms with no fire limit still re-arm forever.
- **Storage**: migration **v234** adds two nullable columns to `alarms`: `rrule TEXT` and `rrule_label TEXT`. `ALTER TABLE ADD COLUMN` only — no CHECK change, no table rebuild, no migration risk. The legacy 5-value `recurrence` enum is untouched; rrule alarms hold an inert `'daily'` placeholder in `recurrence` (the column is NOT NULL) and the discriminator is the `rrule` column itself.
- **Display surfaces**: [AlarmsSidebar.tsx](../../src/renderer/src/features/alarms/AlarmsSidebar.tsx), [AlarmDetail.tsx](../../src/renderer/src/features/alarms/AlarmDetail.tsx), and [AlarmRingModal.tsx](../../src/renderer/src/features/alarms/AlarmRingModal.tsx) all render `rrule_label` when set, falling back to the legacy label otherwise. `AlarmDetail` hides the 5-value recurrence dropdown for rrule alarms — read-only with an "edit via Quick Add text" hint.
- **UI anchors**: 4 entries in `STATIC_UI_ANCHORS` ([src/shared/ui-anchor-registry.ts](../../src/shared/ui-anchor-registry.ts)) — tab button, phrase textarea, preview row, Save button. Required for App Tour spotlights and the `GET /ui/snapshot` CLI route.

### Design decisions worth knowing

1. **One field, not two** — Calendar Quick Add splits Item + Repeat because event titles can read awkwardly with recurrence baked in (_"1:1 with Alex"_ + _"every other Tuesday"_). Alarms don't have that problem — _"every weekday at 7:30 AM stand-up"_ reads cleanly as a single phrase, and the parser is just as accurate either way. So Quick Launch Alarm stays single-textarea.

2. **Reuse the calendar parse channel, no new IPC** — every shape that's useful for an alarm is also useful for a calendar event. Forking the channel would mean duplicating the regex layer + LLM fallback + Zod validator, doubling the surface to test and giving us two cost caps to reason about. The calendar tab and alarm tab share one parser, one cap, one cost line in the API log.

3. **`rrule != null` is the discriminator, not a 6th enum value** — adding `'rrule'` to the `AlarmRecurrence` union would have required rebuilding the `alarms` table (the 5-value CHECK constraint is hardcoded in the v196 migration plus 6 test bootstraps). Two nullable columns is a clean ADD-COLUMN migration with zero CHECK churn. The legacy evaluator still handles the legacy enum unchanged; only rrule alarms take the new branch.

4. **RRULE→legacy collapse in the mapper** — most rrules the parser emits (DAILY, weekly-with-weekday-set) are expressible in the legacy enum. Collapsing them down means the proven legacy path keeps handling 90%+ of saves; only the genuinely rich cases (interval, BYSETPOS, every-N-weeks) trigger the rrule path. Less surface area exercised in production = fewer chances to regress the alarm engine.

5. **Read-only rrule in AlarmDetail** — coercing a rich rrule into the legacy enum would silently lose its shape (_"every 3 weeks"_ becoming plain _"daily"_). Showing the rrule label read-only with an "edit via Quick Add text" hint forces a delete-and-recreate, which is honest about the constraint.

6. **No Undo pane like Calendar Quick Add** — alarms are local, cheap to recreate, and the Alarms virtual project's sidebar shows the new alarm immediately. Adding an in-tab Undo + confirmation pane would have meant building the same exit-state machine the calendar tab needs (Google Calendar deletes are slow and async). Alarms don't earn that complexity.

## Related

- [quick-launch-modal.md](quick-launch-modal.md) — the parent feature. Alarm is one tab inside Quick Launch; everything about the hotkey, voice mic, project chip suppression, and tab strip behavior lives there.
- [alarms.md](alarms.md) — the underlying alarm feature. Pane 3 editor, fire/ring/snooze semantics, folders, sound picker, per-alarm overrides, the inbox `alarm-fired` row. Quick Launch is one creation surface; the Alarms virtual project is where the alarm lives once it exists.
- [calendar-quick-add.md](calendar-quick-add.md) — the sibling tab the alarm tab shares its parser with. Same regex-first → Haiku-fallback orchestration, same $0.10/day cap, same multi-row preview philosophy (alarm shows just the top interpretation as a one-line summary instead of 2–4 clickable rows, since alarms don't have ambiguity around event title vs date the way calendar events do).
