aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-24 07:16:42 -0500
committerCraig Jennings <c@cjennings.net>2026-05-24 07:16:42 -0500
commitbc9652752c231e8634a90c875ed6d206ff172458 (patch)
tree44c5012bb947784f4f8cb16ffd43c8f97308d68f
parent6dfc41af54551bf1fe13af6e1aae6e1e864060c0 (diff)
downloaddotemacs-bc9652752c231e8634a90c875ed6d206ff172458.tar.gz
dotemacs-bc9652752c231e8634a90c875ed6d206ff172458.zip
refactor(video-capture): drop startup timers for lazy protocol init
quick-video-capture scheduled an after-init-hook idle timer plus a 2-second fallback run-with-timer to call cj/setup-video-download, which require-d org-protocol and org-capture and registered both the protocol handler and the capture template. That loaded Org protocol/capture plumbing at every startup even when the video workflow was never used. Split the two concerns the way org-webclipper already does. The org-protocol handler is registered in a with-eval-after-load (quote org-protocol) block — a lightweight add-to-list that needs no org-capture — so it is in place whenever org-protocol loads (org-config requires it at startup). cj/setup-video-download now registers only the capture template, lazily, on the first capture (org-capture-mode-hook) or the first protocol call (the handler ensures it). Both startup timers are gone. Tests pin that setup registers the template idempotently and no longer touches the protocol alist; verified in a live daemon that the protocol registers on load.
-rw-r--r--modules/quick-video-capture.el52
-rw-r--r--tests/test-quick-video-capture--init.el57
2 files changed, 80 insertions, 29 deletions
diff --git a/modules/quick-video-capture.el b/modules/quick-video-capture.el
index d74082e3..a14fefab 100644
--- a/modules/quick-video-capture.el
+++ b/modules/quick-video-capture.el
@@ -77,23 +77,15 @@ bound by the org-protocol entry point, or prompts when invoked manually."
(cj/setup-video-download)))
(defun cj/setup-video-download ()
- "Initialize video download functionality.
-This function sets up org-protocol handlers and capture templates.
-It's designed to be idempotent - safe to call multiple times."
+ "Register the video-download org-capture template, idempotently.
+Runs lazily on the first capture (via `org-capture-mode-hook') or the
+first protocol call, so module load has no startup side effect. The
+org-protocol handler is registered separately in the
+`with-eval-after-load' block below, which is the lightweight piece a
+protocol URL needs; the capture template is the heavier piece deferred
+to here."
(when (not cj/video-download-initialized)
- ;; Load required packages
- (require 'org-protocol)
(require 'org-capture)
-
- ;; Register the org-protocol handler if not already registered
- (unless (assoc "video-download" org-protocol-protocol-alist)
- (add-to-list 'org-protocol-protocol-alist
- '("video-download"
- :protocol "video-download"
- :function cj/org-protocol-video-download
- :kill-client t)))
-
- ;; Add the capture template if not already added
(unless (assoc "v" org-capture-templates)
(add-to-list 'org-capture-templates
'("v" "Video Download" entry
@@ -101,9 +93,8 @@ It's designed to be idempotent - safe to call multiple times."
"%(cj/video-download-capture-handler)"
:immediate-finish t
:jump-to-captured nil)))
-
(setq cj/video-download-initialized t)
- (cj/log-silently "Video download functionality initialized")))
+ (cj/log-silently "Video download capture template registered")))
(defun cj/video-download-bookmarklet-instructions ()
"Display instructions for setting up the browser bookmarklet."
@@ -124,18 +115,21 @@ It's designed to be idempotent - safe to call multiple times."
(insert "and emacsclient is properly configured for org-protocol.\n"))
(switch-to-buffer buf)))
-;; Deferred initialization strategy:
-;; 1. Try to load shortly after Emacs is idle following init
-;; 2. Fallback timer ensures loading within 2 seconds regardless
-(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))
-
-;; If someone manually triggers capture before initialization
+;; Register the org-protocol handler as soon as org-protocol is available.
+;; This is the lightweight piece a "video-download" URL needs — it adds an
+;; entry to the alist without loading org-capture or yt-dlp. No startup
+;; timer: the capture template is set up lazily on first use (below), and
+;; the protocol handler ensures it before capturing.
+(with-eval-after-load 'org-protocol
+ (unless (assoc "video-download" org-protocol-protocol-alist)
+ (add-to-list 'org-protocol-protocol-alist
+ '("video-download"
+ :protocol "video-download"
+ :function cj/org-protocol-video-download
+ :kill-client t))))
+
+;; Set up the capture template the first time a capture starts, so a manual
+;; C-c c never races an uninitialized template.
(with-eval-after-load 'org-capture
(add-hook 'org-capture-mode-hook #'cj/ensure-video-download-initialized))
diff --git a/tests/test-quick-video-capture--init.el b/tests/test-quick-video-capture--init.el
new file mode 100644
index 00000000..ffdb547a
--- /dev/null
+++ b/tests/test-quick-video-capture--init.el
@@ -0,0 +1,57 @@
+;;; test-quick-video-capture--init.el --- Tests for video-capture lazy init -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Initialization is split so module load has no startup side effects:
+;; `cj/setup-video-download' registers only the org-capture template (lazily,
+;; on first capture or first protocol call), while the org-protocol handler is
+;; registered separately via `with-eval-after-load'. These tests pin that
+;; separation so setup can't drag in org-protocol plumbing on its own.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+;; Load these for real so `org-capture-templates' and
+;; `org-protocol-protocol-alist' are genuine special vars the tests can
+;; dynamically rebind (a `let' over a non-special symbol would be lexical
+;; and invisible to the module's functions).
+(require 'org-capture)
+(require 'org-protocol)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'quick-video-capture)
+
+;;; cj/setup-video-download
+
+(ert-deftest test-qvc-setup-registers-capture-template ()
+ "Normal: setup registers the \"v\" capture template and flips the flag."
+ (let ((org-capture-templates nil)
+ (cj/video-download-initialized nil))
+ (cl-letf (((symbol-function 'cj/log-silently) #'ignore))
+ (cj/setup-video-download)
+ (should (assoc "v" org-capture-templates))
+ (should cj/video-download-initialized))))
+
+(ert-deftest test-qvc-setup-is-idempotent ()
+ "Boundary: a second setup call does not duplicate the capture template."
+ (let ((org-capture-templates nil)
+ (cj/video-download-initialized nil))
+ (cl-letf (((symbol-function 'cj/log-silently) #'ignore))
+ (cj/setup-video-download)
+ (cj/setup-video-download)
+ (should (= 1 (length (seq-filter (lambda (e) (equal (car e) "v"))
+ org-capture-templates)))))))
+
+(ert-deftest test-qvc-setup-does-not-register-protocol ()
+ "Boundary: setup touches only capture state, not the org-protocol alist.
+Protocol registration is the job of the `with-eval-after-load' block, so
+setup must not add to `org-protocol-protocol-alist' on its own."
+ (let ((org-capture-templates nil)
+ (org-protocol-protocol-alist nil)
+ (cj/video-download-initialized nil))
+ (cl-letf (((symbol-function 'cj/log-silently) #'ignore))
+ (cj/setup-video-download)
+ (should-not (assoc "video-download" org-protocol-protocol-alist)))))
+
+(provide 'test-quick-video-capture--init)
+;;; test-quick-video-capture--init.el ends here