From c3420106b57b999db6526c62c1ce0e33c28ef121 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 15 May 2026 02:13:37 -0500 Subject: test(architecture): guard top-level timers + add startup-contract smoke test Add a tiny source-level architecture suite at tests/test-architecture-startup-contracts.el with two checks: - Only keybindings.el may globally own the exact C-; prefix. Catches accidental cross-module rebinding before it ships. - Top-level timer scheduling (run-with-timer / run-at-time / run-with-idle-timer) must be guarded by (unless noninteractive ...) so requiring a module in batch / test mode does not schedule startup timers. Timer calls inside defuns are exempt -- the test only rejects forms that execute their body when the module loads. Four modules had unguarded top-level timer scheduling and would have tripped the new test. Wrap their startup hooks/timers in (unless noninteractive ...): - modules/org-agenda-config.el: 10s idle cache build - modules/org-refile-config.el: 5s idle cache build - modules/quick-video-capture.el: after-init-hook + 2s fallback - modules/wrap-up.el: emacs-startup-hook bury-buffers delay The contract being protected is "requiring a module in batch should not start a clock running." Test failures will now point straight at the offending file/form. --- modules/org-agenda-config.el | 15 ++++++++------- modules/org-refile-config.el | 15 ++++++++------- modules/quick-video-capture.el | 13 +++++++------ modules/wrap-up.el | 3 ++- 4 files changed, 25 insertions(+), 21 deletions(-) (limited to 'modules') diff --git a/modules/org-agenda-config.el b/modules/org-agenda-config.el index 9e64a04b..c37ec1c9 100644 --- a/modules/org-agenda-config.el +++ b/modules/org-agenda-config.el @@ -199,13 +199,14 @@ improves performance from several seconds to instant." (- (float-time) (float-time start-time))))))) (setq org-agenda-files files))) -;; Build cache asynchronously after startup to avoid blocking Emacs -(run-with-idle-timer - 10 ; Wait 10 seconds after Emacs is idle - nil ; Don't repeat - (lambda () - (cj/log-silently "Building org-agenda files cache in background...") - (cj/build-org-agenda-list))) +;; Build cache asynchronously after startup to avoid blocking Emacs. +(unless noninteractive + (run-with-idle-timer + 10 ; Wait 10 seconds after Emacs is idle + nil ; Don't repeat + (lambda () + (cj/log-silently "Building org-agenda files cache in background...") + (cj/build-org-agenda-list)))) (defun cj/org-agenda-refresh-files () "Force rebuild of agenda files cache. diff --git a/modules/org-refile-config.el b/modules/org-refile-config.el index 63343e9f..16b37bf9 100644 --- a/modules/org-refile-config.el +++ b/modules/org-refile-config.el @@ -101,13 +101,14 @@ so caching improves performance from 15-20 seconds to instant." (- (float-time) (float-time start-time))))))) (setq org-refile-targets targets))) -;; Build cache asynchronously after startup to avoid blocking Emacs -(run-with-idle-timer - 5 ; Wait 5 seconds after Emacs is idle - nil ; Don't repeat - (lambda () - (cj/log-silently "Building org-refile targets cache in background...") - (cj/build-org-refile-targets))) +;; Build cache asynchronously after startup to avoid blocking Emacs. +(unless noninteractive + (run-with-idle-timer + 5 ; Wait 5 seconds after Emacs is idle + nil ; Don't repeat + (lambda () + (cj/log-silently "Building org-refile targets cache in background...") + (cj/build-org-refile-targets)))) (defun cj/org-refile-refresh-targets () "Force rebuild of refile targets cache. diff --git a/modules/quick-video-capture.el b/modules/quick-video-capture.el index 4e62309e..0e9567dd 100644 --- a/modules/quick-video-capture.el +++ b/modules/quick-video-capture.el @@ -125,12 +125,13 @@ It's designed to be idempotent - safe to call multiple times." ;; Deferred initialization strategy: ;; 1. Try to load shortly after Emacs is idle following init ;; 2. Fallback timer ensures loading within 2 seconds regardless -(add-hook 'after-init-hook - (lambda () - (run-with-idle-timer 0.5 nil #'cj/setup-video-download))) +(unless noninteractive + (add-hook 'after-init-hook + (lambda () + (run-with-idle-timer 0.5 nil #'cj/setup-video-download))) -;; Fallback: ensure initialization within 2 seconds of loading this file -(run-with-timer 2 nil #'cj/setup-video-download) + ;; Fallback: ensure initialization within 2 seconds of loading this file + (run-with-timer 2 nil #'cj/setup-video-download)) ;; If someone manually triggers capture before initialization (with-eval-after-load 'org-capture @@ -145,4 +146,4 @@ It's designed to be idempotent - safe to call multiple times." ;; the download through yt-dlp and task-spooler. (provide 'quick-video-capture) -;;; quick-video-capture.el ends here \ No newline at end of file +;;; quick-video-capture.el ends here diff --git a/modules/wrap-up.el b/modules/wrap-up.el index 523d55b2..93b74776 100644 --- a/modules/wrap-up.el +++ b/modules/wrap-up.el @@ -25,7 +25,8 @@ (defun cj/bury-buffers-after-delay () "Run cj/bury-buffers after a delay." (run-with-timer 1 nil 'cj/bury-buffers)) -(add-hook 'emacs-startup-hook 'cj/bury-buffers-after-delay) +(unless noninteractive + (add-hook 'emacs-startup-hook 'cj/bury-buffers-after-delay)) (cj/log-silently "<-- end of init file.") -- cgit v1.2.3