Alarm Quick Add
A tab inside the Quick Launch floating composer that creates an Omniscio alarm from one natural-language line such as "every weekday at 7:30 AM", covering the phrasings the parser understands, the defaults it fills in, the preview row's status messages, and how to edit the alarm afterwards.
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); 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/PMbefore 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). 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 — owns the textarea state, the 300ms debounced parse, the preview slot, and the Save submit. No
rruleimport — keeps the renderer bundle clean. - Action registry: declared in 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. - Pure-shared mapper: src/shared/alarm-quick-add.ts —
mapInterpretationToAlarm(interp, now?)andsummarizeAlarmInterpretation(interp). Maps aQuickAddInterpretationto anAlarmInputShape. Performs the RRULE→legacy collapse: DAILY ⇒'daily', weekday set ⇒'weekdays', SA/SU ⇒'weekends', anything else stays asrrule+rruleLabel. Returnsnullfor 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_ADDas-is. The alarm tab is one of the parse channel's two consumers (the other being QuickLaunchCalendarTab.tsx). No new IPC was added for the alarm side — the cost-cap label, the regex parser, the Haiku LLM fallback, and the OpenRouterjson_objectmode all live in src/main/services/google/calendar-ai-service.tsquickAddParse()and are shared. - Create channel: existing
ALARMS_CREATEhandler. The Quick Launch tab adds no new backend write paths; it front-ends the same insert pipelineQuickAddAlarmModalalready uses. Schema lives in src/shared/ipc-schemas.tsalarmCreateSchema—rruleandrruleLabelare.nullable().optional()so the existing manual-form callers compile unchanged. - RRULE evaluation: 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). Returnsnullwhen the rrule is exhausted (COUNT reached, UNTIL passed).validateAlarmRrule(rrule)rejects unsupported shapes —FREQmust 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
computeNextFireAtchecksif (alarm.rrule)first and delegates tonextRruleFireAt; otherwise falls through to the legacy 14-day day-by-day loop. Therrule != nulldiscriminator is the single branch point. - Exhaustion handling: src/main/services/alarm/alarm-service.ts
advanceSchedulesoft-deletes rrule alarms whencomputeNextFireAtreturns 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 TEXTandrrule_label TEXT.ALTER TABLE ADD COLUMNonly — no CHECK change, no table rebuild, no migration risk. The legacy 5-valuerecurrenceenum is untouched; rrule alarms hold an inert'daily'placeholder inrecurrence(the column is NOT NULL) and the discriminator is therrulecolumn itself. - Display surfaces: AlarmsSidebar.tsx, AlarmDetail.tsx, and AlarmRingModal.tsx all render
rrule_labelwhen set, falling back to the legacy label otherwise.AlarmDetailhides 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) — tab button, phrase textarea, preview row, Save button. Required for App Tour spotlights and theGET /ui/snapshotCLI route.
Design decisions worth knowing
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.
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.
rrule != nullis the discriminator, not a 6th enum value — adding'rrule'to theAlarmRecurrenceunion would have required rebuilding thealarmstable (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.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.
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.
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 — 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 — the underlying alarm feature. Pane 3 editor, fire/ring/snooze semantics, folders, sound picker, per-alarm overrides, the inbox
alarm-firedrow. Quick Launch is one creation surface; the Alarms virtual project is where the alarm lives once it exists. - 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).
Last verified 2026-09-23