aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-20 16:22:49 -0500
committerCraig Jennings <c@cjennings.net>2026-07-20 16:22:49 -0500
commitc7e551f28a6cb410ed05c3a8f867c70672927b94 (patch)
treec43ef9a52d141b8a54307226f7eefecb676c4b95
parented56da8c5b52c052ec3fdbf5e0b04b2040ca59d6 (diff)
downloaddotemacs-c7e551f28a6cb410ed05c3a8f867c70672927b94.tar.gz
dotemacs-c7e551f28a6cb410ed05c3a8f867c70672927b94.zip
fix(agenda): repair the refresh tick, tighten the gutter, allow point motion
Every 5-minute refresh failed. The F command's general settings carried (org-agenda-sticky t), and those settings are baked into the buffer's series-redo-cmd and re-applied on each redo. With the buffer already existing, org-agenda-use-sticky-p turned true mid-redo and org-agenda-prepare threw 'exit ("use r to refresh") to a catch that doesn't exist inside org-agenda-redo. I removed sticky from the command settings (the spawn wrapper still binds it, which is where the buffer gets its name) and kept the redo path free of sticky, since org-agenda-redo handles the in-place rebuild itself. Verified with a real tick against a live frame: rebuild succeeds, the failure banner clears, the latch resets. The wide gutter between the source column and the item was the global agenda prefix format's 25-char category pad. The F view now sets its own format with a 10-char pad. C-a hit the deny catch-all. I allowlisted read-only point motion (C-a, C-e, C-f, C-b) and isearch (C-s, C-r).
-rw-r--r--modules/org-agenda-frame.el31
-rw-r--r--tests/test-org-agenda-frame.el49
2 files changed, 74 insertions, 6 deletions
diff --git a/modules/org-agenda-frame.el b/modules/org-agenda-frame.el
index efd74115..fd911a97 100644
--- a/modules/org-agenda-frame.el
+++ b/modules/org-agenda-frame.el
@@ -108,9 +108,18 @@ a non-nil global default can't open a second window at build time."
((org-agenda-span 7)
(org-agenda-start-day "0d")
(org-agenda-start-on-weekday nil)
- (org-agenda-start-with-follow-mode nil))))
- ((org-agenda-sticky t)
- (org-agenda-window-setup 'current-window))))
+ (org-agenda-start-with-follow-mode nil)
+ ;; Narrow category column: the global agenda format pads the
+ ;; category to 25 chars, leaving a wide blank gutter between
+ ;; the source name (todo:, dcal:) and the item.
+ (org-agenda-prefix-format " %i %-10:c%?-12t% s"))))
+ ;; No `org-agenda-sticky' here, deliberately: these settings are baked
+ ;; into the buffer's series-redo-cmd and re-applied by every redo, and a
+ ;; sticky t mid-redo makes `org-agenda-use-sticky-p' true while the
+ ;; buffer exists -- `org-agenda-prepare' then throws \\='exit ("use `r'
+ ;; to refresh") with no catch, failing every refresh tick. Stickiness
+ ;; is bound in the spawn wrapper instead, where it names the buffer.
+ ((org-agenda-window-setup 'current-window))))
(defun cj/--agenda-frame-register-command ()
"Register the agenda-frame view in `org-agenda-custom-commands'.
@@ -220,6 +229,13 @@ The default binding for every key not on the allowlist."
(define-key map (kbd "M-v") #'scroll-down-command)
(define-key map (kbd "M-<") #'beginning-of-buffer)
(define-key map (kbd "M->") #'end-of-buffer)
+ ;; Read-only point motion and search within the agenda.
+ (define-key map (kbd "C-a") #'move-beginning-of-line)
+ (define-key map (kbd "C-e") #'move-end-of-line)
+ (define-key map (kbd "C-f") #'forward-char)
+ (define-key map (kbd "C-b") #'backward-char)
+ (define-key map (kbd "C-s") #'isearch-forward)
+ (define-key map (kbd "C-r") #'isearch-backward)
(define-key map (kbd "C-g") #'keyboard-quit)
;; (b) Engage / open — routed to the working frame, never the agenda frame.
;; Bind the GUI function-key events ([return]/[tab]) as well as the ASCII
@@ -609,7 +625,14 @@ Either way the frame is never blank, unrestricted, or non-retryable."
(snapshot (cj/--agenda-frame-snapshot buffer window)))
(unwind-protect
(condition-case nil
- (let ((inhibit-message t))
+ ;; Never bind sticky here: `org-agenda-redo' handles the
+ ;; in-place rebuild itself (binds sticky nil, redirects the
+ ;; buffer name). A sticky t reaching `org-agenda-prepare'
+ ;; mid-redo makes it throw \\='exit with no catch, failing
+ ;; every tick. current-window is bound as a belt so a rule
+ ;; can't split the frame during the rebuild.
+ (let ((inhibit-message t)
+ (org-agenda-window-setup 'current-window))
(org-agenda-redo)
(cj/--agenda-frame-remove-overlay buffer)
(cj/--agenda-frame-restore-point old-marker old-line)
diff --git a/tests/test-org-agenda-frame.el b/tests/test-org-agenda-frame.el
index c29d057b..29b6a36f 100644
--- a/tests/test-org-agenda-frame.el
+++ b/tests/test-org-agenda-frame.el
@@ -129,6 +129,35 @@
(should (assq 'org-agenda-start-on-weekday s))
(should (null (cadr (assq 'org-agenda-start-on-weekday s))))))
+(ert-deftest test-org-agenda-frame-command-tight-prefix-format ()
+ "Normal: the view sets its own prefix format with a narrow category column.
+Without it the global agenda format applies, whose 25-char category pad
+leaves a wide blank gutter between the source name and the item."
+ (let ((s (test-org-agenda-frame--block-settings)))
+ (should (assq 'org-agenda-prefix-format s))
+ (should (string-match-p "%-10:c"
+ (cadr (assq 'org-agenda-prefix-format s))))))
+
+(ert-deftest test-org-agenda-frame-do-redo-leaves-sticky-alone ()
+ "Normal: the redo binds current-window but never touches sticky.
+org-agenda-redo handles the in-place rebuild itself (it binds sticky nil
+and redirects the buffer name); a sticky t reaching org-agenda-prepare
+mid-redo makes it throw \\='exit with no catch and the tick fails."
+ (let ((org-agenda-sticky nil)
+ seen-sticky seen-setup (params '()))
+ (with-temp-buffer
+ (insert "agenda line\n")
+ (cl-letf (((symbol-function 'org-agenda-redo)
+ (lambda (&rest _)
+ (setq seen-sticky org-agenda-sticky
+ seen-setup org-agenda-window-setup)))
+ ((symbol-function 'frame-parameter) (lambda (_f p) (alist-get p params)))
+ ((symbol-function 'set-frame-parameter)
+ (lambda (_f p v) (setf (alist-get p params) v))))
+ (cj/--agenda-frame-do-redo 'af (current-buffer) nil)
+ (should (null seen-sticky))
+ (should (eq seen-setup 'current-window))))))
+
(ert-deftest test-org-agenda-frame-command-follow-mode-off ()
"Boundary: follow-mode is forced off locally so a global default can't split."
(let ((s (test-org-agenda-frame--block-settings)))
@@ -136,9 +165,14 @@
(should (null (cadr (assq 'org-agenda-start-with-follow-mode s))))))
(ert-deftest test-org-agenda-frame-command-sticky-and-current-window ()
- "Normal: sticky (distinct buffer) and current-window (no frame split)."
+ "Normal: current-window in the settings; sticky deliberately NOT there.
+The general settings are baked into the buffer's series-redo-cmd and
+re-applied on every redo; a sticky t there makes org-agenda-use-sticky-p
+true mid-redo (the buffer exists), and org-agenda-prepare throws \\='exit
+with no catch -- every refresh tick fails. Stickiness belongs only in
+the spawn wrapper, where it names the buffer."
(let ((g (test-org-agenda-frame--general-settings)))
- (should (eq (cadr (assq 'org-agenda-sticky g)) t))
+ (should-not (assq 'org-agenda-sticky g))
;; org evaluates custom-command setting values via org-let, so the stored
;; form is (quote current-window); eval it the way org would.
(should (eq (eval (cadr (assq 'org-agenda-window-setup g)) t)
@@ -193,6 +227,17 @@
(should (eq (lookup-key cj/agenda-frame-mode-map (kbd "p")) 'org-agenda-previous-line))
(should (eq (lookup-key cj/agenda-frame-mode-map (kbd "C-g")) 'keyboard-quit)))
+(ert-deftest test-org-agenda-frame-map-point-motion-and-isearch-allowed ()
+ "Normal: read-only point motion and isearch work in the frame.
+C-a/C-e/C-f/C-b move point and C-s/C-r search; all are read-only and
+must not hit the deny catch-all."
+ (should (eq (lookup-key cj/agenda-frame-mode-map (kbd "C-a")) 'move-beginning-of-line))
+ (should (eq (lookup-key cj/agenda-frame-mode-map (kbd "C-e")) 'move-end-of-line))
+ (should (eq (lookup-key cj/agenda-frame-mode-map (kbd "C-f")) 'forward-char))
+ (should (eq (lookup-key cj/agenda-frame-mode-map (kbd "C-b")) 'backward-char))
+ (should (eq (lookup-key cj/agenda-frame-mode-map (kbd "C-s")) 'isearch-forward))
+ (should (eq (lookup-key cj/agenda-frame-mode-map (kbd "C-r")) 'isearch-backward)))
+
(ert-deftest test-org-agenda-frame-map-engage-routed ()
"Normal: RET and TAB route to the working-frame engage command."
(should (eq (lookup-key cj/agenda-frame-mode-map (kbd "RET"))