From ce3a1014d929b94bd90cbc565608655d10d843bc Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 9 Jul 2026 16:08:47 -0500 Subject: fix: drop nil day counts before taking the minimum chime--days-until-event maps over every timestamp on an event and keeps a day count only for the all-day ones. A timed timestamp maps to nil. An event carrying both kinds handed -min a list with a nil in it, which signals wrong-type-argument. An event with no all-day timestamp handed it an empty list, which signals too. Both crash on every tick once chime-day-wide-advance-notice is on. The recurring failure walks the async counter up to the persistent-failure warning. The map now runs through -non-nil, and an empty result returns nil, which the docstring already promised. I added a nil branch to chime--day-wide-notification-text so a nil count can't reach the = comparisons. The advance-notice window should already exclude an event with no all-day timestamp, so that branch is a guard rather than a live path. --- chime.el | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/chime.el b/chime.el index 823057c..dc74a8f 100644 --- a/chime.el +++ b/chime.el @@ -1310,20 +1310,26 @@ For timed events, checks if the time is today (past or future)." (defun chime--days-until-event (all-times) "Calculate minimum days until the soonest all-day timestamp in ALL-TIMES. ALL-TIMES is a list of (TIMESTAMP-STR . TIME-OBJECT) cons cells. -Returns integer days (ceiling), or nil if no all-day timestamps found." - (let ((now (current-time))) - (-min - (--map - (when-let* ((timestamp-str (car it)) - (is-all-day (not (chime--has-timestamp timestamp-str))) - (parsed (org-parse-time-string timestamp-str)) - (year (nth 5 parsed)) - (month (nth 4 parsed)) - (day (nth 3 parsed))) - (let* ((event-time (encode-time 0 0 0 day month year)) - (seconds-until (time-subtract event-time now))) - (ceiling (/ (float-time seconds-until) 86400.0)))) - all-times)))) +Returns integer days (ceiling), or nil if no all-day timestamps found. + +A timed timestamp contributes no day count, so an event mixing timed and +all-day timestamps yields nils that must be dropped before `-min' sees +them. An event with no all-day timestamp at all leaves nothing to +minimize, hence the nil return the docstring promises." + (let* ((now (current-time)) + (days (-non-nil + (--map + (when-let* ((timestamp-str (car it)) + (is-all-day (not (chime--has-timestamp timestamp-str))) + (parsed (org-parse-time-string timestamp-str)) + (year (nth 5 parsed)) + (month (nth 4 parsed)) + (day (nth 3 parsed))) + (let* ((event-time (encode-time 0 0 0 day month year)) + (seconds-until (time-subtract event-time now))) + (ceiling (/ (float-time seconds-until) 86400.0)))) + all-times)))) + (when days (-min days)))) (defun chime--day-wide-notification-text (event) "Generate notification text for day-wide EVENT. @@ -1338,6 +1344,10 @@ Handles both same-day events and advance notices." (is-advance-notice (let ((days-until (chime--days-until-event (chime--event-times event)))) (cond + ;; No all-day timestamp means no day count to report. The + ;; advance-notice window should have excluded such an event, so + ;; this is a belt-and-braces guard against a nil reaching `='. + ((null days-until) (format "%s is due or scheduled today" title)) ((= days-until 1) (format "%s is tomorrow" title)) ((= days-until 2) (format "%s is in 2 days" title)) (t (format "%s is in %d days" title days-until))))) -- cgit v1.2.3