aboutsummaryrefslogtreecommitdiff
path: root/README.org
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-18 11:13:39 -0600
committerCraig Jennings <c@cjennings.net>2025-11-18 11:13:39 -0600
commit4835fadabf243b33fb78557e45428055675e7300 (patch)
tree2e8ccd7995ffa6f6dd99943d829fb8b7e3112874 /README.org
downloadchime-4835fadabf243b33fb78557e45428055675e7300.tar.gz
chime-4835fadabf243b33fb78557e45428055675e7300.zip
changed repositories
Diffstat (limited to 'README.org')
-rw-r--r--README.org1081
1 files changed, 1081 insertions, 0 deletions
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..56c061d
--- /dev/null
+++ b/README.org
@@ -0,0 +1,1081 @@
+
+* *CHIME Heralds Imminent Events*
+
+Customizable org notifications for Emacs with visual alerts, audible chimes, and modeline display.
+
+** Table of Contents
+
+- [[#about][About]]
+- [[#features][Features]]
+- [[#installation][Installation]]
+- [[#quick-start][Quick Start]]
+- [[#configuration][Configuration]]
+- [[#usage][Usage]]
+- [[#known-limitations][Known Limitations]]
+- [[#full-example-configuration][Full Example Configuration]]
+- [[#manual-check][Manual Check]]
+- [[#troubleshooting][Troubleshooting]]
+- [[#requirements][Requirements]]
+- [[#testing][Testing]]
+- [[#license][License]]
+- [[#credits][Credits]]
+- [[#migration-from-org-wild-notifier][Migration from org-wild-notifier]]
+- [[#development][Development]]
+
+** About
+:PROPERTIES:
+:CUSTOM_ID: about
+:END:
+
+CHIME (backronym: *CHIME Heralds Imminent Events*) provides notification support for your org-agenda events. Get visual notifications, a pleasant chime sound, and see your next upcoming event in your modeline.
+
+This is an updated and maintained fork of the abandoned [[https://github.com/akhramov/org-wild-notifier.el][org-wild-notifier]] project, renamed to CHIME with bug-fixes and new features.
+
+Note: while I've found this package to be quite reliable, it's still undergoing feature development and will be changing frequently.
+
+** Features
+:PROPERTIES:
+:CUSTOM_ID: features
+:END:
+
+- *Visual notifications* with customizable alert times
+- *Audible chime sound* when notifications are displayed
+- *Interactive modeline display* of next upcoming event with extensive customization:
+ - Enable/disable modeline modifications
+ - Hover tooltip showing all upcoming events grouped by day
+ - Click to jump directly to event's org entry
+ - Customize notification text format (show/hide time, countdown, or title)
+ - Choose 12-hour or 24-hour time display
+ - Customize time-until format (verbose or compact)
+ - Configurable lookahead window and tooltip event limit
+- Multiple notification times per event (e.g., 5 minutes before and at event time)
+- Works with SCHEDULED and DEADLINE and just plain ol' regular timestamps
+- Supports repeating timestamps (=+1w=, =.+1d=, =++1w=)
+- Async background checking (runs every minute)
+- Configurable notification filtering by keywords and tags
+- [[https://github.com/cjennings/chime.el/tree/main/tests][Well-tested]], including with org-gcal
+
+** Installation
+:PROPERTIES:
+:CUSTOM_ID: installation
+:END:
+
+This package is NOT YET available on MELPA.
+
+*** package-vc-install (Emacs 29+)
+
+#+BEGIN_SRC elisp
+(unless (package-installed-p 'chime)
+ (package-vc-install "https://github.com/cjennings/chime.el"))
+#+END_SRC
+
+*** use-package with :vc (Emacs 29+)
+
+#+BEGIN_SRC elisp
+(use-package chime
+ :vc (:url "https://github.com/cjennings/chime.el" :rev :newest)
+ :after alert
+ :commands (chime-mode chime-check)
+ :bind ("C-c A" . chime-check)
+ :config
+ ;; Alert intervals: (minutes . severity) pairs
+ ;; Notify 5 minutes before (medium urgency) and at event time (high urgency)
+ (setq chime-alert-intervals '((5 . medium) (0 . high)))
+
+ ;; Chime sound
+ (setq chime-play-sound t)
+
+ ;; Modeline display (see "Modeline Display" section for more options)
+ (setq chime-enable-modeline t)
+ (setq chime-modeline-lookahead-minutes 60)
+ (setq chime-modeline-format " ⏰ %s")
+
+ ;; Notification settings
+ (setq chime-notification-title "Reminder")
+
+ ;; Don't filter by TODO keywords - notify for all events
+ (setq chime-keyword-whitelist nil)
+ (setq chime-keyword-blacklist nil)
+
+ ;; Only notify for non-done items
+ (setq chime-predicate-blacklist
+ '(chime-done-keywords-predicate))
+
+ ;; Enable chime-mode automatically
+ (chime-mode 1))
+#+END_SRC
+
+*** Manual Installation (Recommended for Development)
+
+#+BEGIN_SRC elisp
+;; Add to load-path
+(add-to-list 'load-path "~/path/to/chime.el")
+
+;; Load and configure
+(require 'chime)
+#+END_SRC
+
+** Quick Start
+:PROPERTIES:
+:CUSTOM_ID: quick-start
+:END:
+
+Minimal configuration:
+
+clone the package somewhere, then add
+
+#+BEGIN_SRC elisp
+ (add-to-list 'load-path "/path/to/chime.el/")
+ (require 'chime)
+
+ ;; Alert intervals: notify 5 minutes before (medium) and at event time (high)
+ (setq chime-alert-intervals '((5 . medium) (0 . high)))
+
+ ;; Enable notifications
+ (chime-mode 1)
+#+END_SRC
+
+** Configuration
+:PROPERTIES:
+:CUSTOM_ID: configuration
+:END:
+
+*** Polling Interval
+
+Control how often chime checks for upcoming events:
+
+#+BEGIN_SRC elisp
+;; Default: check every 60 seconds (1 minute) - recommended for most users
+(setq chime-check-interval 60)
+
+;; More responsive: check every 30 seconds
+(setq chime-check-interval 30)
+
+;; Reduce polling overhead: check every 5 minutes
+(setq chime-check-interval 300)
+#+END_SRC
+
+Lower values make notifications more responsive but increase system load. Higher values reduce polling overhead but may delay notifications slightly.
+
+**Choosing a polling interval:**
+
+- *120-300 seconds (2-5 minutes)*: Okay for reducing system load, but most people require more timely notifications.
+- *60 seconds (default)*: Ideal for most users. Matches org's minute-based timestamps and provides timely notifications with minimal overhead.
+- *30 seconds*: Fine if you want quicker notification delivery. Reasonable resource usage.
+- *15-10 seconds*: Maximum responsiveness, but you're polling 4-6 times more frequently for marginal precision gain on minute-based events.
+- *Below 10 seconds*: Not recommended or supported. Org events are scheduled to the minute. Faster polling provides near-zero benefit while significantly increasing CPU, disk I/O, and battery usage.
+
+**Note:** Changes take effect after restarting chime-mode (=M-x chime-mode= twice, or restart Emacs).
+
+*** Alert Intervals
+
+Set when to receive notifications and their urgency levels using (minutes . severity) pairs:
+
+#+BEGIN_SRC elisp
+;; Single notification 10 minutes before with medium urgency
+(setq chime-alert-intervals '((10 . medium)))
+
+;; Multiple notifications with escalating urgency
+(setq chime-alert-intervals '((10 . low) ;; 10 min before: low urgency
+ (5 . medium) ;; 5 min before: medium urgency
+ (0 . high))) ;; At event time: high urgency
+#+END_SRC
+
+Severity levels (=high=, =medium=, =low=) control notification urgency and may affect how your notification system displays them.
+
+*** Chime Sound
+
+Control the audible chime that plays when notifications appear:
+
+#+BEGIN_SRC elisp
+;; Enable/disable chime sound (default: t)
+(setq chime-play-sound t)
+
+;; Use custom sound file (defaults to bundled chime.wav)
+(setq chime-sound-file "/path/to/your/chime.wav")
+
+;; Disable sound completely (no sound file, no beep)
+(setq chime-sound-file nil)
+#+END_SRC
+
+The package includes a pleasant chime sound in GPL-compatible WAV format. You can use your own sound file if preferred.
+
+*** Modeline Display
+
+Display your next upcoming event in your modeline:
+
+#+BEGIN_SRC elisp
+;; Enable/disable modeline display (default: t)
+(setq chime-enable-modeline t)
+
+;; Show events up to 60 minutes ahead (default: 60)
+(setq chime-modeline-lookahead-minutes 60)
+
+;; Customize the modeline prefix format (default: " ⏰ %s")
+(setq chime-modeline-format " [Next: %s]")
+#+END_SRC
+
+The modeline will display the soonest event within the lookahead window, formatted as:
+- Default: =⏰ Meeting with Team at 02:30 PM (in 15 minutes)=
+- Updates automatically every minute
+
+**** Interactive Modeline Features
+
+The modeline text is interactive - you can click it and hover for more information:
+
+***** Tooltip
+
+Hover your mouse over the modeline event to see a tooltip showing all upcoming events within the lookahead window, grouped by day:
+
+#+BEGIN_EXAMPLE
+Upcoming Events as of Mon Oct 28 2024 @ 02:00 PM
+
+Today, Oct 28:
+─────────────
+Team Meeting at 02:10 PM (in 10 minutes)
+Code Review at 02:30 PM (in 30 minutes)
+Coffee break at 02:45 PM (in 45 minutes)
+
+Tomorrow, Oct 29:
+─────────────
+Sprint Planning at 09:00 AM (tomorrow)
+Quarterly Review at 02:00 PM (tomorrow)
+#+END_EXAMPLE
+
+The tooltip displays up to 5 events by default. Configure the maximum with:
+
+#+BEGIN_SRC elisp
+;; Show up to 10 events in tooltip
+(setq chime-modeline-tooltip-max-events 10)
+
+;; Show all events in lookahead window (beware -- no limit!)
+(setq chime-modeline-tooltip-max-events nil)
+#+END_SRC
+
+***** Tooltip Lookahead Window
+
+The tooltip can show events beyond the modeline lookahead window. By default, it shows events up to 1 year (8760 hours) in the future, while the modeline only shows events within the next hour:
+
+#+BEGIN_SRC elisp
+;; Modeline shows events within next 60 minutes (default)
+(setq chime-modeline-lookahead-minutes 60)
+
+;; Tooltip shows events within next 8760 hours / 1 year (default)
+(setq chime-tooltip-lookahead-hours 8760)
+
+;; Example: Show only today's events in tooltip (24 hours)
+(setq chime-tooltip-lookahead-hours 24)
+
+;; Example: Show events for the next week in tooltip
+(setq chime-tooltip-lookahead-hours 168) ; 7 days × 24 hours
+#+END_SRC
+
+This separation allows you to:
+- Keep the modeline focused on imminent events (tactical view)
+- See a broader timeline in the tooltip (strategic view)
+
+***** Click Actions
+
+The modeline supports two click actions:
+
+- **Left-click**: Opens your calendar in a web browser (if configured)
+- **Right-click**: Jumps directly to the event's org entry in its file
+
+To enable left-click calendar access, set your calendar URL:
+
+#+BEGIN_SRC elisp
+;; Open Google Calendar on left-click
+(setq chime-calendar-url "https://calendar.google.com")
+
+;; Or Outlook Calendar
+(setq chime-calendar-url "https://outlook.office.com/calendar")
+
+;; Or any custom calendar web interface
+(setq chime-calendar-url "https://your-calendar-url")
+#+END_SRC
+
+When no calendar URL is set (default), left-click does nothing. Right-click always jumps to the next event in your org file.
+
+**** Customizing Modeline Content
+
+Control what information appears in the modeline with fine-grained formatting:
+
+***** Notification Text Format
+
+Customize which components are shown:
+
+#+BEGIN_SRC elisp
+;; Default: title, time, and countdown
+(setq chime-notification-text-format "%t at %T (%u)")
+;; → "Meeting with Team at 02:30 PM (in 15 minutes)"
+
+;; Title and time only (no countdown)
+(setq chime-notification-text-format "%t at %T")
+;; → "Meeting with Team at 02:30 PM"
+
+;; Title and countdown only (no time)
+(setq chime-notification-text-format "%t (%u)")
+;; → "Meeting with Team (in 15 minutes)"
+
+;; Title only (minimal)
+(setq chime-notification-text-format "%t")
+;; → "Meeting with Team"
+
+;; Custom separator
+(setq chime-notification-text-format "%t - %T")
+;; → "Meeting with Team - 02:30 PM"
+
+;; Time first
+(setq chime-notification-text-format "%T: %t")
+;; → "02:30 PM: Meeting with Team"
+#+END_SRC
+
+Available placeholders:
+- =%t= - Event title
+- =%T= - Event time (formatted per =chime-display-time-format-string=)
+- =%u= - Time until event (formatted per =chime-time-left-format-*=)
+
+***** Event Time Format
+
+Choose between 12-hour and 24-hour time display:
+
+#+BEGIN_SRC elisp
+;; 12-hour with AM/PM (default)
+(setq chime-display-time-format-string "%I:%M %p")
+;; → "02:30 PM"
+
+;; 24-hour format
+(setq chime-display-time-format-string "%H:%M")
+;; → "14:30"
+
+;; 12-hour without space before AM/PM
+(setq chime-display-time-format-string "%I:%M%p")
+;; → "02:30PM"
+
+;; 12-hour with lowercase am/pm
+(setq chime-display-time-format-string "%I:%M %P")
+;; → "02:30 pm"
+#+END_SRC
+
+Available format codes:
+- =%I= - Hour (01-12, 12-hour format)
+- =%H= - Hour (00-23, 24-hour format)
+- =%M= - Minutes (00-59)
+- =%p= - AM/PM (uppercase)
+- =%P= - am/pm (lowercase)
+
+***** Time-Until Format
+
+Customize how the countdown is displayed:
+
+#+BEGIN_SRC elisp
+;; Default: verbose format
+(setq chime-time-left-format-short "in %M") ; Under 1 hour
+(setq chime-time-left-format-long "in %H %M") ; 1 hour or more
+;; → "in 10 minutes" or "in 1 hour 30 minutes"
+
+;; Compact format
+(setq chime-time-left-format-short "in %mm")
+(setq chime-time-left-format-long "in %hh %mm")
+;; → "in 10m" or "in 1h 30m"
+
+;; Very compact (no prefix)
+(setq chime-time-left-format-short "%mm")
+(setq chime-time-left-format-long "%hh%mm")
+;; → "10m" or "1h30m"
+
+;; Custom "at event time" message
+(setq chime-time-left-format-at-event "NOW!")
+;; → "NOW!" instead of "right now"
+#+END_SRC
+
+Available format codes (from =format-seconds=):
+- =%h= / =%H= - Hours (number only / with unit name)
+- =%m= / =%M= - Minutes (number only / with unit name)
+
+***** Title Truncation
+
+Limit the length of long event titles to conserve modeline space:
+
+#+BEGIN_SRC elisp
+;; No truncation - show full title (default)
+(setq chime-max-title-length nil)
+;; → " ⏰ Very Long Meeting Title That Goes On And On ( in 10m)"
+
+;; Truncate to 25 characters
+(setq chime-max-title-length 25)
+;; → " ⏰ Very Long Meeting Titl... ( in 10m)"
+
+;; Truncate to 15 characters
+(setq chime-max-title-length 15)
+;; → " ⏰ Very Long Me... ( in 10m)"
+#+END_SRC
+
+**Important:** This setting affects *only the event title* (%t), not the icon, time, or countdown. The icon comes from =chime-modeline-format= and is added separately.
+
+The truncation includes the "..." in the character count, so a 15-character limit means up to 12 characters of title plus "...".
+
+Minimum recommended value: 10 characters.
+
+***** Complete Compact Example
+
+For maximum modeline space savings:
+
+#+BEGIN_SRC elisp
+(setq chime-enable-modeline t)
+(setq chime-modeline-lookahead-minutes 60)
+(setq chime-modeline-format " ⏰%s") ; Minimal prefix
+(setq chime-notification-text-format "%t (%u)") ; No time shown
+(setq chime-time-left-format-short "%mm") ; Compact short
+(setq chime-time-left-format-long "%hh%mm") ; Compact long
+(setq chime-max-title-length 20) ; Truncate long titles
+;; Result: "⏰Meeting (10m)" or "⏰Very Long Meeti... (1h30m)"
+#+END_SRC
+
+***** Disabling Modeline Display
+
+#+BEGIN_SRC elisp
+;; Completely disable modeline modifications
+(setq chime-enable-modeline nil)
+
+;; Alternative: set lookahead to 0 (legacy method)
+(setq chime-modeline-lookahead-minutes 0)
+#+END_SRC
+
+*** Notification Settings
+
+#+BEGIN_SRC elisp
+;; Notification title
+(setq chime-notification-title "Reminder")
+
+;; Note: Severity is now configured per-interval in chime-alert-intervals
+;; See "Alert Intervals" section above
+#+END_SRC
+
+*** Filtering
+
+#+BEGIN_SRC elisp
+;; Only notify for specific TODO keywords
+(setq chime-keyword-whitelist '("TODO" "NEXT"))
+
+;; Never notify for these keywords
+(setq chime-keyword-blacklist '("DONE" "CANCELLED"))
+
+;; Only notify for specific tags
+(setq chime-tags-whitelist '("@important"))
+
+;; Never notify for these tags
+(setq chime-tags-blacklist '("someday"))
+#+END_SRC
+
+**** Whitelist and Blacklist Precedence
+
+If the same keyword or tag appears in both a whitelist and blacklist, the **blacklist takes precedence** and the item will be filtered out. This ensures sensitive information cannot accidentally be exposed in notifications.
+
+Examples:
+- Item with =TODO= keyword when =TODO= is in both ~chime-keyword-whitelist~ and ~chime-keyword-blacklist~ → **filtered out** (blacklist wins)
+- Item with =:urgent:= tag when =urgent= is in both ~chime-tags-whitelist~ and ~chime-tags-blacklist~ → **filtered out** (blacklist wins)
+- Item with whitelisted keyword but blacklisted tag → **filtered out** (blacklist wins)
+
+Most users configure either whitelists or blacklists, not both. If you use both, ensure they don't overlap to avoid confusion.
+
+*** All-Day Events
+
+Chime distinguishes between *timed events* (with specific times like =10:00=) and *all-day events* (without times, such as birthdays or holidays).
+
+**** What are All-Day Events?
+
+All-day events are org timestamps without a time component:
+
+#+BEGIN_SRC org
+,* Blake's Birthday
+<2025-12-19 Fri>
+
+,* Holiday: Christmas
+<2025-12-25 Thu>
+
+,* Multi-day Conference
+<2025-11-10 Mon>--<2025-11-13 Thu>
+#+END_SRC
+
+Compare with timed events:
+
+#+BEGIN_SRC org
+,* Team Meeting
+<2025-10-28 Tue 14:30-15:30>
+
+,* Doctor Appointment
+SCHEDULED: <2025-10-30 Thu 10:00>
+#+END_SRC
+
+**** Current Behavior
+
+**Modeline:**
+- All-day events are *never* shown in the modeline
+- Only timed events with specific times appear
+- Rationale: Modeline shows urgent, time-sensitive items
+
+**Notifications:**
+- All-day events can trigger notifications at configured times
+- By default, =chime-day-wide-alert-times= is =nil= (notifications disabled)
+- When set, chime will notify you of all-day events happening *today* at those times
+
+**** Configuring All-Day Event Notifications
+
+To receive notifications for all-day events (like birthdays):
+
+#+BEGIN_SRC elisp
+;; Notify at 8:00 AM for all-day events happening today
+(setq chime-day-wide-alert-times '("08:00"))
+
+;; Multiple notification times
+(setq chime-day-wide-alert-times '("08:00" "17:00")) ; Morning and evening
+
+;; Disable all-day event notifications (default)
+(setq chime-day-wide-alert-times nil)
+#+END_SRC
+
+**Example workflow:**
+1. You have =* Blake's Birthday <2025-12-19 Fri>= in your org file
+2. On December 19th at 8:00 AM, chime notifies: "Blake's Birthday is due or scheduled today"
+3. This gives you a reminder to send birthday wishes or buy a gift
+
+**** Showing Overdue TODOs
+
+Control whether overdue TODO items and past events appear alongside all-day event notifications:
+
+#+BEGIN_SRC elisp
+;; Show overdue items with all-day event notifications (default: t)
+(setq chime-show-any-overdue-with-day-wide-alerts t)
+
+;; Only show today's events, not overdue items from past days
+(setq chime-show-any-overdue-with-day-wide-alerts nil)
+#+END_SRC
+
+**When enabled (default =t=):**
+- Shows today's DEADLINE/SCHEDULED tasks that have passed (e.g., 9am deadline when it's now 2pm)
+- Shows today's all-day events even if you launch Emacs after the alert time (e.g., launch at 10am when alert was 8am)
+- Shows all-day events from past days (e.g., yesterday's birthday, last week's holiday)
+
+**When disabled (=nil=):**
+- Shows today's DEADLINE/SCHEDULED tasks that have passed ✓
+- Shows today's all-day events even if you launch Emacs late ✓
+- Hides all-day events from past days (prevents old birthday/holiday spam) ✓
+
+Most users want the default (=t=) to catch overdue items. Disable it if you only want to see today's events and don't want past birthdays/holidays cluttering notifications.
+
+***** Understanding the Interplay with Alert Times
+
+The relationship between =chime-day-wide-alert-times= and =chime-show-any-overdue-with-day-wide-alerts= can be confusing:
+
+- =chime-day-wide-alert-times= controls **when** notifications fire (e.g., 8:00 AM)
+- =chime-show-any-overdue-with-day-wide-alerts= controls **what happens if you miss that time**
+
+**Example scenario:**
+#+BEGIN_EXAMPLE
+You have:
+ (setq chime-day-wide-alert-times '("08:00"))
+ (setq chime-show-any-overdue-with-day-wide-alerts t)
+
+Today's birthday: * Blake's Birthday <2025-10-28 Tue>
+
+Timeline:
+- 8:00 AM: Chime fires notification "Blake's Birthday is due or scheduled today" ✓
+- You close Emacs at 9:00 AM
+- You relaunch Emacs at 2:00 PM (afternoon)
+- Because overdue alerts are ENABLED (t), chime shows the notification again ✓
+ → This catches you up on today's events you might have missed
+#+END_EXAMPLE
+
+**If you disable overdue alerts:**
+#+BEGIN_EXAMPLE
+ (setq chime-show-any-overdue-with-day-wide-alerts nil)
+
+Same scenario, but now:
+- 8:00 AM: Chime fires notification ✓
+- You close Emacs at 9:00 AM
+- You relaunch Emacs at 2:00 PM
+- Because overdue alerts are DISABLED (nil), chime STILL shows today's birthday ✓
+ → Today's events are always shown regardless of this setting
+ → This setting only hides events from PAST DAYS (yesterday, last week, etc.)
+#+END_EXAMPLE
+
+**Key insight:** You'll always see today's all-day events when you launch Emacs, even if you missed the configured alert time. The =chime-show-any-overdue-with-day-wide-alerts= setting only controls whether you see events from *previous days*.
+
+**** Common Use Cases
+
+**Birthdays:**
+#+BEGIN_SRC org
+,* Blake Michael's Birthday
+<2025-02-20 Thu>
+#+END_SRC
+
+With =chime-day-wide-alert-times= set to ='("08:00")=, you'll get a morning reminder on the birthday.
+
+**Holidays:**
+#+BEGIN_SRC org
+,* Holiday: Thanksgiving
+<2025-11-27 Thu>
+#+END_SRC
+
+**Multi-day Events:**
+#+BEGIN_SRC org
+,* Conference: EmacsCon 2025
+<2025-11-10 Mon>--<2025-11-13 Thu>
+#+END_SRC
+
+You'll receive notifications on each day of the conference at your configured alert times.
+
+**** Integration with org-contacts
+
+If you use [[https://repo.or.cz/org-contacts.git][org-contacts]] for managing contacts and birthdays, chime provides built-in integration to ensure birthdays appear in your agenda and trigger notifications.
+
+**The Problem:**
+
+Org-contacts stores birthdays as properties (=:BIRTHDAY: 1985-03-15=) and uses diary sexps (=%%(org-contacts-anniversaries)=) to display them in your agenda. However, chime's async subprocess doesn't have org-contacts loaded, causing "Bad sexp" errors and preventing birthdays from appearing.
+
+**The Solution:**
+
+Chime provides a two-part solution:
+
+1. **One-time conversion** for existing contacts
+2. **Automatic capture template** for new contacts
+
+Both approaches add plain org timestamps alongside the =:BIRTHDAY:= property, preserving vCard export compatibility while enabling chime notifications.
+
+**Step 1: Convert Existing Contacts**
+
+Use the included conversion script to add birthday timestamps to your existing contacts file:
+
+#+BEGIN_SRC elisp
+;; Load conversion script
+(require 'convert-org-contacts-birthdays
+ (expand-file-name "convert-org-contacts-birthdays.el"
+ (file-name-directory (locate-library "chime"))))
+
+;; Convert your contacts file IN-PLACE (creates timestamped backup)
+M-x chime-convert-contacts-in-place RET ~/org/contacts.org RET
+#+END_SRC
+
+This modifies your contacts.org file, transforming:
+
+#+BEGIN_SRC org
+,* Alice Anderson
+:PROPERTIES:
+:EMAIL: alice@example.com
+:BIRTHDAY: 1985-03-15
+:END:
+#+END_SRC
+
+Into:
+
+#+BEGIN_SRC org
+,* Alice Anderson
+:PROPERTIES:
+:EMAIL: alice@example.com
+:BIRTHDAY: 1985-03-15
+:END:
+<1985-03-15 Sat +1y>
+#+END_SRC
+
+**Safety:** The script creates a timestamped backup (=contacts.org.backup-YYYY-MM-DD-HHMMSS=) before making any changes.
+
+After conversion, comment out the diary sexp in your schedule file:
+
+#+BEGIN_SRC org
+# %%(org-contacts-anniversaries)
+#+END_SRC
+
+**Step 2: Enable Capture Template for New Contacts**
+
+To automatically add birthday timestamps when capturing new contacts:
+
+#+BEGIN_SRC elisp
+;; Enable org-contacts integration
+(setq chime-org-contacts-file "~/org/contacts.org")
+
+;; Optional: customize capture key (default: "C")
+(setq chime-org-contacts-capture-key "C")
+
+;; Optional: customize heading (default: "Contacts")
+(setq chime-org-contacts-heading "Contacts")
+#+END_SRC
+
+This adds an org-capture template that:
+- Prompts for contact details (name, email, phone, birthday, etc.)
+- Automatically inserts a yearly repeating timestamp if birthday is provided
+- Preserves the =:BIRTHDAY:= property for vCard export
+
+**Using the Capture Template:**
+
+1. Press =C-c c= (or your org-capture binding)
+2. Press =C= (or your configured capture key)
+3. Fill in contact information
+4. Birthday timestamps are added automatically on save
+
+**Template Fields:**
+- Name, Email, Phone, Address
+- Birthday (YYYY-MM-DD or MM-DD format)
+- Nickname, Company, Title, Website
+- Note (instead of free-form text below properties)
+
+**Disabling the Integration:**
+
+Set =chime-org-contacts-file= to =nil= to disable the capture template:
+
+#+BEGIN_SRC elisp
+(setq chime-org-contacts-file nil) ; Disabled by default
+#+END_SRC
+
+**Result:**
+
+After setup, birthdays will:
+- ✓ Appear in org-agenda
+- ✓ Trigger chime notifications at configured times
+- ✓ Work with chime's async subprocess (no "Bad sexp" errors)
+- ✓ Still export to vCard via org-contacts
+- ✓ Automatically include timestamps for new contacts
+
+** Usage
+:PROPERTIES:
+:CUSTOM_ID: usage
+:END:
+
+*** Basic Event with Timestamp
+
+#+BEGIN_SRC org
+,* Meeting with Team
+<2025-10-25 Sat 14:00>
+#+END_SRC
+
+Will notify at 14:00 (if =chime-alert-intervals= includes =(0 . severity)=).
+
+*** Events with SCHEDULED or DEADLINE
+
+#+BEGIN_SRC org
+,* TODO Call Doctor
+SCHEDULED: <2025-10-25 Sat 10:00>
+#+END_SRC
+
+*** Repeating Events
+
+Repeating timestamps are fully supported:
+
+#+BEGIN_SRC org
+,* TODO Weekly Team Meeting
+SCHEDULED: <2025-10-25 Sat 14:00 +1w>
+
+,* TODO Daily Standup
+SCHEDULED: <2025-10-25 Sat 09:00 +1d>
+
+,* TODO Review Email
+SCHEDULED: <2025-10-25 Sat 08:00 .+1d>
+#+END_SRC
+
+Supported repeaters:
+- =+1w= - Repeat weekly from original date
+- =.+1d= - Repeat daily from completion
+- =++1w= - Repeat weekly from scheduled date
+
+** Known Limitations
+:PROPERTIES:
+:CUSTOM_ID: known-limitations
+:END:
+
+*** S-expression Diary Entries Are Not Supported
+
+Note: org-contacts users will quickly discover the above unsupported format is how org-contacts integrate birthdays into your calendar. If you use org-contacts, you will not be automatically notified about your contacts birthdays.
+
+Specifically, this format is *not supported*:
+
+#+BEGIN_SRC org
+,* TODO Daily Standup
+SCHEDULED: <%%(memq (calendar-day-of-week date) '(1 2 3 4 5))>
+#+END_SRC
+
+For those using this format outside of org-contacts, your workaround is to use standard repeating timestamps instead:
+
+#+BEGIN_SRC org
+,* TODO Daily Standup
+SCHEDULED: <2025-10-24 Fri 09:00 +1d>
+#+END_SRC
+
+For Monday-Friday events, you can either:
+1. Accept weekend notifications (mark as DONE on weekends)
+2. Create 5 separate entries, one for each weekday with =+1w= repeater
+
+** Full Example Configuration
+:PROPERTIES:
+:CUSTOM_ID: full-example-configuration
+:END:
+
+#+BEGIN_SRC elisp
+ (use-package chime
+ :vc (:url "https://github.com/cjennings/chime.el" :rev :newest)
+ :after alert
+ :commands (chime-mode chime-check)
+ :config
+ ;; Polling interval: check every 60 seconds (default)
+ (setq chime-check-interval 60)
+
+ ;; Alert intervals: 5 minutes before (medium) and at event time (high)
+ (setq chime-alert-intervals '((5 . medium) (0 . high)))
+
+ ;; Chime sound
+ (setq chime-play-sound t)
+ ;; Uses bundled chime.wav by default
+
+ ;; Modeline display - compact format
+ (setq chime-enable-modeline t)
+ (setq chime-modeline-lookahead-minutes 120) ; Show events 2 hrs ahead
+ (setq chime-modeline-format " ⏰%s") ; Minimal prefix
+ (setq chime-notification-text-format "%t (%u)") ; Title + countdown only
+ (setq chime-display-time-format-string "%H:%M") ; 24-hour time
+ (setq chime-time-left-format-short "in %mm") ; Compact: "in 5m"
+ (setq chime-time-left-format-long "%hh%mm") ; Compact: "1h30m"
+ (setq chime-time-left-format-at-event "NOW!") ; Custom at-event message
+
+ ;; Notification settings
+ (setq chime-notification-title "Reminder")
+
+ ;; Don't filter by TODO keywords - notify for all events
+ (setq chime-keyword-whitelist nil)
+ (setq chime-keyword-blacklist nil)
+
+ ;; Only notify for non-done items
+ (setq chime-predicate-blacklist
+ '(chime-done-keywords-predicate))
+
+ ;; Enable chime-mode automatically
+ (chime-mode 1))
+#+END_SRC
+
+** Manual Check
+:PROPERTIES:
+:CUSTOM_ID: manual-check
+:END:
+
+You can manually trigger a notification check:
+
+#+BEGIN_SRC elisp
+M-x chime-check
+#+END_SRC
+
+** Troubleshooting
+:PROPERTIES:
+:CUSTOM_ID: troubleshooting
+:END:
+
+*** No notifications appearing
+
+1. Verify chime-mode is enabled: =M-: chime-mode=
+2. Check that alert is configured correctly:
+ #+BEGIN_SRC elisp
+ (setq alert-default-style 'libnotify) ; or 'notifications on some systems
+ #+END_SRC
+3. Manually test: =M-x chime-check=
+4. Check =*Messages*= buffer for error messages
+
+*** No sound playing
+
+1. Verify sound is enabled: =M-: chime-play-sound= should return =t=
+2. Check sound file exists: =M-: (file-exists-p chime-sound-file)=
+3. Test sound directly: =M-: (play-sound-file chime-sound-file)=
+4. Ensure your system has audio support configured
+
+*** Events not being detected
+
+1. Ensure files are in =org-agenda-files=
+2. Verify timestamps have time components: =<2025-10-25 Sat 14:00>= not =<2025-10-25 Sat>=
+3. Check filtering settings (keyword/tag whitelist/blacklist)
+4. Timestamps support both 24-hour (=14:00=) and 12-hour (=2:00pm=, =2:00 PM=) formats
+
+*** org-contacts diary sexp errors
+
+If you see errors like "Bad sexp at line 2: (let ((entry) (date '(10 29 2025))) (org-contacts-anniversaries))" in your =*emacs:err*= buffer, this is because chime's async subprocess doesn't have org-contacts loaded.
+
+*Symptoms:*
+- No events appear in modeline despite having scheduled items
+- =*emacs:err*= buffer shows "Bad sexp" errors for org-contacts
+- Errors appear repeatedly (every minute during chime checks)
+
+*Solution 1: Load org-contacts in your config (Recommended)*
+
+Add this to your config BEFORE chime loads:
+
+#+BEGIN_SRC elisp
+(require 'org-contacts nil t) ; Load if available, don't error if missing
+#+END_SRC
+
+Chime will automatically load org-contacts in its async subprocess if it's installed.
+
+*Solution 2: Comment out the sexp line*
+
+In your org file (usually =schedule.org=), comment out or remove:
+
+#+BEGIN_SRC org
+# %%(org-contacts-anniversaries)
+#+END_SRC
+
+*Solution 3: Convert to plain timestamps*
+
+Use the conversion script to generate plain org entries from your contacts.
+
+*Safety Note:* This script is READ-ONLY. It reads from your org-contacts database but never modifies it. The output is written to a new file.
+
+*Step-by-step process:*
+
+1. (Optional but recommended) Backup your org files
+
+2. Load and run the conversion script:
+ #+BEGIN_SRC elisp
+ ;; Load the conversion script (included in chime.el repo)
+ (require 'convert-org-contacts-birthdays
+ (expand-file-name "convert-org-contacts-birthdays.el"
+ (file-name-directory (locate-library "chime"))))
+
+ ;; Convert contacts to plain org entries
+ M-x chime-convert-contacts-to-file RET ~/birthdays.org RET
+ #+END_SRC
+
+ This creates a NEW file (~/birthdays.org) with entries like:
+ #+BEGIN_SRC org
+ *** John Doe's Birthday
+ <2026-03-15 Sun +1y>
+ #+END_SRC
+
+3. When prompted, allow the script to add =birthdays.org= to =org-agenda-files=
+ (or add it manually later)
+
+4. Comment out or remove the sexp line from your schedule file:
+ #+BEGIN_SRC org
+ # %%(org-contacts-anniversaries)
+ #+END_SRC
+
+5. Restart chime or run =M-x chime-check= to verify birthdays appear without errors
+
+*Why this happens:*
+
+Org-mode diary sexps like =%%(org-contacts-anniversaries)= are dynamic expressions evaluated during agenda building. Chime runs agenda building in an async subprocess for performance, but that subprocess needs the generating package (org-contacts) loaded. Chime includes org-contacts as a soft dependency, but it must be installed for the sexp to work.
+
+*** Multiple Emacs instances producing duplicate notifications
+
+If you receive duplicate notifications for every event, you likely have multiple Emacs processes running with chime-mode enabled.
+
+*Symptoms:*
+- Receiving 2 (or more) identical notifications for each event
+- Notifications appear at the same time but as separate alerts
+
+*Common Scenario:*
+
+You're running chime with an emacsclient connected to an emacs daemon (=emacs --daemon=), then launch a separate Emacs process from the command line. Each process runs its own instance of chime-mode, resulting in duplicate notifications.
+
+*Example:*
+#+BEGIN_SRC bash
+# Start emacs daemon with chime-mode enabled
+emacs --daemon
+
+# Connect with emacsclient (uses daemon - chime runs here)
+emacsclient -c
+
+# Later, accidentally launch standalone Emacs process
+emacs & # This creates a SECOND chime instance!
+#+END_SRC
+
+*Solution:*
+
+1. Check for multiple Emacs processes:
+ #+BEGIN_SRC bash
+ ps aux | grep emacs
+ #+END_SRC
+
+2. Decide on your preferred architecture:
+ - **Option A**: Use emacs daemon + emacsclient exclusively (recommended for consistency)
+ - **Option B**: Use standalone Emacs processes only (simpler, but separate configs)
+
+3. Kill extra processes:
+ #+BEGIN_SRC bash
+ # To stop the daemon
+ emacsclient -e "(kill-emacs)"
+
+ # Or kill specific process by PID
+ kill <PID>
+ #+END_SRC
+
+4. Verify only one Emacs process is running after cleanup
+
+*Prevention:*
+
+- If using emacs daemon, always connect with =emacsclient -c= instead of launching =emacs=
+- Add shell aliases to prevent accidents:
+ #+BEGIN_SRC bash
+ alias emacs="emacsclient -c -a ''" # Auto-start daemon if not running
+ #+END_SRC
+
+** Requirements
+:PROPERTIES:
+:CUSTOM_ID: requirements
+:END:
+
+- Emacs 26.1+
+- Org-mode 9.0+
+- =alert= package
+- =dash= package
+- =async= package
+
+** Testing
+:PROPERTIES:
+:CUSTOM_ID: testing
+:END:
+
+Chime includes a comprehensive test suite with 339 tests covering all functionality. For detailed information about running tests, test architecture, and development workflows, see [[file:TESTING.org][TESTING.org]].
+
+Quick start:
+#+BEGIN_SRC bash
+cd tests
+make test # Run all tests
+make test-file FILE=modeline # Run specific test file
+#+END_SRC
+
+** License
+:PROPERTIES:
+:CUSTOM_ID: license
+:END:
+
+GPL-3.0
+
+** Credits
+:PROPERTIES:
+:CUSTOM_ID: credits
+:END:
+
+All credit and thanks should go to Artem Khramov for his work on [[https://github.com/akhramov/org-wild-notifier.el][org-wild-notifier]], which served me well for some time. Sadly, the author deprecated org-wild-notifier on Aug 2, 2025 in favor of [[https://github.com/spegoraro/org-alert][org-alert]]. I begain fixing bugs and enhancing the feature set into what is now CHIME.
+
+I plan to maintain this in appreciation and gratitude of Artem's work, and for the larger Emacs community.
+
+** Migration from org-wild-notifier
+:PROPERTIES:
+:CUSTOM_ID: migration-from-org-wild-notifier
+:END:
+
+If you're migrating from org-wild-notifier, you'll need to update your configuration:
+
+1. Change package name:
+ - =(require 'org-wild-notifier)= → =(require 'chime)=
+
+2. Update all configured variable names:
+ - =org-wild-notifier-*= → =chime-*=
+
+3. Update configured function names:
+ - =org-wild-notifier-mode= → =chime-mode=
+ - =org-wild-notifier-check= → =chime-check=
+
+4. Note: The =:WILD_NOTIFIER_NOTIFY_BEFORE:= / =:CHIME_NOTIFY_BEFORE:= property has been removed. Use the global =chime-alert-intervals= variable instead (e.g., =(setq chime-alert-intervals '((30 . low) (15 . medium) (5 . medium) (0 . high)))=).
+
+** Development
+:PROPERTIES:
+:CUSTOM_ID: development
+:END:
+
+For information about running tests, test architecture, and development workflows, see [[file:TESTING.org][TESTING.org]].
+