From 0772736d2bca36472f623d5258784f41db9b4f9a Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 10 May 2026 16:36:31 -0500 Subject: docs: split detailed user docs out of readme --- README.org | 1086 +++------------------------------------------- docs/ARCHITECTURE.org | 259 +++++++++++ docs/CONFIGURATION.org | 420 ++++++++++++++++++ docs/INTEGRATIONS.org | 109 +++++ docs/TROUBLESHOOTING.org | 180 ++++++++ 5 files changed, 1019 insertions(+), 1035 deletions(-) create mode 100644 docs/ARCHITECTURE.org create mode 100644 docs/CONFIGURATION.org create mode 100644 docs/INTEGRATIONS.org create mode 100644 docs/TROUBLESHOOTING.org diff --git a/README.org b/README.org index 1a30644..7f34944 100644 --- a/README.org +++ b/README.org @@ -1,6 +1,6 @@ * CHIME Heralds Imminent Modeline Events -[[#features][Features]] | [[#installation][Installation]] | [[#quick-start][Quick Start]] | [[#configuration][Configuration]] | [[#integrations][Integrations]] | [[#troubleshooting][Troubleshooting]] | [[#testing][Development & Testing]] | [[#history][History]] +[[#features][Features]] | [[#installation][Installation]] | [[#quick-start][Quick Start]] | [[#documentation][Documentation]] | [[#development--testing][Development & Testing]] | [[#history][History]] [[https://www.gnu.org/software/emacs/][file:assets/made-for-emacs-badge.svg]] @@ -14,27 +14,16 @@ CHIME (backronym: *CHIME Heralds Imminent Modeline Events*) makes sure your org- :CUSTOM_ID: features :END: -- *Visual notifications* with customizable alert times -- *Audible chime sound* when notifications are displayed (bundled WAV or custom file) -- *Customizable notification icon* for desktop notifications -- *Interactive modeline display* of next upcoming event, highly configurable: - - 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) -- *All-day event support* (birthdays, holidays, multi-day events) - - Configurable daily notification times for all-day events - - Advance notice (e.g., "Blake's birthday is tomorrow") - - Show/hide all-day events in tooltip -- Configurable notification filtering by keywords, tags, and custom predicates -- Configurable startup delay for async org-agenda-files initialization +- Desktop notifications with configurable alert intervals and urgency +- Optional audible chime sound, using the bundled WAV or a custom file +- Interactive modeline display for the next timed event +- Hover tooltip with upcoming events grouped by day +- Left-click calendar URL and right-click jump-to-org-entry actions +- SCHEDULED, DEADLINE, plain timestamp, and repeating timestamp support +- 12-hour and 24-hour org timestamp parsing +- All-day event notifications for birthdays, holidays, and multi-day events +- Filtering by TODO keywords, tags, and custom predicates +- Async agenda checks so Emacs stays responsive - [[https://github.com/cjennings/chime/tree/main/tests][Well-tested]], including with org-gcal ** Installation @@ -48,8 +37,6 @@ Not on MELPA yet, but coming soon. *** package-vc-install (Emacs 29+) -The quickest way to try it out: - #+BEGIN_SRC elisp (unless (package-installed-p 'chime) (package-vc-install "https://github.com/cjennings/chime")) @@ -61,41 +48,26 @@ The quickest way to try it out: (use-package chime :vc (:url "https://github.com/cjennings/chime" :rev :newest) :after alert - :commands (chime-mode chime-check) + :commands (chime-mode chime-check chime-refresh-modeline) :bind ("C-c A" . chime-check) :config - ;; Alert intervals: (minutes . severity) pairs - ;; Notify 5 minutes before (medium urgency) and at event time (high urgency) + ;; Notify 5 minutes before and at event time. (setq chime-alert-intervals '((5 . medium) (0 . high))) - ;; Chime sound (set to nil to disable, or to a path for a custom sound) - ;; (setq chime-sound-file nil) - - ;; Modeline display (see "Modeline Display" section for more options) - (setq chime-enable-modeline t) + ;; Keep the modeline focused on imminent events. (setq chime-modeline-lookahead-minutes 120) - (setq chime-modeline-format " ⏰ %s") - - ;; Notification settings - (setq chime-notification-title "Reminder") + (setq chime-tooltip-lookahead-hours 168) - ;; Filtering — leave nil to notify for everything that isn't excluded - (setq chime-include-filters nil) - - ;; Default exclude rules: skip done items and declined invitations. - ;; (Both predicates ship with chime; this is the package default.) + ;; Default exclude rules skip done items and declined invitations. (setq chime-exclude-filters '((predicates . (chime-done-keywords-predicate chime-declined-events-predicate)))) - ;; Enable chime-mode automatically (chime-mode 1)) #+END_SRC *** straight.el -If you're using straight, you probably don't need me to tell you what to do, but here it is anyway: - #+BEGIN_SRC elisp (straight-use-package '(chime :type git :host github :repo "cjennings/chime")) @@ -113,1035 +85,88 @@ If you're using straight, you probably don't need me to tell you what to do, but git clone https://github.com/cjennings/chime.git ~/path/to/chime #+END_SRC -Then add the following to your init file: - #+BEGIN_SRC elisp (add-to-list 'load-path "~/path/to/chime") (require 'chime) (chime-mode 1) #+END_SRC -Restart Emacs, or evaluate those three lines with =M-x eval-buffer= to start chime immediately. - - ** Quick Start :PROPERTIES: :CUSTOM_ID: quick-start :END: -Once you've installed chime using one of the methods above, the only thing you actually need is: +Once you've installed chime, the only thing you actually need is: #+BEGIN_SRC elisp (chime-mode 1) #+END_SRC -That's it. The defaults are sensible: chime checks your =org-agenda-files= every 60 seconds, notifies you 10 minutes before events (medium urgency) and again at event time (high urgency), plays the bundled chime sound, and shows your next event in the modeline with a ⏰ icon. Make sure your calendar org files are in =org-agenda-files= — no agenda files, no notifications. - -Once chime-mode is on, a ⏰ icon appears in your modeline followed by your next upcoming event. Here's how to interact with it: - -- *Hover* over the modeline text to see a tooltip listing all upcoming events, grouped by day -- *Left-click* opens your calendar in a browser (if you've set ~chime-calendar-url~) -- *Right-click* jumps to the next event's entry in its org file - -When an event is approaching, you'll get a desktop notification and an audible chime. Everything — alert timing, sounds, modeline format, filtering — is configurable in the [[#configuration][Configuration]] section below. - -*** Manual Refresh Commands +The defaults are intentionally usable: chime checks =org-agenda-files= every 60 seconds, notifies 10 minutes before events and again at event time, plays the bundled chime sound, and shows the next timed event in the modeline. Make sure your calendar org files are in =org-agenda-files=. -If you've just added or changed events and don't want to wait for the next polling cycle: +Modeline interaction: -#+BEGIN_SRC elisp -M-x chime-check -#+END_SRC +- *Hover* to see upcoming events grouped by day +- *Left-click* to open =chime-calendar-url=, when configured +- *Right-click* to jump to the next event's org entry -To update the modeline display *without* sending notifications (useful after syncing your calendar with org-gcal): +Manual commands: #+BEGIN_SRC elisp -M-x chime-refresh-modeline +M-x chime-check ; refresh and send due notifications +M-x chime-refresh-modeline ; refresh modeline only, without notifications +M-x chime-validate-configuration #+END_SRC -** Configuration Options +** Documentation :PROPERTIES: -:CUSTOM_ID: configuration +:CUSTOM_ID: documentation :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) -#+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)*: For the "I'll get to it eventually" crowd. -- *60 seconds (default)*: The sweet spot. Org timestamps are minute-granularity anyway. -- *30 seconds*: For the anxious. Negligible resource cost, negligible benefit. -- *10-15 seconds*: You will NOT be 50 seconds more prepared for your meeting. -- *Below 10 seconds*: Your laptop fan will remind you of upcoming events before chime does. - -*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 -;; Default: 10 minutes before (medium) and at event time (high) -(setq chime-alert-intervals '((10 . medium) (0 . high))) - -;; Single notification 10 minutes before -(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=) are passed to [[https://github.com/jwiegley/alert][alert.el]], which maps them to your notification daemon's urgency levels. What that looks like depends on your setup — dunst shows =high= notifications in red and keeps them on screen until dismissed, while =low= ones fade after a timeout. On macOS, the distinction is subtler (sound vs no sound). The exact behavior is up to your alert style and daemon config. - -*** Chime Sound - -Control the audible chime that plays when notifications appear: - -#+BEGIN_SRC elisp - ;; Disable sound entirely - (setq chime-sound-file nil) - - ;; Use your own custom sound file - (setq chime-sound-file "/path/to/your/chime.wav") - ;; (setq chime-sound-file "~/Music/in-a-gadda-da-vida-full-17-minutes.wav") ; don't - - ;; Disable sound completely (no sound file = no beep) - (setq chime-sound-file nil) -#+END_SRC - -A bundled chime.wav is included (GPL-licensed). Swap in your own WAV if you prefer — [[https://freesound.org][Freesound]] (CC-licensed samples, filter by license) and [[https://sonniss.com/gameaudiogdc][Sonniss GDC]] (royalty-free annual packs) are good places to find short notification sounds. Chime uses Emacs's built-in =play-sound-file=, which supports WAV and AU formats. MP3, OGG, and other formats won't work. - -*** Notification Icon - -Set a custom icon for desktop notifications: - -#+BEGIN_SRC elisp -;; Use a custom notification icon -(setq chime-notification-icon "/path/to/icon.png") - -;; No custom icon (default: nil, uses system default) -(setq chime-notification-icon nil) -#+END_SRC - -The icon is passed straight to your notification daemon via alert.el. PNG is the safest choice. Most Linux daemons (dunst, mako, swaync) accept PNG, SVG, and anything GdkPixbuf can load. macOS uses its own icon handling. There are no strict size requirements, but 48x48 to 256x256 pixels is the sweet spot — anything larger gets scaled down anyway. - -*** Startup Delay - -Control how long chime waits before the first event check after enabling =chime-mode=. This allows org-agenda-files and related infrastructure to finish loading: - -#+BEGIN_SRC elisp -;; Default: wait 10 seconds before first check -(setq chime-startup-delay 10) - -;; Faster startup (if you know org is ready) -(setq chime-startup-delay 5) - -;; Increase if you always see "found 0 events" message on startup, then it corrects itself later -(setq chime-startup-delay 20) -#+END_SRC - -*** 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 2 hours ahead (default: 120) -(setq chime-modeline-lookahead-minutes 120) - -;; 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: =⏰ Argue About Tabs vs Spaces at 02:30 PM (in 15 minutes)= -- Updates automatically every minute - -**** Minor mode lighter - -By default, the minor mode lighter is empty — the =⏰= event display already tells you chime is running. If you want a separate indicator in the minor modes list: - -#+BEGIN_SRC elisp -;; Show "Chime" in the minor modes list -(setq chime-modeline-lighter " Chime") - -;; Or an emoji -(setq chime-modeline-lighter " 🔔") -#+END_SRC - -**** No-Events Text - -Control what appears in the modeline when no events are within the lookahead window: - -#+BEGIN_SRC elisp -;; Default: alarm icon -(setq chime-modeline-no-events-text " ⏰") - -;; Muted bell -(setq chime-modeline-no-events-text " 🔕") - -;; Show nothing (clean modeline) -(setq chime-modeline-no-events-text nil) - -;; Custom text -(setq chime-modeline-no-events-text " No events") -#+END_SRC - -This only applies when events exist beyond the lookahead window. If there are no events at all, the modeline is always empty. - -**** Interactive Modeline Features - -The modeline text is interactive. Left-click opens your calendar in a browser, right-click jumps to the event in its org file, and hovering shows a tooltip with all upcoming events. - -***** 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: -───────────── -Argue About Tabs vs Spaces at 02:10 PM (in 10 minutes) -Explain Why You Used goto at 02:30 PM (in 30 minutes) -Find Out Who Keeps Stealing Your Mug at 02:45 PM (in 45 minutes) - -Tomorrow, Oct 29: -───────────── -Sprint Planning (Again) at 09:00 AM (tomorrow) -Quarterly Review (Bring Snacks) 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 - -Customize the tooltip header format: - -#+BEGIN_SRC elisp -;; Default: "Upcoming Events as of Tue Nov 04 2025 @ 08:25 PM" -(setq chime-tooltip-header-format "Upcoming Events as of %a %b %d %Y @ %I:%M %p") - -;; 24-hour time -(setq chime-tooltip-header-format "Upcoming Events as of %a %b %d %Y @ %H:%M") - -;; Minimal header -(setq chime-tooltip-header-format "Events — %a %b %d") -#+END_SRC - -Uses =format-time-string= codes (=%a= weekday, =%b= month, =%d= day, =%Y= year, =%I= 12-hour, =%H= 24-hour, =%M= minutes, =%p= AM/PM). See the [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Time-Parsing.html][Emacs manual on time parsing]] for the full list. - -***** Tooltip Lookahead Window +- [[file:docs/CONFIGURATION.org][Configuration]] — alert intervals, modeline, tooltip, filtering, all-day events, and advanced settings +- [[file:docs/ARCHITECTURE.org][Architecture]] — state flow, async retrieval, event data structures, and subsystem boundaries +- [[file:docs/INTEGRATIONS.org][Integrations]] — org-gcal and org-contacts setup notes +- [[file:docs/TROUBLESHOOTING.org][Troubleshooting]] — debug mode, missing notifications, sound issues, event detection, and duplicate notifications +- [[file:TESTING.org][Testing]] — running and writing the ERT test suite -The tooltip can show events beyond the modeline lookahead window. By default, it shows events up to 1 week (168 hours) in the future, while the modeline only shows events within the next hour: +*** Minimal Configuration Examples -#+BEGIN_SRC elisp -;; Modeline shows events within next 2 hours (default: 120) -(setq chime-modeline-lookahead-minutes 120) - -;; Tooltip shows events within next 168 hours / 1 week (default) -(setq chime-tooltip-lookahead-hours 168) - -;; Example: Show only today's events in tooltip (24 hours) -(setq chime-tooltip-lookahead-hours 24) - -;; Example: Show events for the next month -(setq chime-tooltip-lookahead-hours 720) ; 30 days × 24 hours - -;; Example: Show all events for the next year (may slow checks for large org collections) -(setq chime-tooltip-lookahead-hours 8760) -#+END_SRC - -Note: larger values increase the agenda span fetched by the async subprocess, which can slow event checks if you have many org files. - -This way the modeline stays focused on what's imminent, while the tooltip gives you a wider view of what's coming up. - -***** 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: +Notify at event time only: #+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") +(setq chime-alert-intervals '((0 . high))) #+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: +Use a compact modeline: #+BEGIN_SRC elisp -;; Default: title, time, and countdown -(setq chime-notification-text-format "%t at %T (%u)") -;; → "Argue About Tabs vs Spaces at 02:30 PM (in 15 minutes)" - -;; Title and time only (no countdown) -(setq chime-notification-text-format "%t at %T") -;; → "Argue About Tabs vs Spaces at 02:30 PM" - -;; Title and countdown only (no time) +(setq chime-modeline-format " ⏰ %s") (setq chime-notification-text-format "%t (%u)") -;; → "Argue About Tabs vs Spaces (in 15 minutes)" - -;; Title only (minimal) -(setq chime-notification-text-format "%t") -;; → "Argue About Tabs vs Spaces" - -;; Custom separator -(setq chime-notification-text-format "%t - %T") -;; → "Argue About Tabs vs Spaces - 02:30 PM" - -;; Time first -(setq chime-notification-text-format "%T: %t") -;; → "02:30 PM: Argue About Tabs vs Spaces" -#+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-formats=) - -***** 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: - -All three formats live in one alist, =chime-time-left-formats=, with three keys: -=at-event= (literal string when the event has arrived), =short= -(=format-seconds= template for under 1 hour), and =long= (template for 1 hour -or more). - -#+BEGIN_SRC elisp -;; Default: verbose format -(setq chime-time-left-formats - '((at-event . "right now") - (short . "in %M") - (long . "in %H %M"))) -;; → "in 10 minutes" or "in 1 hour 30 minutes" - -;; Compact format -(setq chime-time-left-formats - '((at-event . "right now") - (short . "in %mm") - (long . "in %hh %mm"))) -;; → "in 10m" or "in 1h 30m" - -;; Very compact (no prefix) (setq chime-time-left-formats - '((at-event . "right now") - (short . "%mm") - (long . "%hh%mm"))) -;; → "10m" or "1h30m" - -;; Tweak just one key — `setf' on `alist-get' is fine for one-shots -(setf (alist-get 'at-event chime-time-left-formats) "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) -;; → " ⏰ Retrospective on Why the Last Retrospective Failed ( in 10m)" - -;; Truncate to 25 characters -(setq chime-max-title-length 25) -;; → " ⏰ Retrospective on Why... ( in 10m)" - -;; Truncate to 15 characters -(setq chime-max-title-length 15) -;; → " ⏰ Retrospect... ( 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-formats ; Compact countdown - '((at-event . "right now") + '((at-event . "now") (short . "%mm") (long . "%hh%mm"))) -(setq chime-max-title-length 20) ; Truncate long titles -;; Result: "⏰ Dentist (10m)" or "⏰ Retrospective o... (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 - -Two alists control which events trigger notifications: -=chime-include-filters= (must match) and =chime-exclude-filters= (must -not match). Each takes the same shape: - -#+BEGIN_SRC elisp -'((keywords . ("TODO" "NEXT")) ; org TODO keywords - (tags . ("work" "urgent")) ; org tags - (predicates . (my-predicate-fn))) ; functions taking a marker -#+END_SRC - -When =chime-include-filters= is nil (the default), every event passes the -include phase. When it's set, an event must match at least one listed -value in any one entry. =chime-exclude-filters= then drops anything -matching any of its entries. +Disable sound: #+BEGIN_SRC elisp -;; Only notify for specific TODO keywords and tags -(setq chime-include-filters - '((keywords . ("TODO" "NEXT")) - (tags . ("important")))) - -;; Never notify for these keywords or tags -(setq chime-exclude-filters - '((keywords . ("DONE" "CANCELLED")) - (tags . ("someday")) - (predicates . (chime-done-keywords-predicate - chime-declined-events-predicate)))) +(setq chime-sound-file nil) #+END_SRC -**** Include and Exclude Precedence - -If the same keyword or tag appears in both filters, the *exclude wins* -and the item is filtered out. - -Examples: -- Item with =TODO= keyword when =TODO= appears under =keywords= in both filters → *filtered out* -- Item with =:urgent:= tag when =urgent= appears under =tags= in both filters → *filtered out* -- Item with an included keyword but an excluded tag → *filtered out* - -Most users set one or the other, not both. If you set both, ensure -they don't overlap to avoid confusion. - -**** Custom Predicate Filtering - -Keywords and tags cover most filtering needs, but sometimes you want -logic they can't express — like silencing work events on weekends, or -only getting notified about events in a specific file. - -For filtering logic that goes beyond keywords and tags, you can write -custom predicate functions. Each predicate receives an org marker -(POM) and should return non-nil to match. Predicates listed under the -=predicates= key in =chime-include-filters= include events that match; -predicates under =predicates= in =chime-exclude-filters= drop them. +Filter out done items and declined invitations: #+BEGIN_SRC elisp -;; Include: only notify for events in work.org -(defun my-work-file-predicate (marker) - "Match events that live in work.org." - (string-match-p "work\\.org" - (or (buffer-file-name (marker-buffer marker)) ""))) - -(setq chime-include-filters - '((predicates . (my-work-file-predicate)))) - -;; Exclude: silence work events on weekends -(defun my-weekend-work-silencer (marker) - "Match work.org events on Saturday or Sunday." - (and (memq (nth 6 (decode-time)) '(0 6)) - (string-match-p "work\\.org" - (or (buffer-file-name (marker-buffer marker)) "")))) - -;; Exclude: drop events with a NO_NOTIFY property -(defun my-no-notify-predicate (marker) - "Match events with a NO_NOTIFY property set." - (org-entry-get marker "NO_NOTIFY")) - -;; Compose multiple exclude predicates with chime's defaults (setq chime-exclude-filters '((predicates . (chime-done-keywords-predicate - chime-declined-events-predicate - my-weekend-work-silencer - my-no-notify-predicate)))) -#+END_SRC - -The built-in =chime-done-keywords-predicate= and -=chime-declined-events-predicate= ship in =chime-exclude-filters= by -default, filtering out done items and declined Google Calendar -invitations. - -*** 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 -,* Meeting That Could've Been an Email -<2025-10-28 Tue 14:30-15:30> - -,* Pretend to Floss Consultation -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 ='("08:00")= (morning notification) -- 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 -(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. - -**** Advance Notice for All-Day Events - -Get notified about all-day events before they happen — useful for birthdays (buying gifts) or conferences (packing, travel): - -#+BEGIN_SRC elisp -;; Only notify on the day of the event (default) -(setq chime-day-wide-advance-notice nil) - -;; Notify 1 day before as well -(setq chime-day-wide-advance-notice 1) -;; → "Blake's birthday is tomorrow" at 08:00 the day before -;; → "Blake's birthday is today" at 08:00 on the day - -;; Notify 2 days before -(setq chime-day-wide-advance-notice 2) -#+END_SRC - -Note: This only affects notifications, not tooltip or modeline display. - -**** Showing All-Day Events in Tooltip - -Control whether all-day events (birthdays, holidays, etc.) appear in the modeline tooltip: - -#+BEGIN_SRC elisp -;; Show all-day events in tooltip (default: t) -(setq chime-tooltip-show-all-day-events t) - -;; Hide all-day events from tooltip -(setq chime-tooltip-show-all-day-events nil) -#+END_SRC - -All-day events are never shown in the modeline itself (only timed events appear there). This setting controls only the tooltip display. Notifications are unaffected. - -***** How alert times and overdue settings interact - -Short version: =chime-day-wide-alert-times= controls *when* notifications fire. =chime-show-any-overdue-with-day-wide-alerts= controls whether you see events from *previous days*. Today's events always show up regardless — if you launch Emacs at 2 PM and the alert was at 8 AM, you'll still see it. - -**** 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: New Year's Day -<2026-01-01 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. - -*** Advanced Settings - -**** Failure Warnings - -If the async event check fails repeatedly (e.g., due to a misconfigured org file), chime will show a warning after a configurable number of consecutive failures: - -#+BEGIN_SRC elisp -;; Warn after 5 consecutive failures (default) -(setq chime-max-consecutive-failures 5) - -;; Disable failure warnings -(setq chime-max-consecutive-failures 0) -#+END_SRC - -**** Validation Retries - -On startup, chime validates that =org-agenda-files= is populated. If your config loads org-agenda-files asynchronously (e.g., via idle timers), chime will retry validation a few times before giving up: - -#+BEGIN_SRC elisp -;; Retry validation 3 times before showing error (default) -(setq chime-validation-max-retries 3) - -;; Show error immediately if org-agenda-files is empty -(setq chime-validation-max-retries 0) -#+END_SRC - -**** Extra Alert Arguments - -Pass additional arguments to every =alert= call (see alert.el documentation for available options): - -#+BEGIN_SRC elisp -;; Example: always use a specific alert style -(setq chime-extra-alert-plist '(:style libnotify)) -#+END_SRC - -**** Async Environment Variables - -If you have custom variables that need to be available in chime's async subprocess (e.g., for custom predicate functions), add their name patterns: - -#+BEGIN_SRC elisp -;; Inject custom variables into the async process -(setq chime-additional-environment-regexes '("my-custom-var")) -#+END_SRC - -*** Full Example Configuration - -#+BEGIN_SRC elisp - (use-package chime - :vc (:url "https://github.com/cjennings/chime" :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 — uses bundled chime.wav by default - ;; (setq chime-sound-file nil) to disable - - ;; Modeline display - compact format - (setq chime-enable-modeline t) - (setq chime-modeline-lookahead-minutes 180) ; Show events 3 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-formats ; Countdown formats - '((at-event . "NOW!") - (short . "in %mm") - (long . "%hh%mm"))) - - ;; Notification settings - (setq chime-notification-title "Reminder") - - ;; Filtering — leave nil to notify for everything that isn't excluded - (setq chime-include-filters nil) - (setq chime-exclude-filters - '((predicates . (chime-done-keywords-predicate - chime-declined-events-predicate)))) - - ;; Enable chime-mode automatically - (chime-mode 1)) -#+END_SRC - -**** Events with SCHEDULED or DEADLINE - -#+BEGIN_SRC org -,* TODO Dentist (Don't Cancel This Time) -SCHEDULED: <2025-10-25 Sat 10:00> -#+END_SRC - -**** Repeating Events - -Repeating timestamps are fully supported: - -#+BEGIN_SRC org -,* TODO Weekly Meeting (Could've Been an Email) -SCHEDULED: <2025-10-25 Sat 14:00 +1w> - -,* TODO Daily Standup (Somehow 45 Minutes) -SCHEDULED: <2025-10-25 Sat 09:00 +1d> - -,* TODO Inbox Zero (Aspirational) -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 - -***** 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 (Somehow 45 Minutes) -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 (Somehow 45 Minutes) -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 - -** Integrations -:PROPERTIES: -:CUSTOM_ID: integrations -:END: - -*** org-gcal -:PROPERTIES: -:CUSTOM_ID: integration-with-org-gcal -:END: - -[[https://github.com/kidd/org-gcal.el][org-gcal]] syncs Google Calendar events into org files, which is exactly what chime reads. They work well together with no special configuration — just make sure the org files that org-gcal writes to are in your =org-agenda-files=. - -Typical setup: - -#+BEGIN_SRC elisp -(use-package org-gcal - :after org - :config - (setq org-gcal-client-id "your-client-id" - org-gcal-client-secret "your-client-secret" - org-gcal-fetch-file-alist - '(("you@gmail.com" . "~/org/gcal.org")))) - -;; Make sure chime can see those events -(add-to-list 'org-agenda-files "~/org/gcal.org") -#+END_SRC - -After org-gcal syncs, run =M-x chime-refresh-modeline= to pick up new events immediately (otherwise chime will find them on the next polling cycle). - -One thing to watch: org-gcal uses plain timestamps (=<2026-04-05 Sun 14:00>=), not SCHEDULED/DEADLINE. Chime handles both, so this works out of the box. If you're filtering by TODO keywords, note that org-gcal events won't have them unless you add keywords to your org-gcal templates. - -*** org-contacts -:PROPERTIES: -:CUSTOM_ID: integration-with-org-contacts -:END: - -If you use [[https://repo.or.cz/org-contacts.git][org-contacts]] for birthdays, you'll hit a snag: org-contacts uses diary sexps (=%%(org-contacts-anniversaries)=) that chime's async subprocess can't evaluate, producing "Bad sexp" errors. Chime works around this by converting birthdays to plain org timestamps. - -*Step 1: Convert existing contacts* - -#+BEGIN_SRC elisp -;; Load the 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 first) -M-x chime-convert-contacts-in-place RET ~/org/contacts.org RET -#+END_SRC - -This adds a yearly repeating timestamp (=<1985-03-15 Sat +1y>=) below each contact's properties, alongside the existing =:BIRTHDAY:= property. vCard export still works. - -After conversion, comment out the diary sexp in your schedule file: - -#+BEGIN_SRC org -# %%(org-contacts-anniversaries) -#+END_SRC - -*Step 2: Set up capture template for new contacts* - -The =chime-org-contacts= integration is optional and lives in its own file. Require it after =org-capture= loads so the template registers cleanly: - -#+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") - -;; Load the integration once org-capture is available -(with-eval-after-load 'org-capture - (require 'chime-org-contacts)) -#+END_SRC - -Or, with =use-package=: - -#+BEGIN_SRC elisp -(use-package chime-org-contacts - :after org-capture - :init - (setq chime-org-contacts-file "~/org/contacts.org")) -#+END_SRC - -Either form adds an org-capture template (=C-c c C=) that prompts for contact details and automatically inserts a yearly repeating timestamp if a birthday is provided. - -Set =chime-org-contacts-file= to =nil= to disable (the default). - -** Troubleshooting -:PROPERTIES: -:CUSTOM_ID: troubleshooting -:END: - -*** Enabling Debug Mode - -If something isn't working right, enable debug mode for detailed diagnostics in the =*Messages*= buffer: - -#+BEGIN_SRC elisp -;; Enable BEFORE loading chime -(setq chime-debug t) -(require 'chime) -#+END_SRC - -This loads =chime-debug.el= and gives you these commands: - -- =M-x chime-debug-dump-events= -- show all stored upcoming events -- =M-x chime-debug-dump-tooltip= -- show tooltip content -- =M-x chime-debug-config= -- dump complete configuration -- =M-x chime-debug-show-async-stats= -- show async process success/failure stats -- =M-x chime-debug-force-check= -- force an immediate check with diagnostics - -It also monitors event loading timing and async process performance in the background, logging to =*Messages*=. - -*** No Notifications Appearing - -1. Verify a notification daemon is running on your system (e.g., =dunst=, =mako=, or =swaync= on Linux; macOS and Windows have built-in notification support) -2. Verify chime-mode is enabled: =M-: chime-mode= -3. Check that alert is configured for your system: - #+BEGIN_SRC elisp - (setq alert-default-style 'libnotify) ; Linux with libnotify - ;; or 'notifications for D-Bus, 'osx-notifier for macOS, etc. - #+END_SRC -4. Manually test: =M-x chime-check= -4. Check =*Messages*= buffer for error messages -5. Enable [[#enabling-debug-mode][debug mode]] for detailed diagnostics - -*** No Sound Playing - -1. Verify a sound file is configured: =M-: chime-sound-file= should return a path (not nil) -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 (`chime-include-filters' / `chime-exclude-filters') -4. Timestamps support both 24-hour (=14:00=) and 12-hour (=2:00pm=, =2:00 PM=) formats - -*** 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! + chime-declined-events-predicate)))) #+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 - #+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 - ** Development & Testing :PROPERTIES: -:CUSTOM_ID: testing +:CUSTOM_ID: development--testing :END: -*** Development - Clone the repo and load from source: #+BEGIN_SRC bash @@ -1153,24 +178,15 @@ git clone https://github.com/cjennings/chime.git (require 'chime) #+END_SRC -Before submitting changes, check for byte-compilation warnings: +Quick test commands: #+BEGIN_SRC bash -cd tests -make validate # Check parentheses balance -make lint # Full elisp-lint (requires elisp-lint package) +make test +make test-file FILE=modeline +make coverage #+END_SRC -*** Testing - -645 ERT tests across 53 files. See [[file:TESTING.org][TESTING.org]] for the full story. - -Quick start: -#+BEGIN_SRC bash -cd tests -make test # Run all tests -make test-file FILE=modeline # Run specific test file -#+END_SRC +See [[file:TESTING.org][TESTING.org]] for the full test guide. ** History :PROPERTIES: @@ -1197,11 +213,11 @@ If you're migrating from org-wild-notifier, update your configuration: - =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)))=). +4. Note: The =:WILD_NOTIFIER_NOTIFY_BEFORE:= / =:CHIME_NOTIFY_BEFORE:= property has been removed. Use the global =chime-alert-intervals= variable instead. + ** License :PROPERTIES: :CUSTOM_ID: license :END: [[file:LICENSE][GPL-3.0]] - diff --git a/docs/ARCHITECTURE.org b/docs/ARCHITECTURE.org new file mode 100644 index 0000000..f05f36c --- /dev/null +++ b/docs/ARCHITECTURE.org @@ -0,0 +1,259 @@ +#+TITLE: Chime Architecture +#+AUTHOR: Craig Jennings + +Architecture notes for contributors and reviewers. + +[[file:../README.org][README]] | [[file:CONFIGURATION.org][Configuration]] | [[file:INTEGRATIONS.org][Integrations]] | [[file:TROUBLESHOOTING.org][Troubleshooting]] | [[file:../TESTING.org][Testing]] + +* Overview + +Chime is a small event pipeline around =org-agenda=. It periodically asks org for upcoming entries, converts those entries into serializable event alists, then uses that event list for notifications, modeline text, tooltip content, and jump-to-source actions. + +The central design constraint is responsiveness: agenda scanning can be slow for large org collections, so Chime does that work in an async Emacs subprocess and keeps the interactive Emacs session limited to scheduling, callbacks, rendering, and notification dispatch. + +* Runtime Flow + +#+BEGIN_EXAMPLE +chime-mode + | + v +chime--start + | + v +timer -> chime-check + | + v + chime--maybe-validate + | + v + chime--fetch-and-process + | + v + async child process + | + v + chime--retrieve-events + | + v + org-agenda-list -> markers -> filters -> chime--gather-info + | + v + event alists returned to parent process + | + v + chime--handle-async-success + | + v + callback + | | + v v +chime--process-events chime--update-modeline + | | + v v +chime--notify chime-modeline-string +#+END_EXAMPLE + +Manual refresh uses the same validation and async retrieval path, but supplies a modeline-only callback so it does not send notifications: + +#+BEGIN_EXAMPLE +M-x chime-refresh-modeline + -> chime--maybe-validate + -> chime--fetch-and-process + -> chime--update-modeline +#+END_EXAMPLE + +* Major Components + +** Lifecycle + +=chime-mode= controls the package lifecycle. Enabling the mode calls =chime--start=, which installs a timer. Disabling the mode calls =chime--stop=, which cancels the timer, interrupts any active async process, and resets validation state. + +Key state: + +- =chime--timer= — active polling timer +- =chime--process= — currently running async process, if any +- =chime--last-check-time= — last event check time +- =chime--validation-done= — whether startup validation has succeeded +- =chime--validation-retry-count= — retry counter for startup validation + +** Validation + +=chime--maybe-validate= gates event retrieval. It gives startup configuration a chance to populate =org-agenda-files= before Chime starts checking. + +=chime-validate-configuration= is also interactive. Interactively, it prints a checklist to =*Messages*=. Programmatically, it returns issue pairs so callers can decide whether to continue. + +** Async Retrieval + +=chime--retrieve-events= returns the child-process form used by async.el. The child process: + +1. Receives selected parent variables through =async-inject-variables=. +2. Initializes package.el. +3. Requires =chime=. +4. Runs =org-agenda-list= for the required lookahead span. +5. Extracts org markers from agenda line text properties. +6. Applies include/exclude filters. +7. Converts markers to event alists with =chime--gather-info=. + +The parent process receives only plain Lisp data. This is why event source identity is stored as file path and buffer position rather than as live marker objects. + +** Event Processing + +=chime--process-events= handles notification dispatch. It combines: + +- timed notifications from =chime--check-event= +- day-wide notifications from =chime--day-wide-notifications= + +Timed notifications compare event timestamps against current time plus each configured alert interval. Day-wide notifications are handled separately because all-day timestamps have no clock component. + +** Modeline and Tooltip + +=chime--update-modeline= computes two related views: + +- =chime-modeline-string= — rendered text for the soonest timed event inside =chime-modeline-lookahead-minutes= +- =chime--upcoming-events= — sorted tooltip tuples inside =chime-tooltip-lookahead-hours= + +All-day events are never shown in the modeline itself because the modeline is clock-relative. They can appear in the tooltip and in day-wide notifications. + +The tooltip uses =chime--upcoming-events=, grouping entries by day and limiting display with =chime-modeline-tooltip-max-events=. + +* Data Structures + +** Event Alist + +The internal event shape is an alist created by =chime--make-event= and validated by =chime--valid-event-p=: + +#+BEGIN_SRC elisp +((times . (("<2026-05-10 Sun 09:30>" . (26760 32460)))) + (title . "Planning") + (intervals . ((10 . medium) (0 . high))) + (marker-file . "/path/to/agenda.org") + (marker-pos . 1234)) +#+END_SRC + +Keys: + +- =times= — list of timestamp entries +- =title= — sanitized display title +- =intervals= — alert intervals from =chime-alert-intervals= +- =marker-file= — source org file path, or nil for synthesized test events +- =marker-pos= — source buffer position, or nil for synthesized test events + +Use the accessors instead of open-coded =assoc= in production code: + +- =chime--event-times= +- =chime--event-title= +- =chime--event-intervals= +- =chime--event-marker-file= +- =chime--event-marker-pos= + +** Time Entry + +Each entry in =times= is: + +#+BEGIN_SRC elisp +(TIMESTAMP-STRING . PARSED-TIME) +#+END_SRC + +=TIMESTAMP-STRING= is the original org timestamp string. =PARSED-TIME= is a serialized Emacs time value for timed events, or nil for all-day events. + +Examples: + +#+BEGIN_SRC elisp +("<2026-05-10 Sun 09:30>" . (26760 32460)) +("<2026-05-10 Sun>" . nil) +#+END_SRC + +** Upcoming Event Tuple + +Tooltip state uses tuples derived from event alists: + +#+BEGIN_SRC elisp +(EVENT TIME-INFO MINUTES-UNTIL) +#+END_SRC + +Example: + +#+BEGIN_SRC elisp +(((times . ...) + (title . "Planning") + (intervals . ...) + (marker-file . "/path/to/agenda.org") + (marker-pos . 1234)) + ("<2026-05-10 Sun 09:30>" . (26760 32460)) + 15.0) +#+END_SRC + +These tuples are stored in =chime--upcoming-events= and are also passed through tooltip grouping and deduplication helpers. + +* Filtering + +Filtering happens in the async child before event alists are created: + +#+BEGIN_EXAMPLE +markers + -> chime--apply-include-filters + -> chime--apply-exclude-filters + -> chime--gather-info +#+END_EXAMPLE + +Filters operate on org markers, not event alists, so predicates can inspect org properties, tags, TODO state, and source files directly. + +=chime-include-filters= and =chime-exclude-filters= share the same shape: + +#+BEGIN_SRC elisp +((keywords . ("TODO" "NEXT")) + (tags . ("work")) + (predicates . (my-marker-predicate))) +#+END_SRC + +If include filters are configured, a marker must match at least one include predicate. Exclude filters then remove any matching marker. + +* org-gcal Handling + +Regular org entries use SCHEDULED and DEADLINE timestamps first, then plain timestamps in the entry body. + +org-gcal entries are different: Chime detects them by the =entry-id= property and extracts timestamps only from the =:org-gcal:= drawer. That drawer is the authoritative source after calendar syncs; planning lines or body timestamps can lag behind after remote edits. + +* Error Handling + +Async failures are tracked with =chime--consecutive-async-failures=. Repeated failures can trigger a warning controlled by =chime-max-consecutive-failures=. + +=chime--fetch-and-process= records failures from two places: + +- errors returned by the async process +- errors raised while the parent callback handles returned data + +Successful async processing resets the consecutive failure counter. + +* Navigation + +Modeline right-click uses the first item in =chime--upcoming-events=. Chime reconstructs source location from =marker-file= and =marker-pos=: + +#+BEGIN_EXAMPLE +chime--jump-to-first-event + -> chime--jump-to-event + -> find-file + -> goto-char + -> org-fold-show-entry / org-show-entry +#+END_EXAMPLE + +This works across the async boundary because the child returns file path and numeric position, not marker objects. + +* Testing Notes + +Tests build event alists through shared helpers in =tests/testutil-events.el= so fixtures stay aligned with the production event contract. + +Useful focused test files: + +- =tests/test-chime-event-contract.el= +- =tests/test-chime-gather-info.el= +- =tests/test-chime-check-event.el= +- =tests/test-chime-update-modeline.el= +- =tests/test-chime-update-modeline-helpers.el= +- =tests/test-chime-process-notifications.el= + +Run the full suite with: + +#+BEGIN_SRC sh +make test +#+END_SRC diff --git a/docs/CONFIGURATION.org b/docs/CONFIGURATION.org new file mode 100644 index 0000000..ae73071 --- /dev/null +++ b/docs/CONFIGURATION.org @@ -0,0 +1,420 @@ +#+TITLE: Chime Configuration +#+AUTHOR: Craig Jennings + +Reference for Chime's user-facing configuration. + +[[file:../README.org][README]] | [[file:ARCHITECTURE.org][Architecture]] | [[file:INTEGRATIONS.org][Integrations]] | [[file:TROUBLESHOOTING.org][Troubleshooting]] | [[file:../TESTING.org][Testing]] + +* Polling Interval + +Control how often chime checks for upcoming events: + +#+BEGIN_SRC elisp +;; Default: check every 60 seconds +(setq chime-check-interval 60) + +;; More responsive +(setq chime-check-interval 30) +#+END_SRC + +Lower values make notifications more responsive but increase system load. Higher values reduce polling overhead but may delay notifications slightly. + +Changes take effect after restarting =chime-mode=. + +* Alert Intervals + +Set when to receive notifications and their urgency levels using =(minutes . severity)= pairs: + +#+BEGIN_SRC elisp +;; Default: 10 minutes before and at event time +(setq chime-alert-intervals '((10 . medium) (0 . high))) + +;; Single notification at event time +(setq chime-alert-intervals '((0 . high))) + +;; Multiple notifications with escalating urgency +(setq chime-alert-intervals + '((10 . low) + (5 . medium) + (0 . high))) +#+END_SRC + +Severity levels are =high=, =medium=, and =low=. They are passed to [[https://github.com/jwiegley/alert][alert.el]], which maps them to your notification style or daemon. + +* Chime Sound + +#+BEGIN_SRC elisp +;; Disable sound entirely +(setq chime-sound-file nil) + +;; Use a custom WAV or AU file +(setq chime-sound-file "/path/to/chime.wav") +#+END_SRC + +Chime uses Emacs's built-in =play-sound-file=. WAV and AU are the safest formats. + +* Notification Icon + +#+BEGIN_SRC elisp +;; Use a custom icon +(setq chime-notification-icon "/path/to/icon.png") + +;; Use the system/default alert icon +(setq chime-notification-icon nil) +#+END_SRC + +The icon is passed through to alert.el. PNG is the safest cross-platform choice. + +* Startup Delay + +Chime waits before the first check after enabling =chime-mode= so =org-agenda-files= and related config can finish loading. + +#+BEGIN_SRC elisp +;; Default +(setq chime-startup-delay 10) + +;; Increase if startup checks run before org-agenda-files is populated +(setq chime-startup-delay 20) +#+END_SRC + +* Modeline Display + +#+BEGIN_SRC elisp +;; Enable modeline display +(setq chime-enable-modeline t) + +;; Show events up to 2 hours ahead +(setq chime-modeline-lookahead-minutes 120) + +;; Customize the modeline prefix +(setq chime-modeline-format " ⏰ %s") +#+END_SRC + +The modeline displays the soonest timed event within the lookahead window and updates automatically on each check. + +** Minor Mode Lighter + +By default, the minor mode lighter is empty because the event display already indicates chime is running. + +#+BEGIN_SRC elisp +(setq chime-modeline-lighter " Chime") +#+END_SRC + +** No-Events Text + +Control what appears when no events are within the modeline lookahead window: + +#+BEGIN_SRC elisp +;; Default icon +(setq chime-modeline-no-events-text " ⏰") + +;; Show nothing +(setq chime-modeline-no-events-text nil) + +;; Custom text +(setq chime-modeline-no-events-text " No events") +#+END_SRC + +** Tooltip + +Hover over the modeline text to see upcoming events grouped by day. Configure the tooltip event limit: + +#+BEGIN_SRC elisp +;; Show up to 10 events +(setq chime-modeline-tooltip-max-events 10) + +;; Show all events in the lookahead window +(setq chime-modeline-tooltip-max-events nil) +#+END_SRC + +Customize the tooltip header: + +#+BEGIN_SRC elisp +(setq chime-tooltip-header-format + "Upcoming Events as of %a %b %d %Y @ %I:%M %p") +#+END_SRC + +Tooltip display strings are customizable for localization and plain-text +accessibility: + +#+BEGIN_SRC elisp +;; Section labels +(setq chime-tooltip-today-label "Today") +(setq chime-tooltip-tomorrow-label "Tomorrow") +(setq chime-tooltip-relative-day-format "%s, %b %d") +(setq chime-tooltip-future-day-format "%A, %b %d") + +;; Event lines and overflow +(setq chime-tooltip-event-format "%t at %T %u") +(setq chime-tooltip-more-events-format "... and %d more event%s") +(setq chime-tooltip-section-separator "─────────────") +(setq chime-tooltip-no-events-separator "─────────────────────") + +;; Tooltip-specific day/hour countdown words +(setq chime-tooltip-countdown-prefix "in") +(setq chime-tooltip-day-unit-labels '("day" . "days")) +(setq chime-tooltip-hour-unit-labels '("hour" . "hours")) + +;; No-events tooltip guidance +(setq chime-tooltip-no-events-format + "No calendar events in\nthe next %s.") +(setq chime-tooltip-increase-lookahead-format + "Increase `%s`\nto expand scope.") +(setq chime-tooltip-left-click-label "Left-click: Open calendar") +#+END_SRC + +** Tooltip Lookahead + +The tooltip can show events beyond the modeline lookahead window: + +#+BEGIN_SRC elisp +;; Modeline shows events within 2 hours +(setq chime-modeline-lookahead-minutes 120) + +;; Tooltip shows events within 1 week +(setq chime-tooltip-lookahead-hours 168) +#+END_SRC + +Larger values increase the agenda span fetched by the async subprocess and can slow checks for large org collections. + +** Click Actions + +- Left-click opens =chime-calendar-url= in a browser. +- Right-click jumps to the next event's org entry. + +#+BEGIN_SRC elisp +(setq chime-calendar-url "https://calendar.google.com") +#+END_SRC + +* Notification Text + +Control what appears in notifications and the modeline event text: + +#+BEGIN_SRC elisp +;; Default: title, event time, countdown +(setq chime-notification-text-format "%t at %T (%u)") + +;; Title and countdown only +(setq chime-notification-text-format "%t (%u)") +#+END_SRC + +Placeholders: + +- =%t= — event title +- =%T= — event time formatted by =chime-display-time-format-string= +- =%u= — time until event formatted by =chime-time-left-formats= + +* Time Formats + +#+BEGIN_SRC elisp +;; 12-hour with AM/PM +(setq chime-display-time-format-string "%I:%M %p") + +;; 24-hour +(setq chime-display-time-format-string "%H:%M") +#+END_SRC + +Countdown formatting uses =chime-time-left-formats=: + +#+BEGIN_SRC elisp +(setq chime-time-left-formats + '((at-event . "right now") + (short . "in %M") + (long . "in %H %M"))) +#+END_SRC + +* Title Truncation + +#+BEGIN_SRC elisp +;; No truncation +(setq chime-max-title-length nil) + +;; Limit title text +(setq chime-max-title-length 25) +#+END_SRC + +This affects only the event title placeholder =%t=, not the icon, time, or countdown. + +* Filtering + +Two alists control which events trigger notifications: + +- =chime-include-filters= — event must match at least one configured include rule +- =chime-exclude-filters= — event is dropped if it matches any configured exclude rule + +Both use this shape: + +#+BEGIN_SRC elisp +'((keywords . ("TODO" "NEXT")) + (tags . ("work" "urgent")) + (predicates . (my-predicate-fn))) +#+END_SRC + +Example: + +#+BEGIN_SRC elisp +(setq chime-include-filters + '((keywords . ("TODO" "NEXT")) + (tags . ("important")))) + +(setq chime-exclude-filters + '((keywords . ("DONE" "CANCELLED")) + (tags . ("someday")) + (predicates . (chime-done-keywords-predicate + chime-declined-events-predicate)))) +#+END_SRC + +If the same keyword or tag appears in both include and exclude filters, exclude wins. + +** Custom Predicates + +Predicates receive an org marker and return non-nil to match. + +#+BEGIN_SRC elisp +(defun my-no-notify-predicate (marker) + "Match events with a NO_NOTIFY property set." + (org-entry-get marker "NO_NOTIFY")) + +(setq chime-exclude-filters + '((predicates . (chime-done-keywords-predicate + chime-declined-events-predicate + my-no-notify-predicate)))) +#+END_SRC + +* All-Day Events + +Chime distinguishes timed events from all-day events. + +Timed: + +#+BEGIN_SRC org +* Meeting +<2026-05-10 Sun 14:30> +#+END_SRC + +All-day: + +#+BEGIN_SRC org +* Birthday +<2026-05-10 Sun> +#+END_SRC + +All-day events are never shown in the modeline itself. They can appear in tooltip and day-wide notifications. + +** Day-Wide Notification Times + +#+BEGIN_SRC elisp +;; Default: notify at 8:00 AM +(setq chime-day-wide-alert-times '("08:00")) + +;; Multiple times +(setq chime-day-wide-alert-times '("08:00" "17:00")) + +;; 12-hour Org time strings are accepted +(setq chime-day-wide-alert-times '("8:00am" "5:00pm")) + +;; Disable all-day notifications +(setq chime-day-wide-alert-times nil) +#+END_SRC + +** Overdue and Past All-Day Events + +#+BEGIN_SRC elisp +;; Show overdue items with all-day notifications +(setq chime-show-any-overdue-with-day-wide-alerts t) + +;; Show only today's all-day events +(setq chime-show-any-overdue-with-day-wide-alerts nil) +#+END_SRC + +Today's events always show if you launch Emacs after the alert time. This setting controls whether previous-day all-day events are included. + +** Advance Notice + +#+BEGIN_SRC elisp +;; Only notify on the day of the event +(setq chime-day-wide-advance-notice nil) + +;; Also notify one day before +(setq chime-day-wide-advance-notice 1) +#+END_SRC + +** Tooltip Display + +#+BEGIN_SRC elisp +;; Show all-day events in tooltip +(setq chime-tooltip-show-all-day-events t) + +;; Hide all-day events from tooltip +(setq chime-tooltip-show-all-day-events nil) +#+END_SRC + +* Advanced Settings + +** Failure Warnings + +#+BEGIN_SRC elisp +;; Warn after 5 consecutive async failures +(setq chime-max-consecutive-failures 5) + +;; Disable failure warnings +(setq chime-max-consecutive-failures 0) +#+END_SRC + +** Extra Alert Arguments + +#+BEGIN_SRC elisp +(setq chime-extra-alert-plist '(:style libnotify)) +#+END_SRC + +** Async Environment Variables + +If custom predicates need additional variables in the async subprocess, add regexes for their names: + +#+BEGIN_SRC elisp +(setq chime-additional-environment-regexes '("my-custom-var")) +#+END_SRC + +* Full Example + +#+BEGIN_SRC elisp +(use-package chime + :vc (:url "https://github.com/cjennings/chime" :rev :newest) + :after alert + :commands (chime-mode chime-check chime-refresh-modeline) + :config + (setq chime-check-interval 60) + (setq chime-alert-intervals '((5 . medium) (0 . high))) + (setq chime-enable-modeline t) + (setq chime-modeline-lookahead-minutes 180) + (setq chime-tooltip-lookahead-hours 168) + (setq chime-modeline-format " ⏰ %s") + (setq chime-notification-text-format "%t (%u)") + (setq chime-display-time-format-string "%H:%M") + (setq chime-time-left-formats + '((at-event . "NOW!") + (short . "in %mm") + (long . "%hh%mm"))) + (setq chime-exclude-filters + '((predicates . (chime-done-keywords-predicate + chime-declined-events-predicate)))) + (chime-mode 1)) +#+END_SRC + +* Supported Org Timestamp Shapes + +#+BEGIN_SRC org +* Scheduled event +SCHEDULED: <2026-05-10 Sun 10:00> + +* Deadline +DEADLINE: <2026-05-10 Sun 17:00> + +* Plain timestamp +<2026-05-10 Sun 14:30> + +* Repeating timestamp +SCHEDULED: <2026-05-10 Sun 09:00 +1w> +#+END_SRC + +Diary sexp timestamps such as =<%%(...)>= are not supported. Use standard org timestamps or repeating timestamps instead. diff --git a/docs/INTEGRATIONS.org b/docs/INTEGRATIONS.org new file mode 100644 index 0000000..dd8fea8 --- /dev/null +++ b/docs/INTEGRATIONS.org @@ -0,0 +1,109 @@ +#+TITLE: Chime Integrations +#+AUTHOR: Craig Jennings + +Setup notes for related org-mode packages. + +[[file:../README.org][README]] | [[file:CONFIGURATION.org][Configuration]] | [[file:ARCHITECTURE.org][Architecture]] | [[file:TROUBLESHOOTING.org][Troubleshooting]] | [[file:../TESTING.org][Testing]] + +* org-gcal +:PROPERTIES: +:CUSTOM_ID: org-gcal +:END: + +[[https://github.com/kidd/org-gcal.el][org-gcal]] syncs Google Calendar events into org files, which is exactly what chime reads. They work well together with no special configuration as long as the org files that org-gcal writes to are in =org-agenda-files=. + +Typical setup: + +#+BEGIN_SRC elisp +(use-package org-gcal + :after org + :config + (setq org-gcal-client-id "your-client-id" + org-gcal-client-secret "your-client-secret" + org-gcal-fetch-file-alist + '(("you@gmail.com" . "~/org/gcal.org")))) + +;; Make sure chime can see those events +(add-to-list 'org-agenda-files "~/org/gcal.org") +#+END_SRC + +After org-gcal syncs, run: + +#+BEGIN_SRC elisp +M-x chime-refresh-modeline +#+END_SRC + +That refreshes the modeline without sending notifications. Otherwise chime will pick up synced events on the next polling cycle. + +org-gcal commonly writes plain timestamps like: + +#+BEGIN_SRC org +* Team Sync +<2026-05-10 Sun 14:00> +#+END_SRC + +Chime handles plain timestamps, SCHEDULED timestamps, and DEADLINE timestamps. If you filter by TODO keyword, remember that org-gcal events usually do not have TODO keywords unless you add them through your org-gcal templates. + +Chime also ships =chime-declined-events-predicate=, which filters out org-gcal entries with =:STATUS: declined=. This predicate is included in the default =chime-exclude-filters=. + +* org-contacts +:PROPERTIES: +:CUSTOM_ID: org-contacts +:END: + +org-contacts birthdays are often exposed through diary sexps such as: + +#+BEGIN_SRC org +%%(org-contacts-anniversaries) +#+END_SRC + +Chime does not support diary sexp timestamps as event timestamps. For birthday notifications, convert contacts to standard repeating org timestamps. + +** Convert Existing Contacts + +Load the conversion script: + +#+BEGIN_SRC elisp +(require 'convert-org-contacts-birthdays + (expand-file-name "convert-org-contacts-birthdays.el" + (file-name-directory (locate-library "chime")))) +#+END_SRC + +Then run: + +#+BEGIN_SRC elisp +M-x chime-convert-contacts-in-place RET ~/org/contacts.org RET +#+END_SRC + +The command creates a timestamped backup first, then adds yearly repeating timestamps below contacts with birthday properties. vCard export still works because the original =:BIRTHDAY:= property remains in place. + +After conversion, comment out the diary sexp from your schedule file: + +#+BEGIN_SRC org +# %%(org-contacts-anniversaries) +#+END_SRC + +** Capture Template for New Contacts + +The optional =chime-org-contacts= integration adds an org-capture template that prompts for contact details and inserts a yearly repeating timestamp when a birthday is provided. + +#+BEGIN_SRC elisp +(setq chime-org-contacts-file "~/org/contacts.org") + +;; Optional: customize capture key. Default is "C". +(setq chime-org-contacts-capture-key "C") + +(with-eval-after-load 'org-capture + (require 'chime-org-contacts)) +#+END_SRC + +With use-package: + +#+BEGIN_SRC elisp +(use-package chime-org-contacts + :after org-capture + :init + (setq chime-org-contacts-file "~/org/contacts.org")) +#+END_SRC + +Set =chime-org-contacts-file= to nil to disable the integration. diff --git a/docs/TROUBLESHOOTING.org b/docs/TROUBLESHOOTING.org new file mode 100644 index 0000000..5f28a5c --- /dev/null +++ b/docs/TROUBLESHOOTING.org @@ -0,0 +1,180 @@ +#+TITLE: Chime Troubleshooting +#+AUTHOR: Craig Jennings + +Debugging guide for common Chime setup issues. + +[[file:../README.org][README]] | [[file:CONFIGURATION.org][Configuration]] | [[file:ARCHITECTURE.org][Architecture]] | [[file:INTEGRATIONS.org][Integrations]] | [[file:../TESTING.org][Testing]] + +* Validate Configuration + +Start with: + +#+BEGIN_SRC elisp +M-x chime-validate-configuration +#+END_SRC + +Interactive validation prints a checklist in =*Messages*= showing whether =org-agenda-files= is set, whether files exist, whether org-agenda is loadable, and whether modeline support is available. + +* Enable Debug Mode +:PROPERTIES: +:CUSTOM_ID: enable-debug-mode +:END: + +Enable debug mode before loading chime: + +#+BEGIN_SRC elisp +(setq chime-debug t) +(require 'chime) +#+END_SRC + +This loads =chime-debug.el= and provides: + +- =M-x chime-debug-dump-events= — show stored upcoming events +- =M-x chime-debug-dump-tooltip= — show tooltip content +- =M-x chime-debug-config= — dump complete configuration +- =M-x chime-debug-show-async-stats= — show async success/failure stats +- =M-x chime-debug-force-check= — force an immediate diagnostic check + +Debug mode also logs event loading and async process timing to =*Messages*=. + +* No Notifications Appearing + +Check these in order: + +1. Verify a notification daemon is running on your system. + - Linux examples: =dunst=, =mako=, =swaync= + - macOS and Windows have built-in notification support. +2. Verify =chime-mode= is enabled: + #+BEGIN_SRC elisp + M-: chime-mode + #+END_SRC +3. Check alert.el style configuration: + #+BEGIN_SRC elisp + (setq alert-default-style 'libnotify) + #+END_SRC +4. Run =M-x chime-check= manually. +5. Check =*Messages*= for errors. +6. Enable [[#enable-debug-mode][debug mode]] for more detail. + +* No Sound Playing + +1. Verify a sound file is configured: + #+BEGIN_SRC elisp + M-: chime-sound-file + #+END_SRC +2. Check that the file exists: + #+BEGIN_SRC elisp + M-: (file-exists-p chime-sound-file) + #+END_SRC +3. Test sound directly: + #+BEGIN_SRC elisp + M-: (play-sound-file chime-sound-file) + #+END_SRC +4. Ensure your system has audio support configured. + +Set =chime-sound-file= to nil to disable sound. + +* Events Not Being Detected + +Common causes: + +1. The org file is not in =org-agenda-files=. +2. The timestamp is all-day and you expected it in the modeline. All-day events do not appear in the modeline. +3. The timestamp is a diary sexp such as =<%%(...)>=. Chime does not support those as event timestamps. +4. Filters are excluding the event. +5. The event is outside the modeline or tooltip lookahead window. + +Chime supports timed 24-hour and 12-hour timestamps: + +#+BEGIN_SRC org +* 24-hour event +<2026-05-10 Sun 14:00> + +* 12-hour event +<2026-05-10 Sun 2:00pm> +#+END_SRC + +* Modeline Is Empty + +Check: + +1. =chime-enable-modeline= is non-nil. +2. =chime-modeline-lookahead-minutes= is greater than zero. +3. There is a timed event inside the modeline lookahead. +4. Events are visible to =org-agenda-files=. + +All-day events may appear in the tooltip, but never in the modeline itself. + +* Tooltip Has Too Many or Too Few Events + +Relevant settings: + +#+BEGIN_SRC elisp +(setq chime-tooltip-lookahead-hours 168) +(setq chime-modeline-tooltip-max-events 5) +(setq chime-tooltip-show-all-day-events t) +#+END_SRC + +If you increase =chime-tooltip-lookahead-hours= a lot, async checks can take longer because chime fetches a wider agenda span. + +* Multiple Emacs Instances Produce Duplicate Notifications + +If you receive duplicate notifications for every event, you likely have multiple Emacs processes running with =chime-mode= enabled. + +Check running Emacs processes: + +#+BEGIN_SRC bash +ps aux | grep emacs +#+END_SRC + +Common scenario: + +#+BEGIN_SRC bash +emacs --daemon +emacsclient -c +emacs & +#+END_SRC + +The final command starts a second standalone Emacs process, so chime runs twice. + +Choose one approach: + +- Use daemon + emacsclient consistently. +- Use standalone Emacs processes only. + +To stop a daemon: + +#+BEGIN_SRC bash +emacsclient -e "(kill-emacs)" +#+END_SRC + +* Startup Validation Fails Before org-agenda-files Loads + +If your configuration populates =org-agenda-files= asynchronously, chime may validate before those files are ready. + +Options: + +#+BEGIN_SRC elisp +;; Give startup more time before first check. +(setq chime-startup-delay 20) +#+END_SRC + +or ensure =org-agenda-files= is set before enabling =chime-mode=. + +* Async Check Failures + +Chime warns after repeated async failures. Tune or disable those warnings: + +#+BEGIN_SRC elisp +;; Default +(setq chime-max-consecutive-failures 5) + +;; Disable warnings +(setq chime-max-consecutive-failures 0) +#+END_SRC + +For custom predicate functions, ensure any variables they depend on are available in the async subprocess: + +#+BEGIN_SRC elisp +(setq chime-additional-environment-regexes '("my-custom-var")) +#+END_SRC -- cgit v1.2.3