aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-31 11:04:42 -0500
committerCraig Jennings <c@cjennings.net>2026-07-31 11:04:42 -0500
commit445a0be315f3d6fff585f1558262fb5dacd10d12 (patch)
treeca12cae724f4516f30e3e0995436bb89fef6ed71 /modules
parent37f21839ca62617fef07684b2f98268de47a71c2 (diff)
downloaddotemacs-445a0be315f3d6fff585f1558262fb5dacd10d12.tar.gz
dotemacs-445a0be315f3d6fff585f1558262fb5dacd10d12.zip
feat(agenda): refresh the render cache on a timer, without the daemon
The surface reading this has to stay correct while Emacs is down, and emacsclient is exactly the thing that cannot. So the writer is a batch Emacs and a user timer runs it every five minutes. - scripts/agenda-render-cache writes the cache from a batch Emacs. It resolves the agenda file list through the config's own resolver rather than restating it, so adding a calendar source stays a one-place change. - The timer uses OnCalendar so Persistent actually applies. On a monotonic schedule it is silently ignored, and a machine that slept would come back to a stale file with nothing to trigger a catch-up. - The window is now three days, yesterday through tomorrow. A consumer drawing a rolling window centred on now has nothing to draw for the part of its span outside today, which late in the evening is half the surface. The keyword list moved to user-constants, where a batch Emacs can reach it without this config's package dependencies. Without it org did not recognise DOING or VERIFY, so it stopped parsing those headlines as tasks at all: the keyword and priority cookie stayed glued to the front of every title and each row reported no keyword and not-done. The file parsed cleanly and was wrong, which is the failure worth guarding. The bats tests run against a fixture and this checkout, not the machine's real agenda and installed config. Asserting over live data let the same suite pass on a day that happened to have no keyworded entries.
Diffstat (limited to 'modules')
-rw-r--r--modules/agenda-query.el20
-rw-r--r--modules/org-config.el8
-rw-r--r--modules/user-constants.el18
3 files changed, 37 insertions, 9 deletions
diff --git a/modules/agenda-query.el b/modules/agenda-query.el
index 5a185076..c98b7fb7 100644
--- a/modules/agenda-query.el
+++ b/modules/agenda-query.el
@@ -575,18 +575,28 @@ entry spans its day and a point event has zero width. See
json))
(defun cj/agenda-render-cache-update ()
- "Write today's agenda to `cj/agenda-render-cache-file' for the renderer.
+ "Write the agenda around today to `cj/agenda-render-cache-file'.
-The window is the whole local day, midnight to day close, so it is 23 or 25
-hours long on a DST changeover rather than a flat 24. Returns the path.
+The window is three whole local days: yesterday's midnight through tomorrow's
+day close. A consumer drawing a rolling window centred on now needs entries
+from either side of midnight, and a single calendar day leaves it with nothing
+to draw for the part of its span that falls outside today -- half the surface,
+late in the evening. Three days covers any rolling span up to a full day
+either way, and the consumer filters to what it actually draws.
+
+Day boundaries are computed rather than assumed, so the span is 71, 72 or 73
+hours across a DST changeover rather than a flat 72. Returns the path.
Safe to call repeatedly and from a timer: it only reads org files, creates the
cache directory if needed, and replaces the file by rename, so a reader on its
own schedule never sees a partial write."
(interactive)
(let* ((d (decode-time (time-convert nil 'integer)))
- (start (cj/--agenda-query-epoch 0 0 0 (nth 3 d) (nth 4 d) (nth 5 d)))
- (end (cj/--agenda-query-day-close (nth 3 d) (nth 4 d) (nth 5 d))))
+ (day (nth 3 d)) (month (nth 4 d)) (year (nth 5 d))
+ ;; Out-of-range day fields normalize, so day 0 is last month's last
+ ;; day and day+1 rolls the month or year without special cases.
+ (start (cj/--agenda-query-epoch 0 0 0 (1- day) month year))
+ (end (cj/--agenda-query-day-close (1+ day) month year)))
(make-directory (file-name-directory cj/agenda-render-cache-file) t)
(cj/agenda-render-json start end cj/agenda-render-cache-file)
(when (called-interactively-p 'interactive)
diff --git a/modules/org-config.el b/modules/org-config.el
index a9fc4811..55fdf1d0 100644
--- a/modules/org-config.el
+++ b/modules/org-config.el
@@ -16,6 +16,7 @@
;;; Code:
(require 'keybindings) ;; provides cj/custom-keymap (used in :init below)
+(require 'user-constants) ;; provides cj/org-todo-keywords (used in :config)
;; Declare org variables and functions used before org is loaded so this module
;; byte-compiles standalone. Plain `defvar' (no value) marks the symbol special
@@ -284,10 +285,9 @@ a no-op identical-state transition (see `cj/org--noop-state-log-p')."
"All org-todo related settings are grouped and set in this function."
;; logging task creation, task start, and task resolved states
- (setq org-todo-keywords '((sequence "TODO(t)" "PROJECT(p)" "DOING(i)"
- "WAITING(w)" "VERIFY(v)" "STALLED(s)"
- "DELEGATED(x)" "|"
- "FAILED(f!)" "DONE(d!)" "CANCELLED(c!)")))
+ ;; Defined in user-constants so a batch Emacs can load the sequence without
+ ;; this module's package dependencies. See `cj/org-todo-keywords'.
+ (setq org-todo-keywords cj/org-todo-keywords)
;; Keyword and priority faces are defined and wired in org-faces-config.el
;; (loaded just after this module): each keyword and priority maps to its own
diff --git a/modules/user-constants.el b/modules/user-constants.el
index 570b142f..ec387930 100644
--- a/modules/user-constants.el
+++ b/modules/user-constants.el
@@ -275,5 +275,23 @@ and portable across different machines."
;; bare `(require 'user-constants)' (tests, byte-compile, batch) stays
;; side-effect-free.
+(defconst cj/org-todo-keywords
+ '((sequence "TODO(t)" "PROJECT(p)" "DOING(i)"
+ "WAITING(w)" "VERIFY(v)" "STALLED(s)"
+ "DELEGATED(x)" "|"
+ "FAILED(f!)" "DONE(d!)" "CANCELLED(c!)"))
+ "The TODO keyword sequence, kept where a batch Emacs can reach it.
+
+`org-config' sets `org-todo-keywords' from this, and so does any batch process
+that has to read the org files the way the editor does. It lives here rather
+than in `org-config' because that module loads through `use-package' and needs
+packages a batch run has no reason to install.
+
+The duplication this avoids is not cosmetic. A reader that does not know
+DOING is a keyword does not merely mislabel it: org stops parsing the headline
+as a task at all, so the keyword and the priority cookie stay glued to the
+front of the title and the entry reads as not-done regardless of its real
+state.")
+
(provide 'user-constants)
;;; user-constants.el ends here