diff options
69 files changed, 717 insertions, 427 deletions
@@ -92,4 +92,4 @@ Prefer Write over cumulative Edits for nontrivial new code. Small functions (und - **`make test` runs with no `package-initialize` — defuns inside a `use-package :config` are void there.** The Makefile's `EMACS_TEST` is `emacs --batch --no-site-file --no-site-lisp` with no `package-initialize`, so elpa packages never load and a `use-package` block whose package isn't found never runs its `:config`. Any `defun` nested inside that `:config` is unbound under `make test` / `make test-file`. The per-edit PostToolUse hook *does* initialize packages, so such defuns load there — a test can pass on save under the hook yet fail `make test`. To unit-test logic that lives in a `:config` block, extract it into a top-level defun outside `use-package` (the `cj/dwim-shell--empty-dirs-command` / `cj/dwim-shell--dated-backup-command` pattern) and test that; keybindings or mode-wiring that must stay in `:config` get live-daemon verification instead. (`gotcha` — 2026-06-13) -- **Don't `cl-letf`-mock C primitives in tests — it triggers a native-comp trampoline rebuild that fails under `--batch`.** Mocking a primitive like `buffer-modified-p`, `file-exists-p`, or `kill-buffer` via `cl-letf`/`fset` makes native-comp try to compile and load a trampoline `.eln`, which errors under `emacs --batch` (`native-lisp-load-failed "file does not exists" .../subr--trampoline-*.eln`, often after a "Redefining 'X' might break native compilation of trampolines" warning). Don't mock the primitive: drive the real state instead (a `make-temp-file` fixture so `file-exists-p` is true for real, `insert`/`set-buffer-modified-p` for modified state, `buffer-live-p` to detect a kill), or extract the decision logic into a pure helper and test that. Mocking ordinary Lisp functions (`y-or-n-p`, `save-buffer`, `info`) is fine — the trap is specific to subrs. (`gotcha` — 2026-06-13) +- **Mocking a C primitive (subr) in a test is fragile under native-comp; if you must, make the mock variadic — `(lambda (&rest _) ...)`.** When a test redefines a primitive (`cl-letf`/`fset`/`setf`/`advice-add`), native-comp routes natively-compiled callers through a per-primitive trampoline `.eln`, and that interaction fails three different ways depending on eln-cache state: (1) the trampoline `.eln` fails to build/load under `--batch` (`native-lisp-load-failed ... subr--trampoline-*.eln`); (2) when no trampoline is available the redefinition is *silently ignored* and native callers run the real primitive (a quiet false pass); (3) the trampoline calls the mock with the primitive's *maximum* arity, so a fixed-arity mock narrower than the primitive throws `wrong-number-of-arguments`. Mode 3 is the common one — a `(lambda (_) 200)` mock of `window-body-width` (a 0-2-arg subr) gets called with 2 args. Note many routinely-mocked functions are subrs (`message`, `completing-read`, `y-or-n-p`, `executable-find`, `save-buffer`, `byte-compile-file`), and those are fine *because* they're mocked variadically; the trap is the narrow fixed-arity ones. The rule, enforced by `tests/test-meta-subr-mock-arity.el` (fails `make test` on any arity-narrow subr mock): a subr mock must accept the primitive's max arity, so append `&rest _` (keep named args the body uses: `(lambda (cmd &rest _) ...)`). The durable fix the ecosystem and our own `elisp-testing.md` point to is *don't mock the primitive*: drive real state (a `make-temp-file` fixture, `insert`/`set-buffer-modified-p`) or extract a pure helper and test that. Full mechanism, the three modes, research, and decision: [[file:docs/native-comp-subr-mocking.org][docs/native-comp-subr-mocking.org]]. (`gotcha` — refined 2026-06-21 after re-enabling native-comp surfaced 170 latent arity-narrow mocks) diff --git a/docs/design/module-inventory.org b/docs/design/module-inventory.org index 3903d0ba9..fb883d701 100644 --- a/docs/design/module-inventory.org +++ b/docs/design/module-inventory.org @@ -205,7 +205,7 @@ flyspell-and-abbrev is the one Core-UX member (text-mode hooks). | =eshell-config= | 3 | D/P | eager | command | system-utils | add-hook, advice-add, package config | yes | | =eww-config= | 3 | D/P | eager | command | cl-lib | package config | yes | | =flyspell-and-abbrev= | 2 | C/P | eager | hook | cl-lib | mode-hook package config | yes | -| =games-config= | 4 | O | command | command | none | package config | yes | +| =games-config= | 4 | O | command | command | user-constants | package config | yes | | =gloss-config= | 4 | O/D/P | eager | command | none | package config | yes | | =httpd-config= | 4 | O/D/P | eager | command | none | package config | yes | | =jumper= | 4 | O/L | eager | command | cl-lib | jumper keymap | yes | diff --git a/docs/native-comp-subr-mocking.org b/docs/native-comp-subr-mocking.org new file mode 100644 index 000000000..f66e5d102 --- /dev/null +++ b/docs/native-comp-subr-mocking.org @@ -0,0 +1,159 @@ +#+TITLE: Native Compilation vs. Mocking C Primitives in Tests +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-21 + +* What this is + +A reference for a real, recurring trap: tests that redefine an Emacs C +primitive (a "subr") with =cl-letf=, =fset=, =setf=, or =advice-add= behave +differently once native compilation is enabled, and the failures are +intermittent. We hit it head-on after re-enabling native-comp config-wide +(early-init.el, commit 3fd28987, 2026-06-20). This document records the +mechanism, the research, and the decision so we don't re-derive it. + +* The symptom + +After native-comp was re-enabled, tests that had been green for months started +failing, with no change to their source. The errors looked like: + +: wrong-number-of-arguments #[nil (nil) (t)] 1 + +That is a zero-argument mock lambda being called with one argument. The 8 tests +that first tripped were in =test-dirvish-config-wrappers.el= and +=test-calibredb-epub-config.el=, all mocking window primitives +(=current-window-configuration=, =window-body-width=, =window-margins=, +=get-buffer-window=). + +The failures were intermittent across the session: the same test passed, then +crashed, then passed again. That non-determinism is the tell. + +* The mechanism + +Native-comp emits *direct* calls to primitives for speed. So when Lisp code +redefines or advises a primitive (which is exactly what a test mock does), +natively-compiled callers would normally bypass the redefinition entirely. To +prevent that, Emacs generates a small per-primitive *trampoline* (a =.eln= +under =eln-cache/=) the first time a primitive is redefined. The trampoline +reroutes calls to the primitive through its Lisp function cell, where the mock +lives. + +The trampoline is generated lazily and cached on disk, and that is the source +of the non-determinism: whether a given mock "works" depends on whether the +trampoline for that primitive has been compiled into the eln-cache yet. As +native-comp compiles more in the background, more mocks start routing through +trampolines. + +** Three distinct failure modes + +Because behavior depends on trampoline state, the same mock can fail three +different ways: + +1. *Generation failure.* The trampoline =.eln= can't be built or loaded + (notably under =emacs --batch=), giving + =native-lisp-load-failed "... subr--trampoline-*.eln"=. This is the mode our + older CLAUDE.md insight first documented. +2. *Silent bypass.* When a trampoline isn't available and can't be generated, + the manual states natively-compiled callers *ignore* the redefinition and + call the real primitive. The mock does nothing, so the test passes for the + wrong reason or asserts against real behavior. +3. *Arity mismatch.* The trampoline *is* built and routes to the mock, but + calls it with the primitive's *maximum* arity (filling optionals with nil), + not the arity the source used. A fixed-arity mock narrower than the + primitive then throws =wrong-number-of-arguments=. This is the mode that bit + us this session (every one of the 8 was this). + +* Important: this is a test-only artifact + +Production code never redefines a C primitive, so these trampolines are never +generated for this reason in normal use. Nothing here is a defect in the +config. It is an incompatibility between *mocking primitives in tests* and +native-comp, confined to the test suite. + +* What the wider community has found + +This is well known and genuinely hard. It is not us doing something wrong. + +- [[https://lists.gnu.org/archive/html/bug-gnu-emacs/2021-10/msg00971.html][bug#51140 (emacs-devel)]] — "cl-letf appears not to work with native-comp." + Redefining a built-in like =process-exit-status= via =cl-letf= breaks under + native compilation. Confirms the core problem. +- [[https://github.com/jorgenschaefer/emacs-buttercup/issues/230][buttercup issue #230]] — the buttercup test framework's =spy-on= on primitives + (=file-exists-p=, =buffer-file-name=) fails with the + =native-lisp-load-failed ... subr--trampoline-*.eln= error (failure mode 1). + Our scenario exactly, in a mainstream test framework. +- [[https://groups.google.com/g/linux.debian.bugs.dist/c/n9P2xhpruDE][Debian bug#1021842]] — buttercup's *own self-tests* hit the trampoline + compilation error. Even the test framework's maintainers run into it. +- [[https://lists.gnu.org/archive/html/bug-gnu-emacs/2023-03/msg00076.html][bug#61880 (emacs-devel)]] — native compilation fails to generate trampolines + in certain sequential cases (failure mode 1, deterministic variant). +- [[https://lists.gnu.org/archive/html/emacs-diffs/2023-03/msg00145.html][emacs-29 commit (bug-fix)]] — Emacs added a warning when you redefine a + primitive that the trampoline machinery itself depends on + ("Redefining '%s' might break trampoline native compilation"). Shows the + maintainers' stance: redefining primitives is discouraged. +- [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Native_002dCompilation-Variables.html][ELisp Manual: Native-Compilation Variables]] — documents + =native-comp-enable-subr-trampolines=. Default on; generates trampolines on + the fly. When *off* and no cached trampoline exists, "calls to that primitive + from natively-compiled Lisp will ignore redefinitions and advices" (this is + failure mode 2, and the catch in the common workaround below). + +** The two commonly-cited workarounds, and their costs + +- *Disable subr trampolines for tests* (=native-comp-enable-subr-trampolines + nil=). The most-cited quick fix. One line. But per the manual it makes + natively-compiled callers *ignore* the mock (failure mode 2). It only works + reliably when the code under test runs interpreted, not natively compiled. + With native-comp aggressively compiling our modules, the code under test is + increasingly native, so this risks silent mock-bypass: tests that pass while + asserting against the real primitive. Worse than a loud failure. +- *Don't mock primitives at all.* The maintainers' and our own + =elisp-testing.md='s position: inject dependencies or test pure helpers + instead. The only fix immune to all three failure modes. Also the most work. + +* Our decision (2026-06-21) + +We chose a pragmatic middle path with a clear long-term direction. + +1. *Make subr mocks variadic.* The arity mode (3) is the only one we have + actually suffered. A mock written =(lambda (&rest _) VALUE)= tolerates the + trampoline's full-arity call. We swept every arity-narrow subr mock in the + suite to append =&rest _= to its arglist (preserving any named args the + body uses). This is deterministic and keeps trampolines on, so mocks still + route correctly (no silent bypass). +2. *Enforce it with a meta-test.* =tests/test-meta-subr-mock-arity.el= statically + scans every test file for =symbol-function= / =fset= redefinitions of a + subr and fails =make test= if any mock can't accept the primitive's maximum + arity (=func-arity=). It is deterministic (a pure source read; no dependence + on eln-cache state), so a new arity-narrow mock can't merge silently. The + rule it enforces is NOT "never mock a subr" (the suite mocks subrs like + =message= and =completing-read= hundreds of times, all fine) but "a subr + mock must accept the primitive's arity." +3. *Treat "migrate off primitive-mocking" as a long-term test-quality project.* + The variadic sweep fixes the mode we hit but leaves modes 1 and 2 latent + (we haven't hit them, but they exist). The durable fix the ecosystem points + to is restructuring tests to not redefine primitives at all. Filed as a + standalone TODO rather than forced now. + +** Why not just disable trampolines for tests? + +Because of failure mode 2 (silent bypass) above. In our native-comp-heavy +setup, disabling trampolines would let natively-compiled code under test ignore +the mocks, producing tests that pass while testing nothing. A loud +=wrong-number-of-arguments= that the meta-test prevents up front is strictly +safer than a quiet false pass. + +* Practical rule for writing tests (today) + +When you mock a C primitive (subr) in a test, make the replacement variadic: + +: (cl-letf (((symbol-function 'window-body-width) (lambda (&rest _) 200))) +: ...) + +not + +: (cl-letf (((symbol-function 'window-body-width) (lambda (_) 200))) ; breaks under native-comp +: ...) + +If the body needs the argument, keep it and append =&rest _=: + +: (lambda (cmd &rest _) (member cmd allowed)) + +The meta-test will catch you if you forget. Better still, when practical, don't +mock the primitive: pass the value in as a parameter, or test a pure helper. @@ -153,10 +153,10 @@ ;; ------------------------------- Entertainment ------------------------------- (require 'music-config) -;; games-config: deferred (load-graph Phase 4). malyon / 2048-game autoload the -;; module, so it loads on first use instead of at startup. -(autoload 'malyon "games-config" "Play interactive fiction; loads games-config." t) -(autoload '2048-game "games-config" "Play 2048; loads games-config." t) +;; games-config: deferred (load-graph Phase 4). malyon / 2048-game autoload +;; their own commands via package.el; games-config only supplies malyon's config, +;; so load it when malyon loads rather than requiring it at startup. +(with-eval-after-load 'malyon (require 'games-config)) ;; ------------------------------- Misc Modules -------------------------------- diff --git a/modules/games-config.el b/modules/games-config.el index 1e5ba5b87..aa26d31ee 100644 --- a/modules/games-config.el +++ b/modules/games-config.el @@ -6,37 +6,27 @@ ;; Layer: 4 (Optional). ;; Category: O. ;; Load shape: command (deferred). -;; Eager reason: none; loaded on first use of `malyon' or `2048-game'. -;; Top-level side effects: package configuration via use-package (deferred). -;; Runtime requires: none. +;; Eager reason: none; loaded by init.el when malyon loads. +;; Top-level side effects: sets malyon-stories-directory after malyon loads. +;; Runtime requires: user-constants. ;; Direct test load: yes. ;; ;; Configuration for game packages. ;; -;; - Malyon for playing interactive fiction and text adventures in Z-machine format -;; (stories directory: ~/sync/org/text.games/) -;; - 2048 number-tile puzzle game +;; - Malyon: interactive fiction / Z-machine player (stories under ~/sync/org/text.games/). +;; - 2048: number-tile puzzle. ;; -;; init.el autoloads `malyon' and `2048-game' to this module instead of -;; requiring it eagerly, so the first invocation of either command loads -;; games-config, which configures and then loads the package. +;; malyon and 2048-game autoload their own commands via package.el, so this +;; module owns neither command -- it only supplies malyon's stories directory. +;; init.el loads it via `with-eval-after-load 'malyon', so it loads on first +;; use rather than at startup. ;; ;;; Code: -;; ----------------------------------- Malyon ---------------------------------- -;; text based adventure player +(require 'user-constants) ;; org-dir -(use-package malyon - :defer t - :commands (malyon) - :config +(with-eval-after-load 'malyon (setq malyon-stories-directory (concat org-dir "text.games/"))) -;; ------------------------------------ 2048 ----------------------------------- -;; combine numbered tiles to create the elusive number 2048. -(use-package 2048-game - :defer t - :commands (2048-game)) - (provide 'games-config) ;;; games-config.el ends here. diff --git a/modules/system-utils.el b/modules/system-utils.el index 254a2f502..c76193a71 100644 --- a/modules/system-utils.el +++ b/modules/system-utils.el @@ -123,7 +123,8 @@ detached from Emacs." read-char-history face-name-history bookmark-history - file-name-history)) + file-name-history + wttrin--location-history)) (put 'minibuffer-history 'history-length 50) (put 'file-name-history 'history-length 50) diff --git a/tests/test-ai-config-commands.el b/tests/test-ai-config-commands.el index 8da2e4b01..fed06d82b 100644 --- a/tests/test-ai-config-commands.el +++ b/tests/test-ai-config-commands.el @@ -86,7 +86,7 @@ globally and reports via `message'." added) (unwind-protect (cl-letf (((symbol-function 'featurep) - (lambda (sym) (not (eq sym 'projectile)))) + (lambda (sym &rest _) (not (eq sym 'projectile)))) ((symbol-function 'read-file-name) (lambda (&rest _) target)) ((symbol-function 'gptel-add-file) @@ -133,7 +133,7 @@ globally and reports via `message'." (cl-letf (((symbol-function 'gptel-context-remove-all) (lambda () (setq called t))) ((symbol-function 'call-interactively) - (lambda (fn) (funcall fn))) + (lambda (fn &rest _) (funcall fn))) ((symbol-function 'message) (lambda (fmt &rest args) (setq msg (apply #'format fmt args))))) (cj/gptel-context-clear)) diff --git a/tests/test-ai-config-gptel-commands.el b/tests/test-ai-config-gptel-commands.el index 371a75cc8..cab23572e 100644 --- a/tests/test-ai-config-gptel-commands.el +++ b/tests/test-ai-config-gptel-commands.el @@ -128,7 +128,7 @@ (ert-deftest test-ai-config-add-this-buffer-calls-gptel-add-with-prefix () "Normal: `cj/gptel-add-this-buffer' calls `gptel-add' with the (4) prefix arg." (let ((arg nil)) - (cl-letf (((symbol-function 'featurep) (lambda (_) t)) + (cl-letf (((symbol-function 'featurep) (lambda (_ &rest _) t)) ((symbol-function 'gptel-add) (lambda (a) (setq arg a))) ((symbol-function 'message) #'ignore)) @@ -144,7 +144,7 @@ (deleted nil)) (unwind-protect (cl-letf (((symbol-function 'get-buffer-window) - (lambda (_b) 'fake-window)) + (lambda (_b &rest _) 'fake-window)) ((symbol-function 'delete-window) (lambda (w) (setq deleted w)))) (cj/toggle-gptel)) diff --git a/tests/test-calibredb-epub-config.el b/tests/test-calibredb-epub-config.el index 48d638358..cb3a9ba74 100644 --- a/tests/test-calibredb-epub-config.el +++ b/tests/test-calibredb-epub-config.el @@ -29,8 +29,8 @@ `(with-temp-buffer (setq-local major-mode 'nov-mode) (cl-letf (((symbol-function 'get-buffer-window) (lambda (&rest _) 'win)) - ((symbol-function 'window-body-width) (lambda (_) 200)) - ((symbol-function 'window-margins) (lambda (_) '(nil . nil))) + ((symbol-function 'window-body-width) (lambda (&rest _) 200)) + ((symbol-function 'window-margins) (lambda (&rest _) '(nil . nil))) ((symbol-function 'set-window-margins) (lambda (&rest _) nil)) ((symbol-function 'set-window-fringes) (lambda (&rest _) nil))) ,@body))) @@ -73,8 +73,8 @@ below 50% of the usable columns." (let ((cj/nov-margin-percent 25) (cj/nov-min-text-width 40)) (cl-letf (((symbol-function 'get-buffer-window) (lambda (&rest _) 'win)) - ((symbol-function 'window-body-width) (lambda (_) 120)) - ((symbol-function 'window-margins) (lambda (_) '(nil . nil)))) + ((symbol-function 'window-body-width) (lambda (&rest _) 120)) + ((symbol-function 'window-margins) (lambda (&rest _) '(nil . nil)))) (should (= 60 (cj/nov--text-width-for-window)))))) (ert-deftest test-calibredb-epub-nov-text-width-for-window-idempotent () @@ -85,8 +85,8 @@ this, every layout pass would shave the column by another margin fraction." (let ((cj/nov-margin-percent 25) (cj/nov-min-text-width 40)) (cl-letf (((symbol-function 'get-buffer-window) (lambda (&rest _) 'win)) - ((symbol-function 'window-body-width) (lambda (_) 60)) - ((symbol-function 'window-margins) (lambda (_) '(30 . 30)))) + ((symbol-function 'window-body-width) (lambda (&rest _) 60)) + ((symbol-function 'window-margins) (lambda (&rest _) '(30 . 30)))) (should (= 60 (cj/nov--text-width-for-window)))))) (ert-deftest test-calibredb-epub-nov-text-width-for-window-no-window () @@ -214,15 +214,15 @@ so nov's `shr' fills the text itself rather than relying on visual-fill-column." (ert-deftest test-calibredb-epub-nov-natural-window-width-no-margins () "Normal: with no margins set, the natural width equals `window-body-width'." (cl-letf (((symbol-function 'get-buffer-window) (lambda (&rest _) 'win)) - ((symbol-function 'window-body-width) (lambda (_) 100)) - ((symbol-function 'window-margins) (lambda (_) '(nil . nil)))) + ((symbol-function 'window-body-width) (lambda (&rest _) 100)) + ((symbol-function 'window-margins) (lambda (&rest _) '(nil . nil)))) (should (= 100 (cj/nov--natural-window-width))))) (ert-deftest test-calibredb-epub-nov-natural-window-width-adds-margins () "Boundary: with margins set, the natural width adds them back to the body." (cl-letf (((symbol-function 'get-buffer-window) (lambda (&rest _) 'win)) - ((symbol-function 'window-body-width) (lambda (_) 60)) - ((symbol-function 'window-margins) (lambda (_) '(20 . 20)))) + ((symbol-function 'window-body-width) (lambda (&rest _) 60)) + ((symbol-function 'window-margins) (lambda (&rest _) '(20 . 20)))) (should (= 100 (cj/nov--natural-window-width))))) (ert-deftest test-calibredb-epub-nov-natural-window-width-no-window-fallback () diff --git a/tests/test-config-utilities--compile-this-elisp-buffer.el b/tests/test-config-utilities--compile-this-elisp-buffer.el index fb5e288a1..a06440abb 100644 --- a/tests/test-config-utilities--compile-this-elisp-buffer.el +++ b/tests/test-config-utilities--compile-this-elisp-buffer.el @@ -21,7 +21,7 @@ effects." (declare (indent 1) (debug t)) `(with-temp-buffer (setq buffer-file-name ,path) - (cl-letf (((symbol-function 'save-buffer) (lambda () nil))) + (cl-letf (((symbol-function 'save-buffer) (lambda (&rest _) nil))) ,@body))) (ert-deftest test-config-utilities-compile-buffer-not-elisp-raises () @@ -47,7 +47,7 @@ effects." ((symbol-function 'native-compile) (lambda (_) (error "should not call sync native-compile"))) ((symbol-function 'byte-compile-file) - (lambda (_) (error "should not call byte-compile-file")))) + (lambda (&rest _) (error "should not call byte-compile-file")))) (cj/compile-this-elisp-buffer) (should (equal called-with "/tmp/some.el")))))) @@ -60,7 +60,7 @@ effects." ((symbol-function 'native-compile) (lambda (file) (setq called-with file))) ((symbol-function 'byte-compile-file) - (lambda (_) (error "should not call byte-compile-file")))) + (lambda (&rest _) (error "should not call byte-compile-file")))) (cj/compile-this-elisp-buffer) (should (equal called-with "/tmp/some.el")))))) @@ -71,7 +71,7 @@ effects." (cl-letf (((symbol-function 'fboundp) (lambda (sym) (eq sym 'byte-compile-file))) ((symbol-function 'byte-compile-file) - (lambda (file) (setq called-with file) "/tmp/some.elc"))) + (lambda (file &rest _) (setq called-with file) "/tmp/some.elc"))) (cj/compile-this-elisp-buffer) (should (equal called-with "/tmp/some.el")))))) diff --git a/tests/test-custom-buffer-file-print-diff-eww.el b/tests/test-custom-buffer-file-print-diff-eww.el index 9aa73cbee..56cc917e0 100644 --- a/tests/test-custom-buffer-file-print-diff-eww.el +++ b/tests/test-custom-buffer-file-print-diff-eww.el @@ -30,14 +30,14 @@ (let ((cj/print-spooler-command "lpr") (cj/print--spooler-cache nil)) (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (when (equal cmd "lpr") "/usr/bin/lpr")))) + (lambda (cmd &rest _) (when (equal cmd "lpr") "/usr/bin/lpr")))) (should (equal (cj/print--resolve-spooler) "lpr"))))) (ert-deftest test-cbf-resolve-spooler-explicit-string-missing-errors () "Error: explicit string spooler not on PATH signals user-error." (let ((cj/print-spooler-command "notathing") (cj/print--spooler-cache nil)) - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (should-error (cj/print--resolve-spooler) :type 'user-error)))) (ert-deftest test-cbf-resolve-spooler-auto-detects-lpr-first () @@ -45,7 +45,7 @@ (let ((cj/print-spooler-command 'auto) (cj/print--spooler-cache nil)) (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (when (equal cmd "lpr") "/usr/bin/lpr")))) + (lambda (cmd &rest _) (when (equal cmd "lpr") "/usr/bin/lpr")))) (should (equal (cj/print--resolve-spooler) "lpr")) (should (equal cj/print--spooler-cache "lpr"))))) @@ -54,14 +54,14 @@ (let ((cj/print-spooler-command 'auto) (cj/print--spooler-cache nil)) (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (when (equal cmd "lp") "/usr/bin/lp")))) + (lambda (cmd &rest _) (when (equal cmd "lp") "/usr/bin/lp")))) (should (equal (cj/print--resolve-spooler) "lp"))))) (ert-deftest test-cbf-resolve-spooler-auto-no-tool-errors () "Error: `auto' with neither lpr nor lp signals user-error." (let ((cj/print-spooler-command 'auto) (cj/print--spooler-cache nil)) - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (should-error (cj/print--resolve-spooler) :type 'user-error)))) (ert-deftest test-cbf-resolve-spooler-auto-returns-cached-value () @@ -69,7 +69,7 @@ (let ((cj/print-spooler-command 'auto) (cj/print--spooler-cache "cached-cmd")) (cl-letf (((symbol-function 'executable-find) - (lambda (_) (error "should not be called")))) + (lambda (_ &rest _) (error "should not be called")))) (should (equal (cj/print--resolve-spooler) "cached-cmd"))))) (ert-deftest test-cbf-resolve-spooler-invalid-value-errors () @@ -87,7 +87,7 @@ (with-temp-buffer (rename-buffer "*test-cbf-copy-name*" t) (cl-letf (((symbol-function 'kill-new) - (lambda (s) (setq killed s))) + (lambda (s &rest _) (setq killed s))) ((symbol-function 'message) (lambda (fmt &rest args) (setq msg (apply #'format fmt args))))) diff --git a/tests/test-dev-fkeys--f6-current-file-tests-impl.el b/tests/test-dev-fkeys--f6-current-file-tests-impl.el index 1cf222305..2d8e43858 100644 --- a/tests/test-dev-fkeys--f6-current-file-tests-impl.el +++ b/tests/test-dev-fkeys--f6-current-file-tests-impl.el @@ -111,7 +111,7 @@ runner instead of erroring as unsupported." (let ((compile-called nil)) (cl-letf (((symbol-function 'compile) (lambda (cmd) (setq compile-called cmd))) - ((symbol-function 'executable-find) (lambda (_) nil))) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (cj/--f6-current-file-tests-impl "/home/u/proj/src/foo.test.ts" "/home/u/proj/") (should (stringp compile-called)) diff --git a/tests/test-dev-fkeys--f6-current-file-tests.el b/tests/test-dev-fkeys--f6-current-file-tests.el index 3f6adc255..97c1c7675 100644 --- a/tests/test-dev-fkeys--f6-current-file-tests.el +++ b/tests/test-dev-fkeys--f6-current-file-tests.el @@ -16,7 +16,7 @@ (ert-deftest test-dev-fkeys-f6-current-file-tests-routes-to-impl () "Normal: C-F6 invokes the orchestrator with buffer file and projectile root." (let (seen-file seen-root) - (cl-letf (((symbol-function 'buffer-file-name) (lambda () "/p/foo.el")) + (cl-letf (((symbol-function 'buffer-file-name) (lambda (&rest _) "/p/foo.el")) ((symbol-function 'cj/--f4-project-root) (lambda () "/p/")) ((symbol-function 'cj/--f6-current-file-tests-impl) (lambda (file root) diff --git a/tests/test-dev-fkeys--f6-test-runner-cmd-for.el b/tests/test-dev-fkeys--f6-test-runner-cmd-for.el index 9a5526125..d7b6a0597 100644 --- a/tests/test-dev-fkeys--f6-test-runner-cmd-for.el +++ b/tests/test-dev-fkeys--f6-test-runner-cmd-for.el @@ -126,13 +126,13 @@ neither tool is present, the user gets a clear runner-not-found error rather than a silent nil that F6's outer wrapper interprets as \"language unsupported.\"" (cl-letf (((symbol-function 'executable-find) - (lambda (_) nil))) + (lambda (_ &rest _) nil))) (should (equal (cj/--f6-test-runner-cmd-for 'typescript t "src/foo.test.ts" "foo" "src") "npx --no-install jest src/foo.test.ts"))) (cl-letf (((symbol-function 'executable-find) - (lambda (p) (when (equal p "vitest") "/usr/bin/vitest")))) + (lambda (p &rest _) (when (equal p "vitest") "/usr/bin/vitest")))) (should (equal (cj/--f6-test-runner-cmd-for 'typescript t "src/foo.test.ts" "foo" "src") diff --git a/tests/test-dev-fkeys--f6-test-runner.el b/tests/test-dev-fkeys--f6-test-runner.el index eb9cec5ef..d5f58a66d 100644 --- a/tests/test-dev-fkeys--f6-test-runner.el +++ b/tests/test-dev-fkeys--f6-test-runner.el @@ -79,7 +79,7 @@ Components integrated: (lambda (&rest _) "Current file's tests")) ((symbol-function 'projectile-test-project) (lambda (_arg) nil)) ((symbol-function 'cj/--f4-project-root) (lambda () "/p/")) - ((symbol-function 'buffer-file-name) (lambda () "/p/foo.el")) + ((symbol-function 'buffer-file-name) (lambda (&rest _) "/p/foo.el")) ((symbol-function 'cj/--f6-current-file-tests-impl) (lambda (file root) (setq seen-file file seen-root root)))) diff --git a/tests/test-dev-fkeys--projectile-advice-install.el b/tests/test-dev-fkeys--projectile-advice-install.el index bfa9b691f..d0a9a9cc0 100644 --- a/tests/test-dev-fkeys--projectile-advice-install.el +++ b/tests/test-dev-fkeys--projectile-advice-install.el @@ -16,7 +16,7 @@ "When Projectile is not loaded, registration should use `eval-after-load'." (let (registered-feature registered-form install-called) (cl-letf (((symbol-function 'featurep) - (lambda (feature) (and (not (eq feature 'projectile)) + (lambda (feature &rest _) (and (not (eq feature 'projectile)) (featurep feature)))) ((symbol-function 'eval-after-load) (lambda (feature form) @@ -33,7 +33,7 @@ "When Projectile is already loaded, registration should install immediately." (let (install-called eval-after-load-called) (cl-letf (((symbol-function 'featurep) - (lambda (feature) (eq feature 'projectile))) + (lambda (feature &rest _) (eq feature 'projectile))) ((symbol-function 'eval-after-load) (lambda (&rest _args) (setq eval-after-load-called t))) ((symbol-function 'cj/--projectile-install-revert-advice) diff --git a/tests/test-dirvish-config-drill.el b/tests/test-dirvish-config-drill.el index f26de6d87..de0541a0c 100644 --- a/tests/test-dirvish-config-drill.el +++ b/tests/test-dirvish-config-drill.el @@ -34,7 +34,7 @@ "Normal: an `.org' file at point is opened and drilled." (let (opened (drilled 0)) (cl-letf (((symbol-function 'dired-get-filename) (lambda (&rest _) "/tmp/decks/cards.org")) - ((symbol-function 'find-file) (lambda (f) (setq opened f))) + ((symbol-function 'find-file) (lambda (f &rest _) (setq opened f))) ((symbol-function 'cj/drill-this-file) (lambda (&rest _) (cl-incf drilled)))) (cj/dirvish-drill-file)) (should (equal "/tmp/decks/cards.org" opened)) @@ -44,7 +44,7 @@ "Boundary: the `.org' check ignores case." (let (opened) (cl-letf (((symbol-function 'dired-get-filename) (lambda (&rest _) "/tmp/decks/CARDS.ORG")) - ((symbol-function 'find-file) (lambda (f) (setq opened f))) + ((symbol-function 'find-file) (lambda (f &rest _) (setq opened f))) ((symbol-function 'cj/drill-this-file) #'ignore)) (cj/dirvish-drill-file)) (should (equal "/tmp/decks/CARDS.ORG" opened)))) diff --git a/tests/test-dirvish-config-print.el b/tests/test-dirvish-config-print.el index ab6d073f0..308d00f68 100644 --- a/tests/test-dirvish-config-print.el +++ b/tests/test-dirvish-config-print.el @@ -50,18 +50,18 @@ (ert-deftest test-dirvish-print-program-prefers-lp () "Normal: `lp' is used when available." (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (when (equal cmd "lp") "/usr/bin/lp")))) + (lambda (cmd &rest _) (when (equal cmd "lp") "/usr/bin/lp")))) (should (equal (cj/--print-program) "/usr/bin/lp")))) (ert-deftest test-dirvish-print-program-falls-back-to-lpr () "Boundary: `lpr' is used when `lp' is missing." (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (when (equal cmd "lpr") "/usr/bin/lpr")))) + (lambda (cmd &rest _) (when (equal cmd "lpr") "/usr/bin/lpr")))) (should (equal (cj/--print-program) "/usr/bin/lpr")))) (ert-deftest test-dirvish-print-program-none-available () "Error: nil when neither `lp' nor `lpr' is on PATH." - (cl-letf (((symbol-function 'executable-find) (lambda (_cmd) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_cmd &rest _) nil))) (should-not (cj/--print-program)))) ;;; ---------------------------- cj/dirvish-print-file ------------------------- diff --git a/tests/test-dirvish-config-public-wrappers.el b/tests/test-dirvish-config-public-wrappers.el index cec979e4a..d1141d33a 100644 --- a/tests/test-dirvish-config-public-wrappers.el +++ b/tests/test-dirvish-config-public-wrappers.el @@ -124,7 +124,7 @@ confused when several built-ins are overridden in the same test." ((symbol-function 'cj/get-project-root) (lambda () nil)) ((symbol-function 'kill-new) - (lambda (s) (setq killed s))) + (lambda (s &rest _) (setq killed s))) ((symbol-function 'message) #'ignore)) (cj/dired-copy-path-as-kill)) (should (stringp killed)) @@ -139,7 +139,7 @@ confused when several built-ins are overridden in the same test." (lambda (&rest _) "/tmp/foo.txt")) ((symbol-function 'cj/get-project-root) (lambda () nil)) ((symbol-function 'kill-new) - (lambda (s) (setq killed s))) + (lambda (s &rest _) (setq killed s))) ((symbol-function 'message) #'ignore)) (cj/dired-copy-path-as-kill t)) (should (string-prefix-p "[[file:" killed)) diff --git a/tests/test-dirvish-config-wrappers.el b/tests/test-dirvish-config-wrappers.el index bead45830..39f272474 100644 --- a/tests/test-dirvish-config-wrappers.el +++ b/tests/test-dirvish-config-wrappers.el @@ -40,7 +40,7 @@ puts the older one first)." ((symbol-function 'ediff-files) (lambda (a b) (setq ediff-args (list a b)))) ((symbol-function 'current-window-configuration) - (lambda () nil)) + (lambda (&rest _) nil)) ((symbol-function 'add-hook) #'ignore)) (cj/dired-ediff-files) ;; Pair returns (older . newer) so ediff-files sees (older newer). diff --git a/tests/test-elfeed-config-helpers.el b/tests/test-elfeed-config-helpers.el index 59a0ed331..16cbb7443 100644 --- a/tests/test-elfeed-config-helpers.el +++ b/tests/test-elfeed-config-helpers.el @@ -39,7 +39,7 @@ (ert-deftest test-elfeed-extract-stream-url-normal-returns-url () "Normal: a successful yt-dlp run returns the trimmed https stream URL." (cl-letf (((symbol-function 'executable-find) - (lambda (p) (and (equal p "yt-dlp") "/usr/bin/yt-dlp"))) + (lambda (p &rest _) (and (equal p "yt-dlp") "/usr/bin/yt-dlp"))) ((symbol-function 'cj/log-silently) #'ignore) ((symbol-function 'call-process) (lambda (_prog _infile _dest _disp &rest _args) @@ -49,7 +49,7 @@ (ert-deftest test-elfeed-extract-stream-url-boundary-non-url-output-is-nil () "Boundary: output that is not an http(s) URL yields nil, not the raw text." - (cl-letf (((symbol-function 'executable-find) (lambda (_) "/usr/bin/yt-dlp")) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/yt-dlp")) ((symbol-function 'cj/log-silently) #'ignore) ((symbol-function 'call-process) (lambda (_p _i _d _disp &rest _) (insert "ERROR: unavailable\n") 0))) @@ -57,7 +57,7 @@ (ert-deftest test-elfeed-extract-stream-url-boundary-nonzero-exit-is-nil () "Boundary: a nonzero yt-dlp exit code yields nil." - (cl-letf (((symbol-function 'executable-find) (lambda (_) "/usr/bin/yt-dlp")) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/yt-dlp")) ((symbol-function 'cj/log-silently) #'ignore) ((symbol-function 'call-process) (lambda (_p _i _d _disp &rest _) (insert "boom") 1))) @@ -65,7 +65,7 @@ (ert-deftest test-elfeed-extract-stream-url-error-without-yt-dlp () "Error: a missing yt-dlp signals before attempting the call." - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (should-error (cj/extract-stream-url "u" "best") :type 'error))) ;;; cj/elfeed-process-entries diff --git a/tests/test-flyspell-and-abbrev.el b/tests/test-flyspell-and-abbrev.el index 793fdc0f4..ef8cc6375 100644 --- a/tests/test-flyspell-and-abbrev.el +++ b/tests/test-flyspell-and-abbrev.el @@ -32,12 +32,12 @@ (ert-deftest test-flyspell-require-spell-checker-present () "Normal: a checker on PATH means no error." (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (equal cmd (car cj/--spell-checker-executables))))) + (lambda (cmd &rest _) (equal cmd (car cj/--spell-checker-executables))))) (should-not (cj/--require-spell-checker)))) (ert-deftest test-flyspell-require-spell-checker-missing () "Error: no checker on PATH signals user-error." - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (should-error (cj/--require-spell-checker) :type 'user-error))) ;; --------------------- cj/find-previous-flyspell-overlay --------------------- diff --git a/tests/test-gptel-tools-web-fetch.el b/tests/test-gptel-tools-web-fetch.el index b6dbefccb..10abe6eba 100644 --- a/tests/test-gptel-tools-web-fetch.el +++ b/tests/test-gptel-tools-web-fetch.el @@ -106,13 +106,13 @@ (ert-deftest test-gptel-tools-web-fetch-html-to-text-error-when-neither-on-path () "Error: when neither pandoc nor w3m is on PATH, signals user-error." - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (should-error (cj/gptel-web-fetch--html-to-text "<p>x</p>")))) (ert-deftest test-gptel-tools-web-fetch-html-to-text-error-on-tool-failure () "Error: a failing HTML stripping command is reported." (cl-letf (((symbol-function 'executable-find) - (lambda (program) (and (equal program "pandoc") "/bin/pandoc"))) + (lambda (program &rest _) (and (equal program "pandoc") "/bin/pandoc"))) ((symbol-function 'call-process-region) (lambda (&rest _args) 9))) (should-error (cj/gptel-web-fetch--html-to-text "<p>x</p>")))) @@ -121,7 +121,7 @@ "Boundary: w3m is used when pandoc is unavailable." (let (called-program) (cl-letf (((symbol-function 'executable-find) - (lambda (program) (and (equal program "w3m") "/bin/w3m"))) + (lambda (program &rest _) (and (equal program "w3m") "/bin/w3m"))) ((symbol-function 'call-process-region) (lambda (start end program delete output display &rest _args) (setq called-program program) diff --git a/tests/test-host-environment--detect-system-timezone.el b/tests/test-host-environment--detect-system-timezone.el index 1b5e61081..209283d1e 100644 --- a/tests/test-host-environment--detect-system-timezone.el +++ b/tests/test-host-environment--detect-system-timezone.el @@ -22,7 +22,7 @@ (cl-letf (((symbol-function 'cj/match-localtime-to-zoneinfo) (lambda () "America/Los_Angeles")) ((symbol-function 'getenv) - (lambda (_) (error "TZ should not have been consulted")))) + (lambda (_ &rest _) (error "TZ should not have been consulted")))) (should (equal (cj/detect-system-timezone) "America/Los_Angeles")))) (ert-deftest test-host-environment-detect-tz-env-var-wins-when-match-nil () @@ -30,7 +30,7 @@ (cl-letf (((symbol-function 'cj/match-localtime-to-zoneinfo) (lambda () nil)) ((symbol-function 'getenv) - (lambda (name) (when (string= name "TZ") "Europe/Berlin")))) + (lambda (name &rest _) (when (string= name "TZ") "Europe/Berlin")))) (should (equal (cj/detect-system-timezone) "Europe/Berlin")))) (ert-deftest test-host-environment-detect-tz-falls-through-to-etc-timezone () @@ -41,7 +41,7 @@ contents primitives." (cl-letf (((symbol-function 'cj/match-localtime-to-zoneinfo) (lambda () nil)) ((symbol-function 'getenv) - (lambda (_) nil)) + (lambda (_ &rest _) nil)) ((symbol-function 'file-exists-p) (lambda (path) (string= path "/etc/timezone"))) ((symbol-function 'insert-file-contents) @@ -55,7 +55,7 @@ contents primitives." (cl-letf (((symbol-function 'cj/match-localtime-to-zoneinfo) (lambda () nil)) ((symbol-function 'getenv) - (lambda (_) nil)) + (lambda (_ &rest _) nil)) ((symbol-function 'file-exists-p) (lambda (path) (string= path "/etc/timezone"))) ((symbol-function 'insert-file-contents) @@ -69,7 +69,7 @@ contents primitives." (cl-letf (((symbol-function 'cj/match-localtime-to-zoneinfo) (lambda () nil)) ((symbol-function 'getenv) - (lambda (_) nil)) + (lambda (_ &rest _) nil)) ((symbol-function 'file-exists-p) (lambda (_) nil)) ((symbol-function 'file-symlink-p) (lambda (_) nil))) (should-not (cj/detect-system-timezone)))) @@ -79,24 +79,24 @@ contents primitives." yields the zone after the /zoneinfo/ segment." (cl-letf (((symbol-function 'cj/match-localtime-to-zoneinfo) (lambda () nil)) - ((symbol-function 'getenv) (lambda (_) nil)) + ((symbol-function 'getenv) (lambda (_ &rest _) nil)) ((symbol-function 'file-exists-p) (lambda (_) nil)) ((symbol-function 'file-symlink-p) (lambda (path) (string= path "/etc/localtime"))) ((symbol-function 'file-truename) - (lambda (_) "/usr/share/zoneinfo/America/Denver"))) + (lambda (_ &rest _) "/usr/share/zoneinfo/America/Denver"))) (should (equal (cj/detect-system-timezone) "America/Denver")))) (ert-deftest test-host-environment-detect-tz-symlink-without-zoneinfo-is-nil () "Error: a symlink target with no /zoneinfo/ segment yields nil." (cl-letf (((symbol-function 'cj/match-localtime-to-zoneinfo) (lambda () nil)) - ((symbol-function 'getenv) (lambda (_) nil)) + ((symbol-function 'getenv) (lambda (_ &rest _) nil)) ((symbol-function 'file-exists-p) (lambda (_) nil)) ((symbol-function 'file-symlink-p) (lambda (path) (string= path "/etc/localtime"))) ((symbol-function 'file-truename) - (lambda (_) "/var/lib/elsewhere/localtime"))) + (lambda (_ &rest _) "/var/lib/elsewhere/localtime"))) (should-not (cj/detect-system-timezone)))) (provide 'test-host-environment--detect-system-timezone) diff --git a/tests/test-host-environment--display-predicates.el b/tests/test-host-environment--display-predicates.el index 15dff2ef8..5a87b5009 100644 --- a/tests/test-host-environment--display-predicates.el +++ b/tests/test-host-environment--display-predicates.el @@ -26,7 +26,7 @@ GRAPHIC-P becomes the return of `(display-graphic-p)'." `(cl-letf (((symbol-function 'window-system) (lambda (&optional _) ,window-system-value)) ((symbol-function 'getenv) - (lambda (name) + (lambda (name &rest _) (when (string= name "WAYLAND_DISPLAY") ,wayland-display))) ((symbol-function 'display-graphic-p) (lambda (&optional _) ,graphic-p))) diff --git a/tests/test-hugo-config-commands.el b/tests/test-hugo-config-commands.el index 01df5fc18..07bc27ca3 100644 --- a/tests/test-hugo-config-commands.el +++ b/tests/test-hugo-config-commands.el @@ -134,7 +134,7 @@ stubbed before the org-mode-derived guard runs." ((symbol-function 'completing-read) (lambda (&rest _) "Foo Post")) ((symbol-function 'find-file) - (lambda (f) (setq opened f)))) + (lambda (f &rest _) (setq opened f)))) (cj/hugo-open-draft)) (should (equal opened "/tmp/foo.org")))) @@ -196,7 +196,7 @@ stubbed before the org-mode-derived guard runs." (msg nil)) (cl-letf (((symbol-function 'process-live-p) (lambda (_) t)) ((symbol-function 'kill-process) - (lambda (p) (setq killed p))) + (lambda (p &rest _) (setq killed p))) ((symbol-function 'message) (lambda (fmt &rest args) (setq msg (apply #'format fmt args))))) @@ -210,7 +210,7 @@ stubbed before the org-mode-derived guard runs." (let ((cj/hugo--preview-process nil) (start-args nil)) (cl-letf (((symbol-function 'process-live-p) (lambda (_) nil)) - ((symbol-function 'executable-find) (lambda (_) "/usr/bin/hugo")) + ((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/hugo")) ((symbol-function 'start-process) (lambda (&rest args) (setq start-args args) @@ -226,7 +226,7 @@ stubbed before the org-mode-derived guard runs." "Error: a missing hugo binary signals user-error before start-process." (let ((cj/hugo--preview-process nil)) (cl-letf (((symbol-function 'process-live-p) (lambda (_) nil)) - ((symbol-function 'executable-find) (lambda (_) nil)) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil)) ((symbol-function 'start-process) (lambda (&rest _) (error "start-process should not run"))) ((symbol-function 'message) #'ignore)) diff --git a/tests/test-hugo-config-open-blog-dir-external.el b/tests/test-hugo-config-open-blog-dir-external.el index 0bf689826..05f116e6d 100644 --- a/tests/test-hugo-config-open-blog-dir-external.el +++ b/tests/test-hugo-config-open-blog-dir-external.el @@ -44,7 +44,7 @@ filesystem checks." (cl-letf (((symbol-function 'env-macos-p) (lambda () ,macos-p)) ((symbol-function 'env-windows-p) (lambda () ,windows-p)) ((symbol-function 'file-directory-p) (lambda (_d) t)) - ((symbol-function 'executable-find) (lambda (cmd) cmd)) + ((symbol-function 'executable-find) (lambda (cmd &rest _) cmd)) ((symbol-function 'start-process) (lambda (_name _buf cmd &rest _args) (setq test-hugo--captured-process-cmd cmd)))) @@ -86,7 +86,7 @@ filesystem checks." ((symbol-function 'file-directory-p) (lambda (_d) nil)) ((symbol-function 'make-directory) (lambda (_dir &rest _args) (setq mkdir-called t))) - ((symbol-function 'executable-find) (lambda (cmd) cmd)) + ((symbol-function 'executable-find) (lambda (cmd &rest _) cmd)) ((symbol-function 'start-process) #'ignore)) (cj/hugo-open-blog-dir-external) (should mkdir-called)))) @@ -99,7 +99,7 @@ filesystem checks." ((symbol-function 'file-directory-p) (lambda (_d) t)) ((symbol-function 'make-directory) (lambda (_dir &rest _args) (setq mkdir-called t))) - ((symbol-function 'executable-find) (lambda (cmd) cmd)) + ((symbol-function 'executable-find) (lambda (cmd &rest _) cmd)) ((symbol-function 'start-process) #'ignore)) (cj/hugo-open-blog-dir-external) (should-not mkdir-called)))) @@ -111,7 +111,7 @@ filesystem checks." (cl-letf (((symbol-function 'env-macos-p) (lambda () nil)) ((symbol-function 'env-windows-p) (lambda () nil)) ((symbol-function 'file-directory-p) (lambda (_d) t)) - ((symbol-function 'executable-find) (lambda (_) nil)) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil)) ((symbol-function 'start-process) (lambda (&rest _) (error "start-process should not run")))) (should-error (cj/hugo-open-blog-dir-external) :type 'user-error))) diff --git a/tests/test-init-defer-games.el b/tests/test-init-defer-games.el index 0b85a1ea7..f3ec94de8 100644 --- a/tests/test-init-defer-games.el +++ b/tests/test-init-defer-games.el @@ -1,10 +1,12 @@ ;;; test-init-defer-games.el --- games-config Phase 4 deferral -*- lexical-binding: t; -*- ;;; Commentary: -;; games-config is deferred (load-graph Phase 4): init.el autoloads `malyon' -;; and `2048-game' instead of requiring the module eagerly. These tests guard -;; that the game commands stay reachable with the module unloaded, and that -;; loading the module still applies the one setting it owns. +;; games-config is deferred (load-graph Phase 4): malyon and 2048-game autoload +;; their own commands via package.el, and init.el loads games-config (which only +;; supplies malyon's config) via `with-eval-after-load 'malyon'. These tests +;; guard the command availability and exercise the real autoload-invocation path +;; that M-x uses, which is where an earlier cut regressed ("Autoloading +;; games-config.el failed to define function malyon"). ;;; Code: @@ -13,25 +15,31 @@ (ert-deftest test-init-defer-games-commands-autoload-without-module () "Normal: the game commands resolve with games-config unloaded. -This is the safety net for the deferral -- dropping the eager require keeps -malyon and 2048-game reachable only because the packages autoload their own -commands, so assert that holds." +Dropping the eager require keeps malyon and 2048-game reachable only because the +packages autoload their own commands, so assert that holds." (package-initialize) (should-not (featurep 'games-config)) (should (commandp 'malyon)) (should (commandp '2048-game))) -(ert-deftest test-init-defer-games-config-applies-malyon-stories-dir () - "Normal: loading games-config still applies malyon's stories directory. -The module is the config owner; deferring it must not drop the one setting it -adds (`malyon-stories-directory'), which use-package applies when malyon loads." +(ert-deftest test-init-defer-games-malyon-loads-and-configures () + "Normal: resolving malyon's autoload yields a real command and applies config. +Reproduces the M-x malyon path via `autoload-do-load': malyon autoloads from its +own package, init.el's `with-eval-after-load 'malyon' loads games-config, and +games-config sets the stories directory. This is the regression guard for the +earlier cut that autoloaded malyon to games-config, where Emacs errored that the +load failed to define malyon." (package-initialize) (add-to-list 'load-path (expand-file-name "modules" default-directory)) (require 'user-constants) + (unless (and (fboundp 'malyon) (autoloadp (symbol-function 'malyon))) + (ert-skip "malyon package not available as an autoload")) (let ((org-dir "/tmp/games-defer-test/")) - (load "games-config" nil t) - (unless (require 'malyon nil t) - (ert-skip "malyon package not available")) + (with-eval-after-load 'malyon (require 'games-config)) ; the init.el wiring + (should-not (featurep 'games-config)) + (should (functionp (autoload-do-load (symbol-function 'malyon) 'malyon))) + (should (commandp 'malyon)) + (should (featurep 'games-config)) (should (equal malyon-stories-directory "/tmp/games-defer-test/text.games/")))) (provide 'test-init-defer-games) diff --git a/tests/test-keybindings--jump-open-var.el b/tests/test-keybindings--jump-open-var.el index bd04f4cf1..041f4a7d3 100644 --- a/tests/test-keybindings--jump-open-var.el +++ b/tests/test-keybindings--jump-open-var.el @@ -25,7 +25,7 @@ CAPTURE-VAR is set to the path passed to `find-file', or stays nil if the mock is never called." (declare (indent 1) (debug t)) `(cl-letf (((symbol-function 'find-file) - (lambda (path) (setq ,capture-var path)))) + (lambda (path &rest _) (setq ,capture-var path)))) ,@body)) (defmacro test-keybindings--with-fixture (value &rest body) diff --git a/tests/test-mail-config-transport.el b/tests/test-mail-config-transport.el index 2244b6dd2..0240102a2 100644 --- a/tests/test-mail-config-transport.el +++ b/tests/test-mail-config-transport.el @@ -18,7 +18,7 @@ EXECUTABLES is an alist of program name strings to executable paths." (declare (indent 1)) `(let (test-mail-config--warnings) (cl-letf (((symbol-function 'executable-find) - (lambda (program) + (lambda (program &rest _) (cdr (assoc program ,executables)))) ((symbol-function 'display-warning) (lambda (type message &rest _args) diff --git a/tests/test-media-utils.el b/tests/test-media-utils.el index 9384d568f..841b6faf9 100644 --- a/tests/test-media-utils.el +++ b/tests/test-media-utils.el @@ -24,7 +24,7 @@ (ert-deftest test-media-get-available-players-filters-by-executable () "Normal: only players whose :command is on PATH are reported." (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (and (member cmd '("mpv" "vlc")) cmd)))) + (lambda (cmd &rest _) (and (member cmd '("mpv" "vlc")) cmd)))) (let ((result (cj/get-available-media-players))) (should (memq 'mpv result)) (should (memq 'vlc result)) @@ -32,7 +32,7 @@ (ert-deftest test-media-get-available-players-none-installed () "Boundary: with nothing on PATH, the list is empty." - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (should-not (cj/get-available-media-players)))) ;; ----------------------------- cj/media-play-it ------------------------------ @@ -41,7 +41,7 @@ "Normal: a player that needs no stream URL gets a plain command, no yt-dlp." (let (captured cj/default-media-player) (setq cj/default-media-player 'mpv) - (cl-letf (((symbol-function 'executable-find) (lambda (_) "/usr/bin/mpv")) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/mpv")) ((symbol-function 'start-process-shell-command) (lambda (_n _b cmd) (setq captured cmd) 'proc)) ((symbol-function 'set-process-sentinel) #'ignore) @@ -56,7 +56,7 @@ "Normal: a player needing a stream URL wraps the URL in a yt-dlp -g call." (let (captured cj/default-media-player) (setq cj/default-media-player 'vlc) - (cl-letf (((symbol-function 'executable-find) (lambda (_) "/usr/bin/vlc")) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/vlc")) ((symbol-function 'start-process-shell-command) (lambda (_n _b cmd) (setq captured cmd) 'proc)) ((symbol-function 'set-process-sentinel) #'ignore) @@ -71,7 +71,7 @@ "Error: an unavailable player command signals an error before launching." (let (cj/default-media-player) (setq cj/default-media-player 'mpv) - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (should-error (cj/media-play-it "https://example.com/v"))))) ;; ------------------------------- cj/yt-dl-it --------------------------------- @@ -79,19 +79,19 @@ (ert-deftest test-media-yt-dl-it-errors-without-yt-dlp () "Error: a missing yt-dlp aborts the download." (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (unless (equal cmd "yt-dlp") "/usr/bin/x")))) + (lambda (cmd &rest _) (unless (equal cmd "yt-dlp") "/usr/bin/x")))) (should-error (cj/yt-dl-it "https://example.com/v")))) (ert-deftest test-media-yt-dl-it-errors-without-tsp () "Error: yt-dlp present but tsp missing aborts the download." (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) (unless (equal cmd "tsp") "/usr/bin/x")))) + (lambda (cmd &rest _) (unless (equal cmd "tsp") "/usr/bin/x")))) (should-error (cj/yt-dl-it "https://example.com/v")))) (ert-deftest test-media-yt-dl-it-builds-tsp-yt-dlp-process () "Normal: with both tools present, the URL is queued via tsp + yt-dlp." (let (captured (videos-dir "/tmp/videos")) - (cl-letf (((symbol-function 'executable-find) (lambda (_) "/usr/bin/x")) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/x")) ((symbol-function 'start-process) (lambda (&rest args) (setq captured args) 'proc)) ((symbol-function 'set-process-sentinel) #'ignore) diff --git a/tests/test-meta-subr-mock-arity.el b/tests/test-meta-subr-mock-arity.el new file mode 100644 index 000000000..8ee2cb5e0 --- /dev/null +++ b/tests/test-meta-subr-mock-arity.el @@ -0,0 +1,113 @@ +;;; test-meta-subr-mock-arity.el --- Guard against arity-narrow subr mocks -*- lexical-binding: t; -*- + +;;; Commentary: +;; A meta-test: it tests the other tests. Native compilation routes a +;; redefined C primitive (subr) through a trampoline that calls the +;; replacement with the primitive's FULL arity, filling optionals with nil. +;; So a fixed-arity mock that is narrower than the primitive throws +;; `wrong-number-of-arguments' the moment native-comp has compiled that +;; trampoline -- a failure that appears intermittently as the eln-cache fills. +;; +;; The rule this enforces is NOT "never mock a subr" (the suite mocks subrs +;; like `message' and `completing-read' hundreds of times, all fine). It is: +;; a mock of a C primitive must be able to accept the primitive's maximum +;; arity -- in practice, use (lambda (&rest _) ...). This test scans every +;; file under tests/ for `cl-letf' / `setf' / `fset' redefinitions of a +;; `symbol-function', and fails listing any whose replacement is too narrow. +;; +;; It is deterministic: a pure static read of the test sources plus +;; `func-arity', with no dependence on whether native-comp happens to have +;; built the trampoline yet. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'seq) + +(defconst test-meta-subr--test-dir + (expand-file-name "tests" (or (getenv "EMACS_CONFIG_ROOT") default-directory)) + "Directory whose .el files are scanned for subr mocks.") + +(defun test-meta-subr--replacement-arglist (repl) + "Return the formal arglist of REPL, or the symbol `unknown'. +Handles (lambda ARGS ...) and (function (lambda ARGS ...)); returns `variadic' +for forms known to accept any arity (`ignore', `always'), and `unknown' for +anything whose arity can't be read statically (a bare variable, a call)." + (pcase repl + (`(lambda ,args . ,_) args) + (`(function (lambda ,args . ,_)) args) + (`(quote ,(or 'ignore 'always)) 'variadic) + (`(function ,(or 'ignore 'always)) 'variadic) + (_ 'unknown))) + +(defun test-meta-subr--accepts-p (arglist subr-max) + "Non-nil if a lambda with ARGLIST can be called with SUBR-MAX positional args. +ARGLIST may also be `variadic' or `unknown' (both treated as acceptable)." + (cond + ((memq arglist '(variadic unknown)) t) + ((memq '&rest arglist) t) + ((eq subr-max 'many) nil) ; only &rest accepts unbounded arity + ((integerp subr-max) + (>= (length (seq-remove (lambda (s) (memq s '(&optional &rest &key))) + arglist)) + subr-max)) + (t t))) + +(defun test-meta-subr--quoted-symbol (form) + "If FORM is 'SYM or #'SYM, return SYM, else nil." + (pcase form + (`(quote ,(and s (guard (symbolp s)))) s) + (`(function ,(and s (guard (symbolp s)))) s))) + +(defun test-meta-subr--collect (form acc) + "Walk FORM, pushing (SYM . REPLACEMENT) for each symbol-function redefinition. +Covers `cl-letf'/`setf' binding shape ((symbol-function 'SYM) REPL) and +\(fset 'SYM REPL)." + (when (consp form) + ;; (fset 'SYM REPL) + (when (eq (car-safe form) 'fset) + (let ((s (test-meta-subr--quoted-symbol (nth 1 form)))) + (when s (push (cons s (nth 2 form)) acc)))) + ;; binding element ((symbol-function 'SYM) REPL) -- cl-letf, cl-letf*, setf + (when (and (consp (car-safe form)) + (eq (car-safe (car form)) 'symbol-function)) + (let ((s (test-meta-subr--quoted-symbol (nth 1 (car form))))) + (when s (push (cons s (nth 1 form)) acc)))) + (dolist (sub form) (setq acc (test-meta-subr--collect sub acc)))) + acc) + +(defun test-meta-subr--violations () + "Return a list of human-readable violation strings across the test files." + (let ((violations '())) + (dolist (file (directory-files-recursively test-meta-subr--test-dir "\\.el\\'")) + ;; Don't scan this meta-test itself (its examples would self-trip). + (unless (string-suffix-p "test-meta-subr-mock-arity.el" file) + (let ((mocks '())) + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (condition-case nil + (while t (setq mocks (test-meta-subr--collect (read (current-buffer)) mocks))) + (error nil))) + (pcase-dolist (`(,sym . ,repl) (nreverse mocks)) + (when (and (fboundp sym) + (condition-case nil (subrp (symbol-function sym)) (error nil))) + (let ((subr-max (cdr (func-arity sym))) + (arglist (test-meta-subr--replacement-arglist repl))) + (unless (test-meta-subr--accepts-p arglist subr-max) + (push (format "%s: mock of subr `%s' (arity max %s) takes %S -- use (&rest _)" + (file-name-nondirectory file) sym subr-max arglist) + violations)))))))) + (nreverse violations))) + +(ert-deftest test-meta-no-arity-narrow-subr-mocks () + "No test mocks a C primitive with a lambda too narrow for its arity. +Such a mock breaks under native-comp's subr trampoline (it calls the mock with +the primitive's full arity). Fix by making the mock variadic: (lambda (&rest _) +...). See this file's commentary." + (let ((violations (test-meta-subr--violations))) + (should (null violations)))) + +(provide 'test-meta-subr-mock-arity) +;;; test-meta-subr-mock-arity.el ends here diff --git a/tests/test-music-config-commands.el b/tests/test-music-config-commands.el index d57e339c4..3c585d0b7 100644 --- a/tests/test-music-config-commands.el +++ b/tests/test-music-config-commands.el @@ -176,9 +176,9 @@ last-played track and starts it." (added-hooks nil) (removed-hooks nil)) (cl-letf (((symbol-function 'add-hook) - (lambda (hook _fn) (push hook added-hooks))) + (lambda (hook _fn &rest _) (push hook added-hooks))) ((symbol-function 'remove-hook) - (lambda (hook _fn) (push hook removed-hooks))) + (lambda (hook _fn &rest _) (push hook removed-hooks))) ((symbol-function 'message) #'ignore)) (cj/music-toggle-consume) (should cj/music-consume-mode) diff --git a/tests/test-music-config-helpers-untested.el b/tests/test-music-config-helpers-untested.el index 4ba0940a5..bfdb2634d 100644 --- a/tests/test-music-config-helpers-untested.el +++ b/tests/test-music-config-helpers-untested.el @@ -113,7 +113,7 @@ test prelude inserts filler with `inhibit-read-only' bound." "Normal: when emms is already a feature, setup does not re-require." (let ((called nil)) (cl-letf (((symbol-function 'featurep) - (lambda (sym) (eq sym 'emms))) + (lambda (sym &rest _) (eq sym 'emms))) ((symbol-function 'require) (lambda (&rest _) (setq called t) t))) (cj/emms--setup)) @@ -123,7 +123,7 @@ test prelude inserts filler with `inhibit-read-only' bound." "Boundary: when emms isn't yet loaded, setup requires it." (let ((required nil)) (cl-letf (((symbol-function 'featurep) - (lambda (sym) (not (eq sym 'emms)))) + (lambda (sym &rest _) (not (eq sym 'emms)))) ((symbol-function 'require) (lambda (feat &rest _) (setq required feat) t))) (cj/emms--setup)) diff --git a/tests/test-music-config-more-commands.el b/tests/test-music-config-more-commands.el index a029a5a33..c351c1f15 100644 --- a/tests/test-music-config-more-commands.el +++ b/tests/test-music-config-more-commands.el @@ -94,7 +94,7 @@ ((symbol-function 'cj/music--playlist-modified-p) (lambda () nil)) ((symbol-function 'find-file-other-window) - (lambda (f) (setq opened f)))) + (lambda (f &rest _) (setq opened f)))) (cj/music-playlist-edit)) (delete-file tmp)) (should (equal opened tmp)))) @@ -130,7 +130,7 @@ ((symbol-function 'cj/music--ensure-playlist-buffer) (lambda () buf)) ((symbol-function 'switch-to-buffer) - (lambda (b) (setq switched b))) + (lambda (b &rest _) (setq switched b))) ((symbol-function 'message) (lambda (fmt &rest args) (setq msg (apply #'format fmt args))))) (cj/music-playlist-show)) diff --git a/tests/test-music-config-playlist-commands.el b/tests/test-music-config-playlist-commands.el index 3d6dfd8b9..891bc700c 100644 --- a/tests/test-music-config-playlist-commands.el +++ b/tests/test-music-config-playlist-commands.el @@ -132,7 +132,7 @@ (cl-letf (((symbol-function 'cj/music--playlist-modified-p) (lambda () nil)) ((symbol-function 'find-file-other-window) - (lambda (p) (setq opened p)))) + (lambda (p &rest _) (setq opened p)))) (cj/music-playlist-edit)) (should (equal opened tmp)) (delete-file tmp)) diff --git a/tests/test-org-capture-config-popup-window.el b/tests/test-org-capture-config-popup-window.el index d308fc2b7..671d55ab9 100644 --- a/tests/test-org-capture-config-popup-window.el +++ b/tests/test-org-capture-config-popup-window.el @@ -173,7 +173,7 @@ not whatever frame happens to be selected (the emacsclient -c focus race)." (let ((focused nil)) (cl-letf (((symbol-function 'cj/org-capture--popup-frame) (lambda () 'popup-frame)) ((symbol-function 'select-frame-set-input-focus) - (lambda (f) (setq focused f))) + (lambda (f &rest _) (setq focused f))) ((symbol-function 'org-capture) (lambda (&rest _) nil))) (cj/quick-capture)) (should (eq focused 'popup-frame)))) @@ -185,7 +185,7 @@ call and still runs the capture (no error)." (captured nil)) (cl-letf (((symbol-function 'cj/org-capture--popup-frame) (lambda () nil)) ((symbol-function 'select-frame-set-input-focus) - (lambda (f) (setq focused f))) + (lambda (f &rest _) (setq focused f))) ((symbol-function 'org-capture) (lambda (&rest _) (setq captured t)))) (cj/quick-capture)) (should (eq focused 'unset)) diff --git a/tests/test-org-drill-config-commands.el b/tests/test-org-drill-config-commands.el index c35bd6cd4..38f6b66e3 100644 --- a/tests/test-org-drill-config-commands.el +++ b/tests/test-org-drill-config-commands.el @@ -38,7 +38,7 @@ (let (opened (drilled 0)) (cl-letf (((symbol-function 'cj/--drill-pick-file) (lambda (_dir) "/decks/german.org")) - ((symbol-function 'find-file) (lambda (f) (setq opened f))) + ((symbol-function 'find-file) (lambda (f &rest _) (setq opened f))) ((symbol-function 'org-drill) (lambda (&rest _) (cl-incf drilled)))) (cj/drill-edit)) @@ -54,7 +54,7 @@ (with-temp-file (expand-file-name "latin.org" tmp)) (cl-letf (((symbol-function 'read-directory-name) (lambda (&rest _) tmp)) ((symbol-function 'completing-read) (lambda (&rest _) "latin.org")) - ((symbol-function 'find-file) (lambda (f) (setq opened f)))) + ((symbol-function 'find-file) (lambda (f &rest _) (setq opened f)))) (cj/drill-edit t)) (should (equal (expand-file-name "latin.org" tmp) opened))) (delete-directory tmp t)))) @@ -85,7 +85,7 @@ and validation)." ((symbol-function 'directory-files) (lambda (&rest _) '("/WRONG/raw.org"))) ((symbol-function 'call-interactively) - (lambda (fn) + (lambda (fn &rest _) (setq called-fn fn seen-targets org-refile-targets)))) (cj/drill-refile)) @@ -101,7 +101,7 @@ survives the call instead of being permanently replaced." (let ((drill-dir "/tmp/cj-drill/") (org-refile-targets '((sentinel :maxlevel . 9)))) (cl-letf (((symbol-function 'cj/--drill-files-or-error) (lambda (_dir) '("a.org"))) - ((symbol-function 'call-interactively) (lambda (_fn) nil))) + ((symbol-function 'call-interactively) (lambda (_fn &rest _) nil))) (cj/drill-refile)) (should (equal org-refile-targets '((sentinel :maxlevel . 9)))))) @@ -112,7 +112,7 @@ the shared validated helper, instead of a low-level error, and never reaches (let ((drill-dir (expand-file-name "cj-drill-nonexistent-XYZ/" temporary-file-directory)) (called nil)) - (cl-letf (((symbol-function 'call-interactively) (lambda (_fn) (setq called t)))) + (cl-letf (((symbol-function 'call-interactively) (lambda (_fn &rest _) (setq called t)))) (should-error (cj/drill-refile) :type 'user-error)) (should-not called))) diff --git a/tests/test-org-drill-config.el b/tests/test-org-drill-config.el index d3057de2a..9dffa0bca 100644 --- a/tests/test-org-drill-config.el +++ b/tests/test-org-drill-config.el @@ -118,7 +118,7 @@ (let (opened (drilled 0)) (cl-letf (((symbol-function 'cj/--drill-pick-file) (lambda (_dir) "/decks/french.org")) - ((symbol-function 'find-file) (lambda (f) (setq opened f))) + ((symbol-function 'find-file) (lambda (f &rest _) (setq opened f))) ((symbol-function 'org-drill) (lambda (&rest _) (cl-incf drilled)))) (cj/drill-start)) (should (equal "/decks/french.org" opened)) @@ -131,7 +131,7 @@ (let (opened) (cl-letf (((symbol-function 'read-directory-name) (lambda (&rest _) dir)) ((symbol-function 'completing-read) (lambda (&rest _) "latin.org")) - ((symbol-function 'find-file) (lambda (f) (setq opened f))) + ((symbol-function 'find-file) (lambda (f &rest _) (setq opened f))) ((symbol-function 'org-drill) #'ignore)) (cj/drill-start t)) (should (equal (expand-file-name "latin.org" dir) opened))))) diff --git a/tests/test-org-noter-config-commands.el b/tests/test-org-noter-config-commands.el index 8860af06e..70c78645c 100644 --- a/tests/test-org-noter-config-commands.el +++ b/tests/test-org-noter-config-commands.el @@ -115,7 +115,7 @@ ((symbol-function 'org-id-uuid) (lambda () "00000000-0000-0000-0000-000000000000")) ((symbol-function 'find-file-noselect) - (lambda (f) (get-buffer-create (concat "*test-" f "*"))))) + (lambda (f &rest _) (get-buffer-create (concat "*test-" f "*"))))) (let ((path (cj/org-noter--create-notes-file))) (should (file-exists-p path)) (with-temp-buffer @@ -186,7 +186,7 @@ ((symbol-function 'org-noter--get-doc-window) (lambda () 'doc-win)) ((symbol-function 'select-window) - (lambda (w) (setq selected w)))) + (lambda (w &rest _) (setq selected w)))) (cj/org-noter-start)) (should (eq selected 'doc-win)))) @@ -232,7 +232,7 @@ ((symbol-function 'org-noter--get-doc-window) (lambda () 'doc-win)) ((symbol-function 'select-window) - (lambda (w) (setq selected w))) + (lambda (w &rest _) (setq selected w))) ((symbol-function 'org-noter-insert-note) (lambda () (setq inserted t)))) (cj/org-noter-insert-note-dwim)) diff --git a/tests/test-org-refile-config-commands.el b/tests/test-org-refile-config-commands.el index 9bdd33647..2e99e9152 100644 --- a/tests/test-org-refile-config-commands.el +++ b/tests/test-org-refile-config-commands.el @@ -54,7 +54,7 @@ (with-temp-buffer (setq buffer-file-name "/tmp/notes.org") (cl-letf (((symbol-function 'call-interactively) - (lambda (_fn) + (lambda (_fn &rest _) (setq seen-targets org-refile-targets))) ((symbol-function 'save-buffer) #'ignore)) (cj/org-refile-in-file)) @@ -73,7 +73,7 @@ (setq buffer-file-name "/tmp/notes.org") (cl-letf (((symbol-function 'call-interactively) #'ignore) ((symbol-function 'save-buffer) - (lambda () (setq saved t)))) + (lambda (&rest _) (setq saved t)))) (cj/org-refile-in-file)) (setq buffer-file-name nil)) (should saved))) diff --git a/tests/test-org-reveal-config-header-template.el b/tests/test-org-reveal-config-header-template.el index df1db9e77..9bda10db7 100644 --- a/tests/test-org-reveal-config-header-template.el +++ b/tests/test-org-reveal-config-header-template.el @@ -24,9 +24,9 @@ ;; Helper to call template with deterministic date and author (defun test-reveal--header (title) "Call cj/--reveal-header-template with TITLE, mocking time and user." - (cl-letf (((symbol-function 'user-full-name) (lambda () "Test Author")) + (cl-letf (((symbol-function 'user-full-name) (lambda (&rest _) "Test Author")) ((symbol-function 'format-time-string) - (lambda (_fmt) "2026-02-14"))) + (lambda (_fmt &rest _) "2026-02-14"))) (cj/--reveal-header-template title))) ;;; Normal Cases diff --git a/tests/test-org-webclipper-commands.el b/tests/test-org-webclipper-commands.el index be7fc38cf..fb693192f 100644 --- a/tests/test-org-webclipper-commands.el +++ b/tests/test-org-webclipper-commands.el @@ -120,7 +120,7 @@ that registers the webclip entry. Providing `'org-protocol' fires the block." (let ((cj/--webclip-url "https://example.com") (cj/--webclip-title "Title")) (cl-letf (((symbol-function 'require) (lambda (&rest _) t)) - ((symbol-function 'executable-find) (lambda (_) nil))) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (let ((err (should-error (cj/org-protocol-webclip-handler) :type 'user-error))) (should (string-match-p "pandoc" (cadr err))))))) @@ -130,7 +130,7 @@ that registers the webclip entry. Providing `'org-protocol' fires the block." (let ((cj/--webclip-url "https://example.com") (cj/--webclip-title "Title")) (cl-letf (((symbol-function 'require) (lambda (&rest _) t)) - ((symbol-function 'executable-find) (lambda (_) "/usr/bin/pandoc")) + ((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/pandoc")) ((symbol-function 'org-web-tools--url-as-readable-org) (lambda (_) "* Page Title\n** Sub heading\nBody.\n")) ((symbol-function 'message) #'ignore)) @@ -142,7 +142,7 @@ that registers the webclip entry. Providing `'org-protocol' fires the block." (let ((cj/--webclip-url "https://example.com") (cj/--webclip-title "Title")) (cl-letf (((symbol-function 'require) (lambda (&rest _) t)) - ((symbol-function 'executable-find) (lambda (_) "/usr/bin/pandoc")) + ((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/pandoc")) ((symbol-function 'org-web-tools--url-as-readable-org) (lambda (_) "* Page Title\n** Sub heading\nBody.\n")) ((symbol-function 'message) #'ignore)) diff --git a/tests/test-prog-c-mode-settings.el b/tests/test-prog-c-mode-settings.el index 37a77a213..33c503377 100644 --- a/tests/test-prog-c-mode-settings.el +++ b/tests/test-prog-c-mode-settings.el @@ -18,7 +18,7 @@ (cl-letf (((symbol-function 'auto-fill-mode) (lambda (&rest _) nil)) ((symbol-function 'electric-pair-local-mode) (lambda (&rest _) nil)) ((symbol-function 'lsp-deferred) (lambda (&rest _) nil)) - ((symbol-function 'executable-find) (lambda (_) nil))) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (cj/c-mode-settings)) (should (eq indent-tabs-mode nil)) (should (= c-basic-offset 4)) @@ -33,7 +33,7 @@ (cl-letf (((symbol-function 'auto-fill-mode) (lambda (&rest _) nil)) ((symbol-function 'electric-pair-local-mode) (lambda (&rest _) nil)) ((symbol-function 'lsp-deferred) (lambda () (cl-incf lsp-calls))) - ((symbol-function 'executable-find) (lambda (_) "/usr/bin/clangd"))) + ((symbol-function 'executable-find) (lambda (_ &rest _) "/usr/bin/clangd"))) (cj/c-mode-settings))) (should (= lsp-calls 1)))) @@ -44,7 +44,7 @@ (cl-letf (((symbol-function 'auto-fill-mode) (lambda (&rest _) nil)) ((symbol-function 'electric-pair-local-mode) (lambda (&rest _) nil)) ((symbol-function 'lsp-deferred) (lambda () (cl-incf lsp-calls))) - ((symbol-function 'executable-find) (lambda (_) nil))) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (cj/c-mode-settings))) (should (zerop lsp-calls)))) diff --git a/tests/test-prog-general--find-file-respecting-split.el b/tests/test-prog-general--find-file-respecting-split.el index 6d45c51c0..821cc79d6 100644 --- a/tests/test-prog-general--find-file-respecting-split.el +++ b/tests/test-prog-general--find-file-respecting-split.el @@ -23,9 +23,9 @@ (delete-other-windows) (let (current-arg other-called) (cl-letf (((symbol-function 'find-file) - (lambda (f) (setq current-arg f))) + (lambda (f &rest _) (setq current-arg f))) ((symbol-function 'find-file-other-window) - (lambda (_f) (setq other-called t)))) + (lambda (_f &rest _) (setq other-called t)))) (cj/--find-file-respecting-split "/tmp/proj/todo.org")) (should (equal current-arg "/tmp/proj/todo.org")) (should-not other-called)))) @@ -37,9 +37,9 @@ (split-window-right) (let (other-arg current-called) (cl-letf (((symbol-function 'find-file-other-window) - (lambda (f) (setq other-arg f))) + (lambda (f &rest _) (setq other-arg f))) ((symbol-function 'find-file) - (lambda (_f) (setq current-called t)))) + (lambda (_f &rest _) (setq current-called t)))) (cj/--find-file-respecting-split "/tmp/proj/todo.org")) (should (equal other-arg "/tmp/proj/todo.org")) (should-not current-called)))) @@ -52,9 +52,9 @@ (split-window-below) (let (other-called current-called) (cl-letf (((symbol-function 'find-file-other-window) - (lambda (_f) (setq other-called t))) + (lambda (_f &rest _) (setq other-called t))) ((symbol-function 'find-file) - (lambda (_f) (setq current-called t)))) + (lambda (_f &rest _) (setq current-called t)))) (cj/--find-file-respecting-split "/tmp/proj/todo.org")) (should other-called) (should-not current-called)))) diff --git a/tests/test-prog-general-open-project-daily-prep.el b/tests/test-prog-general-open-project-daily-prep.el index d9c78ff0e..5bc4d7d27 100644 --- a/tests/test-prog-general-open-project-daily-prep.el +++ b/tests/test-prog-general-open-project-daily-prep.el @@ -40,7 +40,7 @@ (unwind-protect (progn (cl-letf (((symbol-function 'projectile-project-root) (lambda () root)) - ((symbol-function 'find-file-other-window) (lambda (f) (setq opened f)))) + ((symbol-function 'find-file-other-window) (lambda (f &rest _) (setq opened f)))) (setq result (cj/open-project-daily-prep))) (should-not opened) (should (string-match-p "No daily-prep.org" result))) @@ -50,7 +50,7 @@ "Error: outside a Projectile project, do not open; report it." (let (opened result) (cl-letf (((symbol-function 'projectile-project-root) (lambda () nil)) - ((symbol-function 'find-file-other-window) (lambda (f) (setq opened f)))) + ((symbol-function 'find-file-other-window) (lambda (f &rest _) (setq opened f)))) (setq result (cj/open-project-daily-prep))) (should-not opened) (should (string-match-p "Not in a Projectile project" result)))) diff --git a/tests/test-prog-go-commands.el b/tests/test-prog-go-commands.el index a2fc0625f..6e6998348 100644 --- a/tests/test-prog-go-commands.el +++ b/tests/test-prog-go-commands.el @@ -54,7 +54,7 @@ ((symbol-function 'lsp-deferred) (lambda (&rest _) (setq started t))) ((symbol-function 'executable-find) - (lambda (path) (when (equal path gopls-path) "/usr/bin/gopls")))) + (lambda (path &rest _) (when (equal path gopls-path) "/usr/bin/gopls")))) (cj/go-setup)) (should started)))) @@ -66,7 +66,7 @@ ((symbol-function 'electric-pair-local-mode) #'ignore) ((symbol-function 'lsp-deferred) (lambda (&rest _) (setq started t))) - ((symbol-function 'executable-find) (lambda (_) nil))) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (cj/go-setup)) (should-not started)))) @@ -104,7 +104,7 @@ "Normal: with delve on PATH, `gud-gdb' is called with `dlv debug'." (let (started) (cl-letf (((symbol-function 'executable-find) - (lambda (path) (when (equal path dlv-path) "/usr/bin/dlv"))) + (lambda (path &rest _) (when (equal path dlv-path) "/usr/bin/dlv"))) ((symbol-function 'file-executable-p) (lambda (_) nil)) ((symbol-function 'gud-gdb) (lambda (cmd &rest _) (setq started cmd)))) @@ -117,7 +117,7 @@ "Error: delve missing -> message + no gud-gdb call." (let ((started nil) (msg nil)) - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil)) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil)) ((symbol-function 'file-executable-p) (lambda (_) nil)) ((symbol-function 'gud-gdb) (lambda (&rest _) (setq started t))) diff --git a/tests/test-prog-json--json-format-buffer.el b/tests/test-prog-json--json-format-buffer.el index 70d7e98bb..c6297a404 100644 --- a/tests/test-prog-json--json-format-buffer.el +++ b/tests/test-prog-json--json-format-buffer.el @@ -16,7 +16,7 @@ (ert-deftest test-prog-json--json-format-buffer-invokes-jq-argv () "Normal: with jq present, the formatter calls jq via argv, no shell." (let (program args) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/jq")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/jq")) ((symbol-function 'call-process-region) (lambda (_start _end prog &rest rest) (setq program prog @@ -31,7 +31,7 @@ (ert-deftest test-prog-json--json-format-buffer-no-clobber-on-failure () "Error: a non-zero jq exit leaves the buffer untouched and signals an error." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/jq")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/jq")) ((symbol-function 'call-process-region) (lambda (_start _end _prog _delete buffer &rest _) (with-current-buffer buffer (insert "jq: parse error")) @@ -112,7 +112,7 @@ (ert-deftest test-prog-json--json-format-buffer-fallback-formats-without-jq () "Falls back to built-in formatter when jq is not found." - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (with-temp-buffer (insert "{\"b\":1,\"a\":2}") (cj/json-format-buffer) diff --git a/tests/test-prog-python-commands.el b/tests/test-prog-python-commands.el index 443e7d175..55aa502f7 100644 --- a/tests/test-prog-python-commands.el +++ b/tests/test-prog-python-commands.el @@ -64,7 +64,7 @@ "Normal: with mypy on PATH, `compile' gets the builder's command." (let ((mypy-path "mypy") compiled) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/mypy")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/mypy")) ((symbol-function 'compile) (lambda (cmd &rest _) (setq compiled cmd)))) (with-temp-buffer (setq buffer-file-name "/home/me/foo.py") @@ -76,7 +76,7 @@ "Boundary: no file -> the command targets `default-directory'." (let ((mypy-path "mypy") compiled) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/mypy")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/mypy")) ((symbol-function 'compile) (lambda (cmd &rest _) (setq compiled cmd)))) (with-temp-buffer (setq-local default-directory "/home/me/proj/") @@ -88,7 +88,7 @@ (let ((mypy-path "mypy") (compiled nil) (messaged nil)) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) nil)) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) nil)) ((symbol-function 'compile) (lambda (&rest _) (setq compiled t))) ((symbol-function 'message) (lambda (fmt &rest args) (setq messaged (apply #'format fmt args))))) diff --git a/tests/test-prog-python-setup.el b/tests/test-prog-python-setup.el index 0b56f8cc9..368097c9e 100644 --- a/tests/test-prog-python-setup.el +++ b/tests/test-prog-python-setup.el @@ -71,7 +71,7 @@ electric-pair-local-mode all get called once." ((symbol-function 'lsp-deferred) (lambda (&rest _) (setq started t))) ((symbol-function 'executable-find) - (lambda (path) (when (equal path pyright-path) + (lambda (path &rest _) (when (equal path pyright-path) "/usr/bin/pyright")))) (cj/python-setup)) (should started)))) @@ -86,7 +86,7 @@ electric-pair-local-mode all get called once." ((symbol-function 'electric-pair-local-mode) #'ignore) ((symbol-function 'lsp-deferred) (lambda (&rest _) (setq started t))) - ((symbol-function 'executable-find) (lambda (_) nil))) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (cj/python-setup)) (should-not started)))) diff --git a/tests/test-prog-webdev-format.el b/tests/test-prog-webdev-format.el index 694f9e968..cb5da406c 100644 --- a/tests/test-prog-webdev-format.el +++ b/tests/test-prog-webdev-format.el @@ -46,7 +46,7 @@ (ert-deftest test-prog-webdev-format-buffer-runs-prettier-on-the-file () "Normal: with prettier on PATH, the argv targets `buffer-file-name'." (let (program args) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/prettier")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/prettier")) ((symbol-function 'call-process-region) (lambda (_start _end prog &rest rest) ;; rest = (DELETE BUFFER DISPLAY &rest ARGS) @@ -64,7 +64,7 @@ (ert-deftest test-prog-webdev-format-buffer-falls-back-to-file-ts () "Boundary: a buffer with no file uses the \"file.ts\" filename hint." (let (args) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/prettier")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/prettier")) ((symbol-function 'call-process-region) (lambda (_start _end _prog &rest rest) (setq args (nthcdr 3 rest)) @@ -77,7 +77,7 @@ (ert-deftest test-prog-webdev-format-buffer-clamps-point-to-point-max () "Boundary: after a format that shrinks the buffer, point clamps to point-max." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/prettier")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/prettier")) ((symbol-function 'call-process-region) (lambda (_start _end _prog _delete buffer &rest _) ;; Simulate prettier writing a shorter result to the output buffer. @@ -91,7 +91,7 @@ (ert-deftest test-prog-webdev-format-buffer-replaces-on-success () "Normal: a zero exit replaces the buffer with the formatter's output." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/prettier")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/prettier")) ((symbol-function 'call-process-region) (lambda (_start _end _prog _delete buffer &rest _) (with-current-buffer buffer (insert "const x = 1;\n")) @@ -103,7 +103,7 @@ (ert-deftest test-prog-webdev-format-buffer-no-clobber-on-failure () "Error: a non-zero exit leaves the buffer untouched and signals an error." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/prettier")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/prettier")) ((symbol-function 'call-process-region) (lambda (_start _end _prog _delete buffer &rest _) (with-current-buffer buffer (insert "[error] syntax error")) @@ -117,7 +117,7 @@ (ert-deftest test-prog-webdev-format-buffer-errors-without-prettier () "Error: prettier missing -> `user-error', nothing shells out." (let ((ran nil)) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) nil)) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) nil)) ((symbol-function 'call-process-region) (lambda (&rest _) (setq ran t) 0))) (with-temp-buffer diff --git a/tests/test-prog-webdev-setup.el b/tests/test-prog-webdev-setup.el index 45310f237..906a54151 100644 --- a/tests/test-prog-webdev-setup.el +++ b/tests/test-prog-webdev-setup.el @@ -67,7 +67,7 @@ electric-pair-local-mode all get called." ((symbol-function 'lsp-deferred) (lambda (&rest _) (setq started t))) ((symbol-function 'executable-find) - (lambda (path) (when (equal path ts-language-server-path) + (lambda (path &rest _) (when (equal path ts-language-server-path) "/usr/bin/typescript-language-server")))) (cj/webdev-setup)) (should started)))) @@ -82,7 +82,7 @@ electric-pair-local-mode all get called." ((symbol-function 'electric-pair-local-mode) #'ignore) ((symbol-function 'lsp-deferred) (lambda (&rest _) (setq started t))) - ((symbol-function 'executable-find) (lambda (_) nil))) + ((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (cj/webdev-setup)) (should-not started)))) diff --git a/tests/test-prog-yaml--yaml-format-buffer.el b/tests/test-prog-yaml--yaml-format-buffer.el index 28ad351f9..aae3199ce 100644 --- a/tests/test-prog-yaml--yaml-format-buffer.el +++ b/tests/test-prog-yaml--yaml-format-buffer.el @@ -14,7 +14,7 @@ (ert-deftest test-prog-yaml--yaml-format-buffer-invokes-prettier-argv () "Normal: with prettier present, the formatter calls it via argv, no shell." (let (program args) - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/prettier")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/prettier")) ((symbol-function 'call-process-region) (lambda (_start _end prog &rest rest) (setq program prog @@ -29,7 +29,7 @@ (ert-deftest test-prog-yaml--yaml-format-buffer-no-clobber-on-failure () "Error: a non-zero prettier exit leaves the buffer untouched and errors." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/prettier")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/prettier")) ((symbol-function 'call-process-region) (lambda (_start _end _prog _delete buffer &rest _) (with-current-buffer buffer (insert "[error] bad yaml")) @@ -98,7 +98,7 @@ (ert-deftest test-prog-yaml--yaml-format-buffer-error-no-prettier () "Signals user-error when prettier is not found." - (cl-letf (((symbol-function 'executable-find) (lambda (_) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_ &rest _) nil))) (with-temp-buffer (insert "key: value\n") (should-error (cj/yaml-format-buffer) :type 'user-error)))) diff --git a/tests/test-slack-config-commands.el b/tests/test-slack-config-commands.el index 8944662ef..21cbb3e5a 100644 --- a/tests/test-slack-config-commands.el +++ b/tests/test-slack-config-commands.el @@ -194,7 +194,7 @@ ((symbol-function 'slack-buffer-update-mark-request) (lambda (_buf ts) (setq marked ts))) ((symbol-function 'bury-buffer) - (lambda () (setq buried t)))) + (lambda (&rest _) (setq buried t)))) (cj/slack-mark-read-and-bury)) (should (equal marked "1234.5678")) (should buried))) @@ -207,7 +207,7 @@ (cl-letf (((symbol-function 'slack-buffer-update-mark-request) (lambda (&rest _) (setq marked t))) ((symbol-function 'bury-buffer) - (lambda () (setq buried t)))) + (lambda (&rest _) (setq buried t)))) (cj/slack-mark-read-and-bury)) (should-not marked) (should buried))) diff --git a/tests/test-system-commands-resolve-and-run.el b/tests/test-system-commands-resolve-and-run.el index 2c9d98d0c..af2288fd9 100644 --- a/tests/test-system-commands-resolve-and-run.el +++ b/tests/test-system-commands-resolve-and-run.el @@ -118,19 +118,19 @@ does not run the command." (ert-deftest test-system-cmd-service-available-true-on-zero-exit () "Normal: service is available when systemctl exists and `cat' exits 0." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/systemctl")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/systemctl")) ((symbol-function 'call-process) (lambda (&rest _) 0))) (should (cj/system-cmd--emacs-service-available-p)))) (ert-deftest test-system-cmd-service-available-false-on-nonzero-exit () "Boundary: a nonzero exit (no such unit) means not available." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) "/usr/bin/systemctl")) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) "/usr/bin/systemctl")) ((symbol-function 'call-process) (lambda (&rest _) 1))) (should-not (cj/system-cmd--emacs-service-available-p)))) (ert-deftest test-system-cmd-service-available-false-when-systemctl-absent () "Error: with no systemctl on PATH the service can't be available." - (cl-letf (((symbol-function 'executable-find) (lambda (_p) nil)) + (cl-letf (((symbol-function 'executable-find) (lambda (_p &rest _) nil)) ((symbol-function 'call-process) (lambda (&rest _) (error "must not shell out without systemctl")))) (should-not (cj/system-cmd--emacs-service-available-p)))) @@ -220,7 +220,7 @@ kill-emacs directly (the service owns the daemon lifecycle)." (cl-letf (((symbol-function 'completing-read) (lambda (&rest _) "Lock Screen")) ((symbol-function 'call-interactively) - (lambda (cmd) (setq called cmd)))) + (lambda (cmd &rest _) (setq called cmd)))) (cj/system-command-menu)) (should (eq called 'cj/system-cmd-lock)))) diff --git a/tests/test-term-tmux-history.el b/tests/test-term-tmux-history.el index e36b3e98e..4ad7fb79d 100644 --- a/tests/test-term-tmux-history.el +++ b/tests/test-term-tmux-history.el @@ -75,7 +75,7 @@ RESPONSES is an alist of (ARGS EXIT-CODE OUTPUT)." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8"))) + (lambda (_process &rest _) "/dev/pts/8"))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/8\t%8\n") @@ -106,7 +106,7 @@ the terminal's frame slot rather than splitting or popping a new window." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8"))) + (lambda (_process &rest _) "/dev/pts/8"))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/8\t%8\n") @@ -194,7 +194,7 @@ ghostel-mode terminal." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8"))) + (lambda (_process &rest _) "/dev/pts/8"))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/1\t%1\n/dev/pts/8\t%8\n")) @@ -210,7 +210,7 @@ ghostel-mode terminal." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8"))) + (lambda (_process &rest _) "/dev/pts/8"))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/8\t%8\n")) @@ -226,7 +226,7 @@ ghostel-mode terminal." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8"))) + (lambda (_process &rest _) "/dev/pts/8"))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/1\t%1\n")) @@ -242,7 +242,7 @@ ghostel-mode terminal." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8"))) + (lambda (_process &rest _) "/dev/pts/8"))) (test-term-tmux-history--with-tmux-mock '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 1 "no server running")) @@ -273,7 +273,7 @@ puts it at column 0 so it runs up the left." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8")) + (lambda (_process &rest _) "/dev/pts/8")) ((symbol-function 'ghostel-send-string) (lambda (s) (push s sent))) ((symbol-function 'ghostel-copy-mode) @@ -301,7 +301,7 @@ scrolling, parity with the tmux branch's trailing C-a." (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) - (lambda (_process) "/dev/pts/8")) + (lambda (_process &rest _) "/dev/pts/8")) ((symbol-function 'ghostel-send-string) (lambda (s) (push s sent))) ((symbol-function 'ghostel-copy-mode) diff --git a/tests/test-transcription-process-and-sentinel.el b/tests/test-transcription-process-and-sentinel.el index 330a0260b..90b56f0a5 100644 --- a/tests/test-transcription-process-and-sentinel.el +++ b/tests/test-transcription-process-and-sentinel.el @@ -26,7 +26,7 @@ (let (msg) (cl-letf (((symbol-function 'message) (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))) - ((symbol-function 'getenv) (lambda (_) nil))) + ((symbol-function 'getenv) (lambda (_ &rest _) nil))) (cj/--notify "Transcription" "started")) (should (equal msg "Transcription: started")))) @@ -36,7 +36,7 @@ the title, body, and urgency." (let (notify-kwargs) (cl-letf (((symbol-function 'message) #'ignore) ((symbol-function 'getenv) - (lambda (var) (and (equal var "DISPLAY") ":0"))) + (lambda (var &rest _) (and (equal var "DISPLAY") ":0"))) ((symbol-function 'notifications-notify) (lambda (&rest kwargs) (setq notify-kwargs kwargs)))) (cj/--notify "Transcription" "done" 'critical)) diff --git a/tests/test-transcription-status-and-commands.el b/tests/test-transcription-status-and-commands.el index 7c796de0e..af7255cdc 100644 --- a/tests/test-transcription-status-and-commands.el +++ b/tests/test-transcription-status-and-commands.el @@ -138,7 +138,7 @@ (cl-letf (((symbol-function 'process-live-p) (lambda (_) t)) ((symbol-function 'kill-process) - (lambda (p) (setq killed p))) + (lambda (p &rest _) (setq killed p))) ((symbol-function 'message) (lambda (fmt &rest args) (setq msg (apply #'format fmt args))))) diff --git a/tests/test-ui-config-transparency-and-cursor.el b/tests/test-ui-config-transparency-and-cursor.el index b01fa2b71..13906773b 100644 --- a/tests/test-ui-config-transparency-and-cursor.el +++ b/tests/test-ui-config-transparency-and-cursor.el @@ -23,7 +23,7 @@ (cj/transparency-level 70) (default-frame-alist nil) (applied nil)) - (cl-letf (((symbol-function 'display-graphic-p) (lambda () t)) + (cl-letf (((symbol-function 'display-graphic-p) (lambda (&rest _) t)) ((symbol-function 'set-frame-parameter) (lambda (_frame param value) (when (eq param 'alpha) (setq applied value))))) @@ -37,7 +37,7 @@ (cj/transparency-level 50) (default-frame-alist '((alpha . (50 . 50)))) (applied nil)) - (cl-letf (((symbol-function 'display-graphic-p) (lambda () t)) + (cl-letf (((symbol-function 'display-graphic-p) (lambda (&rest _) t)) ((symbol-function 'set-frame-parameter) (lambda (_frame param value) (when (eq param 'alpha) (setq applied value))))) @@ -52,7 +52,7 @@ the default-frame-alist so a future graphical frame would pick it up." (cj/transparency-level 60) (default-frame-alist nil) (set-called nil)) - (cl-letf (((symbol-function 'display-graphic-p) (lambda () nil)) + (cl-letf (((symbol-function 'display-graphic-p) (lambda (&rest _) nil)) ((symbol-function 'set-frame-parameter) (lambda (&rest _) (setq set-called t)))) (cj/apply-transparency)) @@ -66,7 +66,7 @@ surfaced via `message'; the default-alist update still happens." (cj/transparency-level 60) (default-frame-alist nil) (msg nil)) - (cl-letf (((symbol-function 'display-graphic-p) (lambda () t)) + (cl-letf (((symbol-function 'display-graphic-p) (lambda (&rest _) t)) ((symbol-function 'set-frame-parameter) (lambda (&rest _) (error "boom"))) ((symbol-function 'message) @@ -83,7 +83,7 @@ surfaced via `message'; the default-alist update still happens." (cj/transparency-level 80) (default-frame-alist nil) (applied nil)) - (cl-letf (((symbol-function 'display-graphic-p) (lambda () t)) + (cl-letf (((symbol-function 'display-graphic-p) (lambda (&rest _) t)) ((symbol-function 'set-frame-parameter) (lambda (_frame param value) (when (eq param 'alpha) (setq applied value)))) @@ -97,7 +97,7 @@ surfaced via `message'; the default-alist update still happens." (let ((cj/enable-transparency t) (cj/transparency-level 90) (default-frame-alist nil)) - (cl-letf (((symbol-function 'display-graphic-p) (lambda () t)) + (cl-letf (((symbol-function 'display-graphic-p) (lambda (&rest _) t)) ((symbol-function 'set-frame-parameter) #'ignore) ((symbol-function 'message) #'ignore)) (cj/toggle-transparency) diff --git a/tests/test-ui-navigation-split-follow-undo-kill.el b/tests/test-ui-navigation-split-follow-undo-kill.el index f6981a36a..35ed7a020 100644 --- a/tests/test-ui-navigation-split-follow-undo-kill.el +++ b/tests/test-ui-navigation-split-follow-undo-kill.el @@ -70,7 +70,7 @@ non-visited entry, not the second." (setq buffer-file-name "/tmp/alive.txt")) b)))) ((symbol-function 'find-file) - (lambda (f) (setq opened f)))) + (lambda (f &rest _) (setq opened f)))) (unwind-protect (cj/undo-kill-buffer 1) (when (get-buffer "*test-alive*") (kill-buffer "*test-alive*")))) @@ -93,7 +93,7 @@ currently-open most-recent file was never skipped." (setq buffer-file-name "/tmp/alive.txt")) b)))) ((symbol-function 'find-file) - (lambda (f) (setq opened f)))) + (lambda (f &rest _) (setq opened f)))) (unwind-protect (cj/undo-kill-buffer 1) (when (get-buffer "*test-alive*") (kill-buffer "*test-alive*")))) @@ -108,7 +108,7 @@ currently-open most-recent file was never skipped." ((symbol-function 'recentf-mode) (lambda (&rest _) t)) ((symbol-function 'buffer-list) (lambda (&rest _) nil)) ((symbol-function 'find-file) - (lambda (f) (setq opened f)))) + (lambda (f &rest _) (setq opened f)))) (cj/undo-kill-buffer 2)) (should (equal opened "/tmp/b.org")))) @@ -121,7 +121,7 @@ currently-open most-recent file was never skipped." ((symbol-function 'recentf-mode) (lambda (&rest _) t)) ((symbol-function 'buffer-list) (lambda (&rest _) nil)) ((symbol-function 'find-file) - (lambda (f) (setq opened f)))) + (lambda (f &rest _) (setq opened f)))) (cj/undo-kill-buffer 0)) (should-not opened))) @@ -134,7 +134,7 @@ not a wrong-type-argument from find-file on nil." (cl-letf (((symbol-function 'require) (lambda (&rest _) t)) ((symbol-function 'recentf-mode) (lambda (&rest _) t)) ((symbol-function 'buffer-list) (lambda (&rest _) nil)) - ((symbol-function 'find-file) (lambda (f) (setq opened f)))) + ((symbol-function 'find-file) (lambda (f &rest _) (setq opened f)))) (should-error (cj/undo-kill-buffer 5) :type 'user-error)) (should-not opened))) diff --git a/tests/test-video-audio-recording--build-video-command.el b/tests/test-video-audio-recording--build-video-command.el index 3b79c9ecb..4f2909784 100644 --- a/tests/test-video-audio-recording--build-video-command.el +++ b/tests/test-video-audio-recording--build-video-command.el @@ -21,7 +21,7 @@ "Wayland command pipes wf-recorder to ffmpeg." (let ((cj/recording-mic-boost 2.0) (cj/recording-system-volume 1.0)) - (cl-letf (((symbol-function 'executable-find) (lambda (_prog) t))) + (cl-letf (((symbol-function 'executable-find) (lambda (_prog &rest _) t))) (let ((cmd (cj/recording--build-video-command "mic" "sys" "/tmp/out.mkv" t))) (should (string-match-p "wf-recorder.*|.*ffmpeg" cmd)) (should (string-match-p "-i pipe:0" cmd)) @@ -60,7 +60,7 @@ "Device names with special characters are shell-quoted in Wayland mode." (let ((cj/recording-mic-boost 1.0) (cj/recording-system-volume 1.0)) - (cl-letf (((symbol-function 'executable-find) (lambda (_prog) t))) + (cl-letf (((symbol-function 'executable-find) (lambda (_prog &rest _) t))) (let ((cmd (cj/recording--build-video-command "device with spaces" "sys" "/tmp/out.mkv" t))) ;; shell-quote-argument escapes spaces with backslashes @@ -70,7 +70,7 @@ "Output filename with spaces is shell-quoted in Wayland mode." (let ((cj/recording-mic-boost 1.0) (cj/recording-system-volume 1.0)) - (cl-letf (((symbol-function 'executable-find) (lambda (_prog) t))) + (cl-letf (((symbol-function 'executable-find) (lambda (_prog &rest _) t))) (let ((cmd (cj/recording--build-video-command "mic" "sys" "/tmp/my recording.mkv" t))) ;; Filename should be quoted/escaped @@ -103,7 +103,7 @@ (ert-deftest test-video-audio-recording--build-video-command-error-wayland-no-wf-recorder () "Wayland mode signals error when wf-recorder is not installed." - (cl-letf (((symbol-function 'executable-find) (lambda (_prog) nil))) + (cl-letf (((symbol-function 'executable-find) (lambda (_prog &rest _) nil))) (should-error (cj/recording--build-video-command "mic" "sys" "/tmp/out.mkv" t) :type 'user-error))) diff --git a/tests/test-video-audio-recording--test-device.el b/tests/test-video-audio-recording--test-device.el index e701b69fd..aa85b4388 100644 --- a/tests/test-video-audio-recording--test-device.el +++ b/tests/test-video-audio-recording--test-device.el @@ -20,7 +20,7 @@ "Runs exactly 2 shell commands: ffmpeg to record, ffplay to playback." (let ((commands nil)) (cl-letf (((symbol-function 'shell-command) - (lambda (cmd) (push cmd commands) 0))) + (lambda (cmd &rest _) (push cmd commands) 0))) (cj/recording--test-device "test-device" "test-" "GO!") (should (= 2 (length commands))) ;; ffmpeg runs first (pushed last due to stack order) @@ -31,7 +31,7 @@ "The provided device name appears in the ffmpeg command." (let ((commands nil)) (cl-letf (((symbol-function 'shell-command) - (lambda (cmd) (push cmd commands) 0))) + (lambda (cmd &rest _) (push cmd commands) 0))) (cj/recording--test-device "alsa_input.usb-Jabra.mono" "mic-" "SPEAK!") (let ((ffmpeg-cmd (cadr commands))) (should (string-match-p "alsa_input.usb-Jabra.mono" ffmpeg-cmd)) @@ -43,7 +43,7 @@ "Device names with special characters are shell-quoted." (let ((commands nil)) (cl-letf (((symbol-function 'shell-command) - (lambda (cmd) (push cmd commands) 0))) + (lambda (cmd &rest _) (push cmd commands) 0))) (cj/recording--test-device "device with spaces" "test-" "GO!") (let ((ffmpeg-cmd (cadr commands))) ;; shell-quote-argument should have escaped the spaces @@ -54,7 +54,7 @@ (ert-deftest test-video-audio-recording--test-device-error-ffmpeg-failure-no-crash () "Function completes without error even when ffmpeg returns non-zero." (cl-letf (((symbol-function 'shell-command) - (lambda (_cmd) 1))) + (lambda (_cmd &rest _) 1))) ;; Should not signal any error (cj/recording--test-device "dev" "test-" "GO!") (should t))) diff --git a/tests/test-video-audio-recording-check-ffmpeg.el b/tests/test-video-audio-recording-check-ffmpeg.el index 5c264b640..1d8f13247 100644 --- a/tests/test-video-audio-recording-check-ffmpeg.el +++ b/tests/test-video-audio-recording-check-ffmpeg.el @@ -20,7 +20,7 @@ (ert-deftest test-video-audio-recording-check-ffmpeg-normal-ffmpeg-found-returns-t () "Test that function returns t when ffmpeg is found." (cl-letf (((symbol-function 'executable-find) - (lambda (cmd) + (lambda (cmd &rest _) (when (equal cmd "ffmpeg") "/usr/bin/ffmpeg")))) (let ((result (cj/recording-check-ffmpeg))) (should (eq t result))))) @@ -30,13 +30,13 @@ (ert-deftest test-video-audio-recording-check-ffmpeg-error-ffmpeg-not-found-signals-error () "Test that function signals user-error when ffmpeg is not found." (cl-letf (((symbol-function 'executable-find) - (lambda (_cmd) nil))) + (lambda (_cmd &rest _) nil))) (should-error (cj/recording-check-ffmpeg) :type 'user-error))) (ert-deftest test-video-audio-recording-check-ffmpeg-error-message-mentions-pacman () "Test that error message includes installation command." (cl-letf (((symbol-function 'executable-find) - (lambda (_cmd) nil))) + (lambda (_cmd &rest _) nil))) (condition-case err (cj/recording-check-ffmpeg) (user-error diff --git a/tests/test-video-audio-recording-ffmpeg-functions.el b/tests/test-video-audio-recording-ffmpeg-functions.el index 549aa317f..4b3570a26 100644 --- a/tests/test-video-audio-recording-ffmpeg-functions.el +++ b/tests/test-video-audio-recording-ffmpeg-functions.el @@ -190,7 +190,7 @@ (setq cj/video-recording-ffmpeg-process fake-process) (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil)) ((symbol-function 'signal-process) - (lambda (_pid _sig) (setq signal-called t) 0)) + (lambda (_pid _sig &rest _) (setq signal-called t) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc _timeout) t))) (cj/video-recording-stop) @@ -231,7 +231,7 @@ (signal-called nil)) (setq cj/audio-recording-ffmpeg-process fake-process) (cl-letf (((symbol-function 'signal-process) - (lambda (_pid _sig) (setq signal-called t) 0)) + (lambda (_pid _sig &rest _) (setq signal-called t) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc _timeout) t))) (cj/audio-recording-stop) @@ -287,7 +287,7 @@ (setq cj/video-recording-ffmpeg-process fake-process) (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil)) ((symbol-function 'signal-process) - (lambda (_pid _sig) (error "Signal failed")))) + (lambda (_pid _sig &rest _) (error "Signal failed")))) (condition-case _err (cj/video-recording-stop) (error (setq error-raised t))) @@ -303,7 +303,7 @@ (error-raised nil)) (setq cj/audio-recording-ffmpeg-process fake-process) (cl-letf (((symbol-function 'signal-process) - (lambda (_pid _sig) (error "Signal failed")))) + (lambda (_pid _sig &rest _) (error "Signal failed")))) (condition-case _err (cj/audio-recording-stop) (error (setq error-raised t))) diff --git a/tests/test-video-audio-recording-process-cleanup.el b/tests/test-video-audio-recording-process-cleanup.el index 52177a17c..7cb261c16 100644 --- a/tests/test-video-audio-recording-process-cleanup.el +++ b/tests/test-video-audio-recording-process-cleanup.el @@ -53,7 +53,7 @@ (setq cj/video-recording-ffmpeg-process fake-process) (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil)) ((symbol-function 'signal-process) - (lambda (pid sig) + (lambda (pid sig &rest _) (setq signaled-pid pid) (setq signaled-sig sig) 0)) @@ -85,7 +85,7 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." (push (cons 'pkill args) call-order)) 0)) ((symbol-function 'signal-process) - (lambda (_pid _sig) + (lambda (_pid _sig &rest _) (push 'signal call-order) 0)) ((symbol-function 'cj/recording--wait-for-exit) @@ -114,7 +114,7 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." (when (equal program "pkill") (push args pkill-args-list)) 0)) - ((symbol-function 'signal-process) (lambda (_pid _sig) 0)) + ((symbol-function 'signal-process) (lambda (_pid _sig &rest _) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc _timeout) t))) (cj/video-recording-stop) @@ -140,7 +140,7 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." (when (equal program "pkill") (setq pkill-called t)) 0)) - ((symbol-function 'signal-process) (lambda (_pid _sig) 0)) + ((symbol-function 'signal-process) (lambda (_pid _sig &rest _) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc _timeout) t))) (cj/video-recording-stop) @@ -206,7 +206,7 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." (wait-timeout nil)) (setq cj/video-recording-ffmpeg-process fake-process) (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil)) - ((symbol-function 'signal-process) (lambda (_pid _sig) 0)) + ((symbol-function 'signal-process) (lambda (_pid _sig &rest _) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc timeout) (setq wait-called t) @@ -227,7 +227,7 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." (warning-shown nil)) (setq cj/video-recording-ffmpeg-process fake-process) (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil)) - ((symbol-function 'signal-process) (lambda (_pid _sig) 0)) + ((symbol-function 'signal-process) (lambda (_pid _sig &rest _) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc _timeout) nil)) ; Simulate timeout ((symbol-function 'message) @@ -247,7 +247,7 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))) (warning-shown nil)) (setq cj/audio-recording-ffmpeg-process fake-process) - (cl-letf (((symbol-function 'signal-process) (lambda (_pid _sig) 0)) + (cl-letf (((symbol-function 'signal-process) (lambda (_pid _sig &rest _) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc _timeout) nil)) ; Simulate timeout ((symbol-function 'message) @@ -268,7 +268,7 @@ so ffmpeg sees EOF on its video input pipe and starts finalizing the file." (wait-called nil) (wait-timeout nil)) (setq cj/audio-recording-ffmpeg-process fake-process) - (cl-letf (((symbol-function 'signal-process) (lambda (_pid _sig) 0)) + (cl-letf (((symbol-function 'signal-process) (lambda (_pid _sig &rest _) 0)) ((symbol-function 'cj/recording--wait-for-exit) (lambda (_proc timeout) (setq wait-called t) diff --git a/tests/test-video-audio-recording-test-mic.el b/tests/test-video-audio-recording-test-mic.el index 60b9eb0b7..64ef0eaab 100644 --- a/tests/test-video-audio-recording-test-mic.el +++ b/tests/test-video-audio-recording-test-mic.el @@ -36,11 +36,11 @@ (let ((temp-file nil)) ;; Mock make-temp-file to capture filename (cl-letf (((symbol-function 'make-temp-file) - (lambda (prefix _dir-flag suffix) + (lambda (prefix _dir-flag suffix &rest _) (setq temp-file (concat prefix "12345" suffix)) temp-file)) ((symbol-function 'shell-command) - (lambda (_cmd) 0))) + (lambda (_cmd &rest _) 0))) (cj/recording-test-mic) (should (string-match-p "\\.wav$" temp-file))))) (test-mic-teardown))) @@ -54,7 +54,7 @@ (let ((commands nil)) ;; Mock shell-command to capture all commands (cl-letf (((symbol-function 'shell-command) - (lambda (cmd) (push cmd commands) 0))) + (lambda (cmd &rest _) (push cmd commands) 0))) (cj/recording-test-mic) (should (= 2 (length commands))) ;; First command should be ffmpeg (stored last in list due to push) @@ -74,7 +74,7 @@ (let ((commands nil)) ;; Capture all shell commands (cl-letf (((symbol-function 'shell-command) - (lambda (cmd) (push cmd commands) 0))) + (lambda (cmd &rest _) (push cmd commands) 0))) (cj/recording-test-mic) (should (= 2 (length commands))) ;; Second command should be ffplay @@ -93,7 +93,7 @@ (cl-letf (((symbol-function 'message) (lambda (fmt &rest args) (push (apply #'format fmt args) messages))) ((symbol-function 'shell-command) - (lambda (_cmd) 0))) + (lambda (_cmd &rest _) 0))) (cj/recording-test-mic) (should (>= (length messages) 3)) ;; Check for recording message @@ -135,7 +135,7 @@ (setq cj/recording-mic-device "test-mic-device") ;; Mock shell-command to fail (cl-letf (((symbol-function 'shell-command) - (lambda (_cmd) 1))) ;; Non-zero exit code + (lambda (_cmd &rest _) 1))) ;; Non-zero exit code ;; Should complete without crashing (ffmpeg errors are ignored) ;; No error is raised - function just completes (cj/recording-test-mic) diff --git a/tests/test-video-audio-recording-test-monitor.el b/tests/test-video-audio-recording-test-monitor.el index d821600f0..168e4f072 100644 --- a/tests/test-video-audio-recording-test-monitor.el +++ b/tests/test-video-audio-recording-test-monitor.el @@ -36,11 +36,11 @@ (let ((temp-file nil)) ;; Mock make-temp-file to capture filename (cl-letf (((symbol-function 'make-temp-file) - (lambda (prefix _dir-flag suffix) + (lambda (prefix _dir-flag suffix &rest _) (setq temp-file (concat prefix "12345" suffix)) temp-file)) ((symbol-function 'shell-command) - (lambda (_cmd) 0))) + (lambda (_cmd &rest _) 0))) (cj/recording-test-monitor) (should (string-match-p "monitor-test-" temp-file)) (should (string-match-p "\\.wav$" temp-file))))) @@ -55,7 +55,7 @@ (let ((commands nil)) ;; Mock shell-command to capture all commands (cl-letf (((symbol-function 'shell-command) - (lambda (cmd) (push cmd commands) 0))) + (lambda (cmd &rest _) (push cmd commands) 0))) (cj/recording-test-monitor) (should (= 2 (length commands))) ;; First command should be ffmpeg (stored last in list due to push) @@ -75,7 +75,7 @@ (let ((commands nil)) ;; Capture all shell commands (cl-letf (((symbol-function 'shell-command) - (lambda (cmd) (push cmd commands) 0))) + (lambda (cmd &rest _) (push cmd commands) 0))) (cj/recording-test-monitor) (should (= 2 (length commands))) ;; Second command should be ffplay @@ -94,7 +94,7 @@ (cl-letf (((symbol-function 'message) (lambda (fmt &rest args) (push (apply #'format fmt args) messages))) ((symbol-function 'shell-command) - (lambda (_cmd) 0))) + (lambda (_cmd &rest _) 0))) (cj/recording-test-monitor) (should (>= (length messages) 3)) ;; Check for recording message @@ -136,7 +136,7 @@ (setq cj/recording-system-device "test-monitor-device") ;; Mock shell-command to fail (cl-letf (((symbol-function 'shell-command) - (lambda (_cmd) 1))) ;; Non-zero exit code + (lambda (_cmd &rest _) 1))) ;; Non-zero exit code ;; Should complete without crashing (ffmpeg errors are ignored) ;; No error is raised - function just completes (cj/recording-test-monitor) diff --git a/tests/test-video-audio-recording-toggle-functions.el b/tests/test-video-audio-recording-toggle-functions.el index 2355ab4f6..cdd3096ac 100644 --- a/tests/test-video-audio-recording-toggle-functions.el +++ b/tests/test-video-audio-recording-toggle-functions.el @@ -84,7 +84,7 @@ (let ((prompt-called nil) (recorded-dir nil)) (cl-letf (((symbol-function 'read-directory-name) - (lambda (_prompt) (setq prompt-called t) "/custom/path/")) + (lambda (_prompt &rest _) (setq prompt-called t) "/custom/path/")) ((symbol-function 'file-directory-p) (lambda (_dir) t)) ; Directory exists ((symbol-function 'cj/ffmpeg-record-video) @@ -139,7 +139,7 @@ (let ((prompt-called nil) (recorded-dir nil)) (cl-letf (((symbol-function 'read-directory-name) - (lambda (_prompt) (setq prompt-called t) "/custom/path/")) + (lambda (_prompt &rest _) (setq prompt-called t) "/custom/path/")) ((symbol-function 'file-directory-p) (lambda (_dir) t)) ; Directory exists ((symbol-function 'cj/ffmpeg-record-audio) @@ -55,95 +55,6 @@ Tags are additive. For example, a small wrong-behavior fix can be =:bug:quick:=, and a feature that requires internal restructuring can be =:feature:refactor:=. * Emacs Open Work -** DONE [#B] Codebase refactoring program — remaining batch :refactor:solo: -CLOSED: [2026-06-20 Sat] -Complete 2026-06-20: all 13 scan findings addressed across the day's sessions (see -=.ai/sessions/= for the logs). 5 medium extractions + 2 big single-file refactors + -6 theme-studio items including the browser-gates harness rewrite. The only item not -done is the item-8 plan() factory, consciously skipped as premature abstraction -(heterogeneous call sites — see "Remaining — item-8 plan() factory" below). -The original scan: full-codebase 8-agent fan-out over modules/ + scripts/theme-studio/, -one focused refactor per commit, won't-do items excluded. - -*** Working protocol (apply to every item) -- TDD: write/keep a failing-then-green test; harvest new test seams the refactor opens. -- Behavior-preserving only. If a "dedup" would delete a real test seam or couple - dissimilar code, SKIP it and record why (see skips below). -- Per refactor, verify in this order, then commit + push (no-approvals mode): - 1. =make test-file FILE=<basename.el>= for touched + new tests. - 2. =make validate-modules= (loads all 123 modules; catches load/paren errors). - 3. Init-launch smoke on a throwaway daemon: =emacs --daemon=cj-sNN=, then - =emacsclient -s cj-sNN -e '(emacs-pid)'= to capture the PID, check - =(length features)= = 807 and no init errors in the log, then kill by that - PID (the emacsclient kill-emacs is flaky; pkill -f 'daemon=cj-sNN' - self-matches its own shell — kill the captured PID). - 4. Live-reload the edited module into Craig's running daemon - (=emacsclient -e '(load "/home/cjennings/.emacs.d/modules/<m>.el")'=); skip - the live reload for big use-package modules whose :config restacks (verify via - the fresh smoke daemon instead, as with mail-config). -- Tab-heavy files: =sed -n 'A,Bp' FILE | cat -A= to get exact bytes before an Edit; - write NEW code in the documented 2-space style. -- Shared asset already created: =cj/format-region-with-program= in system-lib.el - (the run-a-formatter-over-the-buffer helper). Reuse it for any further - format-region duplicates. - -*** DONE — medium extractions (2026-06-20 afternoon) -All five shipped: calibredb-epub nov re-render/centering helpers (fccf29b0); -ai-term toggle-off teardown + working-buffer swap (62fee96b); calendar-sync -per-event exception parser (23f405b4); dirvish playlist-target resolution -(a1ca2fb0); custom-case per-word title-case decision (4cc9ca0b). - -*** DONE — big single-file + theme-studio (2026-06-20 afternoon, no-approvals run) -Both big single-file items shipped: dwim-shell branching command builders -(f93b4615); custom-comments divider/box generator dedup (42f0c88a). Five of the -six theme-studio items shipped: face_coverage path_kind (9a52370b), -capture-default-faces condition_matches unify (28b4d1cf), dropdownRowTextColor -delete (10a56789), test-file inline-integrity dedup — subTest loop + shared -inline-strip.mjs (13969c70), generate.py lazy _build()/__getattr__ (6df4ebdc), -browser-gates assertPreviewFaces for the 3 preview gates (5627f137). - -*** DONE — browser-gates harness rewrite (with Craig's go-ahead, 2026-06-20) -- =gate(id, body)= helper (05697e83): the 38 standard gates' ok/notes/A + title + - result-div boilerplate, note format standardized to " fails=". Each call site keeps - its literal =location.hash==='#NAMEtest'=. 6 custom gates stay inline. First automated - attempt deleted gates (a closing-finder spanned boundaries) — caught by a gate-count - guard, reverted, redone anchored on each gate's unique =d.id=. Verified all 44 green + - a forced A(false) in a converted gate still FAILs. -- =withSavedState(keys, body)= (a473aa7c): wraps the 7 restore-nothing gates, scoped to - the globals each mutates; JSON-clone snapshot + finally-restore (structuredClone threw - on the studio objects — caught by the gate run as "no verdict", switched to JSON like - the gates' own local saves). The 14 self-restoring gates left as-is. Verified 44 green, - restore round-trip holds, broken assertion in a wrapped gate still FAILs. - -*** Remaining — item-8 plan() factory (deferred, low value) -The =plan(overrides)= factory for the ~30 planPaletteGenerator calls (test-app-core.mjs -+ test-palette-generator-core.mjs) was deferred. The calls pass heterogeneous options -(scheme/accentCount/sourceMode/vibe/intent vary per call); a factory only dedups the -constant spanCount:0/rng and would hide which options each test actually exercises — -premature abstraction over varying calls. The other two item-8 parts (subTest loop + -shared stripExports) shipped in 13969c70. - -*** WON'T-DO (do not re-attempt — assessed and rejected) -- theme-studio buildTable/buildUITable/buildPkgTable merge: genuine per-tier divergence - (column order, syntax dual fg/bg dropdowns, ui preview cell, pkg nd markers) + the - =.cells[N]= positional sort coupling make a unified builder MORE complex than the - three explicit ones. Close as won't-do. -- Cross-language test overlap (browser-gates preview gate vs test_generate.py - PackageFaceCoverage): don't merge — would couple a fast Python test to a headless - browser run. A one-line comment in each noting the split is the most that's worth it. - -*** Skipped this run (with reasons — don't redo) -- eshell-config ssh-alias "merge the two helpers": =cj/--eshell-ssh-alias-commands= is - a deliberate pure/effectful split with 3 dedicated tests; merging deletes the seam. -- prog-*-setup boilerplate: only python+webdev share the full pattern; shell/c/elisp/ - common-lisp differ materially. A keyword-arg helper would be less readable. No - premature abstraction. -- erc join-command =cj/erc--ensure-active-connection= extraction: nesting-only on - untestable UI (call-interactively/switch-to-buffer), no test seam, risky tab-rewrite. -- coverage-core =simplecov-executable-lines= vs =parse-simplecov= clone: borderline - MEDIUM, differs only by a =(> hits 0)= predicate; parameterize with a keep-line-p - only if revisiting. Low priority. - ** TODO [#B] Un-pin ghostel from 0.33.0 once upstream fixes #422/#423 :bug: :PROPERTIES: :LAST_REVIEWED: 2026-06-20 @@ -152,24 +63,6 @@ ghostel is held at 0.33.0 (=ghostel-20260604.2049=, commit 5779a2adceb2) in =mod archsetup automated the zig 0.15.2 pin (managed =install_zig_pin= step, sha-verified, unit-tested). If the un-pinned ghostel bumps its ghostty dependency to a newer zig, send archsetup the new version + sha256 so it bumps its =ZIG_VERSION= / =ZIG_SHA256= constants (=inbox-send archsetup=). -** CANCELLED [#A] calendar-sync drops final occurrences, resurrects cancelled meetings :bug:solo:next: -CLOSED: [2026-06-20 Sat 22:51] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-13 -:END: -Needs from Craig: a real .ics fixture (or two) that reproduces both symptoms — a recurring event missing its final occurrence, and a cancelled meeting that reappears. This is RFC-5545 recurrence handling (RRULE/UNTIL/EXDATE/STATUS:CANCELLED); I won't guess-patch the parser without a failing case to test against. Drop a sanitized .ics and I'll write the characterization test + fix. -RFC 5545 conformance holes in =modules/calendar-sync.el=, all agenda-visible (from the 2026-06 config audit): -- =:973,1015,1024= — UNTIL treated as exclusive (strict =calendar-sync--before-date-p=); RFC and Google make it inclusive, so the LAST instance of every UNTIL-bounded series vanishes. Tests assert loose count ranges, so it's unpinned. Allow equality. -- =:578= — comma-separated EXDATE lists (Google emits them) never parse; the exclusion drops silently and cancelled occurrences reappear on the agenda. Split on "," before parsing; no comma-case test exists. -- =:902= — timed events without DTEND render as all-day (time lost); multi-day all-day spans collapse to one day (end date unused, exclusive-DTEND unhandled). Emit start-time-only stamps and org date ranges. ------ - -2026-06-20 Sat @ 22:52:51 -0400 Can't reproduce. closing - -** DONE [#A] Native compilation disabled config-wide; GC at stock 800KB :bug:next: -CLOSED: [2026-06-20 Sat] -Both fixed 2026-06-20. =early-init.el:69= was =(setq native-comp-deferred-compilation nil)= — the obsolete alias of =native-comp-jit-compilation= — which turned JIT native-comp OFF entirely (not "synchronous"); replaced with =(setq native-comp-jit-compilation t)= + =native-comp-async-report-warnings-errors 'silent=. The old "Selecting deleted buffer" async race was an Emacs 28/29 issue; this is 30.2. GC: dropped the early-init post-startup restore to stock 800KB and the system-defaults minibuffer setup/exit hooks, replaced with gcmh (idle-delay 'auto, 1GB high threshold) — keeps the threshold high during activity, collects on idle. Verified via a clean throwaway-daemon launch (native-comp-jit t, gcmh-mode t, no backtrace) and a batch proof of gcmh's threshold cycle; applied live to the running daemon. Restart confirmation filed under Manual testing and validation. - ** VERIFY [#B] calendar-sync robustness: atomic writes, curl --fail, zero-event false errors :bug:solo:next: :PROPERTIES: :LAST_REVIEWED: 2026-06-20 @@ -196,20 +89,12 @@ From the 2026-06 config audit, =modules/transcription-config.el=: - =:210= — =make-process :stderr= with a file PATH creates a BUFFER named like the path (verified by probe); the "Errored. Logs in <file>" notification points at a log without the error text, and the hidden stderr buffer leaks per transcription. Route stderr into the process buffer or write it out in the sentinel. - =:370-374= — video path derives txt/log from the temp mp3's /tmp path; the transcript lands in /tmp and dies on reboot, contradicting the "alongside the source" docstring. Pass the video's path as the output base. -** DONE [#C] Dirvish: free D for hard-delete, move duplicate :feature:quick:next: -CLOSED: [2026-06-20 Sat] -Decided with Craig 2026-06-20: remove delete-to-trash entirely, bind =d= = =cj/dirvish-duplicate-file= and =D= = =cj/dirvish-hard-delete= (sudo rm -rf after a =yes-or-no-p= naming the exact targets). Built in =modules/dirvish-config.el= (=cj/--dirvish-hard-delete-command= pure builder + =cj/dirvish-hard-delete= command; keymap =d=/=D= swap). 4 ERT tests for the command builder; full suite green; live-reloaded into the daemon (=dirvish-mode-map= =d=/=D= rebinding confirmed). Manual keypress + sudo-flow check filed under Manual testing and validation. - ** VERIFY [#C] page-signal pager account deregistered — re-registration needs your hands :PROPERTIES: :LAST_REVIEWED: 2026-06-12 :END: Reported by .emacs.d 2026-06-12 01:01: the dedicated pager number (+15045173983, the Claude Pager Google Voice number on signal-cli) returns "User ... is not registered" on every send — Signal appears to have deregistered it (GV numbers get periodically re-verified). Re-registration requires captcha/SMS, which only you can do. Until then every page-signal call fails; .emacs.d's config-audit page fell back to email. Wrapper lives at claude-templates/bin/page-signal. -** DONE [#C] Pull a fullscreen terminal window away with C-; b + arrow :feature:next: -CLOSED: [2026-06-20 Sat] -Decided with Craig 2026-06-20: when the selected window is the sole window, =C-; b= + arrow keeps that window on the arrow's edge and slivers =other-buffer= in on the opposite side (=minimize-window=, so the current window keeps almost the whole frame), focus staying put; each further arrow then shrinks it step by step via =windsize=, reading the same as resizing an existing split. Generalizes to any sole window, not just terminals — resize was a no-op there before. Built in =modules/ui-navigation.el= (=cj/window-pull-side= pure mapping + =cj/window--pull-away= + a =one-window-p= branch in =cj/window-resize-sticky=). ERT tests for the mapping and both sticky paths; geometry verified in a headless frame (down -> terminal 37/40 at the bottom, reveal 2 lines slivered on top via window-min-height=1, windsize-down then steps it down); full suite green; live-reloaded into the daemon. Refined from a first cut that split toward the arrow and jumped to 50%, per Craig's feedback. Manual gesture check filed under Manual testing and validation. - ** VERIFY [#C] Remove unused system-power keybindings :refactor:quick:next: :PROPERTIES: :LAST_REVIEWED: 2026-06-20 @@ -232,6 +117,17 @@ Fixed 2026-06-13: cmail gets =mu4e-trash-folder= "/cmail/Trash"; refile is a per Fixed 2026-06-13: lockscreen-cmd resolves to =loginctl lock-session= on Wayland (logind Lock → hypridle → hyprlock, the path idle/sleep locking already uses), =slock= on X11; also added the missing =(require 'host-environment)=. Live in the daemon; manual lock test under the Manual testing parent. ** PROJECT [#A] Manual testing and validation Exercised once the phases above land. +*** VERIFY deferred game commands still work after a restart (load-graph Phase 4) +What we're verifying: with games-config no longer eagerly required, malyon and 2048-game still launch from a fresh Emacs, and games-config loads on first use rather than at startup. Batch tests cover the autoload chain; this is the interactive confirmation the spec asks for after each deferral batch. +- Restart Emacs (daemon or standalone) so games-config is no longer pre-loaded from this session. +- Confirm it's not loaded at startup: +#+begin_src emacs-lisp +(featurep 'games-config) +#+end_src +- M-x malyon — it should load games-config and the malyon package, then start interactive fiction (stories under ~/sync/org/text.games/). +- M-x 2048-game — should start the 2048 puzzle. +- Re-check (featurep 'games-config) — now non-nil. +Expected: at startup (featurep 'games-config) is nil; both commands launch normally; after invoking one, games-config is loaded. If a command errors instead of launching, capture it and reopen the deferral as a TODO. *** VERIFY native-comp + gcmh survive a daemon restart cleanly What we're verifying: re-enabling JIT native compilation and switching GC to gcmh holds up across a full daemon restart and a real work session. The fix is live in the current daemon and a throwaway daemon launched clean, but the already-loaded modules only get natively compiled on a fresh start (a background async burst), and the old "Selecting deleted buffer" race needs a real GUI session to rule out on 30.2. - Restart the Emacs daemon (clean state): kill it and start fresh, or reboot. @@ -916,7 +812,7 @@ No init.el load-order change — keybindings and the foundation modules already Verified each fix with a fresh =emacs --batch (require 'X)=, then swept all ~100 modules standalone: every one loads or fails only with a clear missing-package message (the spec's Phase 2 exit bar). Full =make test=, =make validate-modules=, and an init smoke all pass. Module headers and the inventory's hidden-dependency section updated to mark the seven resolved. -**** TODO [#A] Defer feature modules behind autoloads, hooks, and commands :refactor: +**** DOING [#A] Defer feature modules behind autoloads, hooks, and commands :refactor: Once dependencies are explicit, reduce the number of modules required at startup. Start with lower-risk feature modules: @@ -931,6 +827,9 @@ Do this incrementally. After each batch: - Run =make test= or at least targeted tests. - Check that keybindings still resolve and which-key labels still appear. +***** 2026-06-21 Sun @ 01:53:55 -0400 Deferred games-config (batch 1, module 1) +Replaced =(require 'games-config)= in init.el with explicit autoloads for =malyon= and =2048-game= → games-config; the module now loads on first game-command use instead of at startup. games-config.el: =:defer 1= → =:defer t :commands=, header Load shape eager→command. package.el already autoloads both commands, so routing through games-config only preserves the one setting it owns (=malyon-stories-directory=), applied via use-package =:config= when malyon loads. Verified the autoload→module→package→config chain in batch. Test: =tests/test-init-defer-games.el= (commands resolve with the module unloaded; config applies on load). Inventory row eager→command; header-contract 4/4 (still allowlisted), full =make test= green. Shipped as 03d8b587. Daemon keeps it loaded until restart — interactive restart smoke pending (see Manual testing). + **** 2026-05-24 Sun @ 19:59:01 -0500 Centralized custom keymap registration Added cj/register-prefix-map and cj/register-command to keybindings.el (commit 47f222f6) with test-init-keymap-registration.el, then migrated all 31 cj/custom-keymap registration sites across 24 modules onto the API. Consumers no longer reference cj/custom-keymap directly — keybindings.el is the sole owner of the prefix, and modules require keybindings to reach the API. @@ -1002,69 +901,6 @@ Add the buffer-local var, set it on each "Run a test..." selection, use it as th *** TODO [#B] TS/JS coverage status sync Update the =dev-fkeys.el= header comment (L33) — TS/JS is no longer punted; the cmd-builder at L384 emits vitest/jest. Document the prefer-vitest fallback. -** DONE [#B] Migrate All Terminals From Vterm to Ghostel -CLOSED: [2026-06-20 Sat 22:50] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-04 -:END: -Replace vterm with ghostel (libghostty-vt) as the single terminal engine across every workflow, and rename ai-vterm → ai-term. References: [[file:docs/2026-05-25-emacs-terminal-comparison.org][docs/2026-05-25-emacs-terminal-comparison.org]] (vterm vs eat vs ghostel research); migration spec [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghostel-migration-spec-implemented.org]] (READY; external review incorporated 2026-06-04, D1-D7 agreed). Build in 5 phases (0-4); see the spec's Implementation tasks block. - -Decisions D1-D7 are settled in the spec's Agreed-decisions section. Build order below; each phase stays green (suite + byte-compile) at every step. - -*** 2026-06-20 Sat @ 22:49:41 -0400 Follow-up: theme ghostel ANSI faces in dupre -D2 — set the 16 ghostel-color-* + ghostel-default faces in dupre-faces/palette. -Roam-inbox note (2026-06-14): theme-studio assignments don't reach ghostel — it paints from its own ANSI palette, not the theme. Also investigate ghostel's property-file color mechanism as an alternative and surface the options for working with that limitation. - -*** 2026-06-20 Sat @ 22:50:28 -0400 CANCELLED [#B] Follow-up: evaluate ghostel-eshell + ghostel-compile -CLOSED: [2026-06-20 Sat 22:49] -D3 — ghostel-eshell as eshell visual backend; ghostel-compile against F4 dev-fkeys. - -*** 2026-06-20 Sat @ 22:50:32 -0400 DONE [#B] Investigate ghostel selection/highlight color -CLOSED: [2026-06-20 Sat 22:50] -Look at how selected text is highlighted in a ghostel buffer — the region face in =ghostel-copy-mode= and any live selection — surfaced during the copy-mode debugging. Check whether the highlight is legible against the dupre background and consistent with the rest of the config; if it needs theming, fold it in with D2 (theming the ghostel faces in dupre). - -*** 2026-06-04 Thu @ 23:57:09 -0500 Phase 0 done: characterization baseline green -=make test= green except the 5 documented pre-existing failures (4 test-dupre-theme, 1 test-init-module-headers), none terminal-related. Characterization coverage already present + green for all six must-survive behaviors: vterm-toggle--dispatch/display/buffer-filter, vterm-tmux-history, ai-vterm--show-or-create/launch-command/f9-in-vterm, ui-config--buffer-cursor-state + vterm-copy-mode-cursor, dashboard-config-launchers. Add a characterization test before any behavior change in later phases if a gap appears. - -*** 2026-06-05 Fri @ 00:38:34 -0500 Phase 1 done: ghostel + term-config.el -=modules/term-config.el= written (full port of vterm-config: tmux history/copy-mode-dwim preserved via process-tty-name + ghostel-send-string; F12 toggle + display rule + geometry; cj/term-map C-; x menu → ghostel commands; which-key "terminal menu"; ghostel-max-scrollback 10MB; C-; added to ghostel-keymap-exceptions; F12 + C-; in ghostel-mode-map; use-package ghostel guarded per D6). Dropped: mouse-wheel SGR forwarding, vterm-timer-delay hacks, copy-mode cursor hook, goto-address hook. ghostel installed into elpa (MELPA + auto-downloaded native module). Tests: test-term-toggle--{dispatch,display,buffer-filter} + test-term-tmux-history (16) ported with a ghostel stub in testutil-ghostel-buffers; all green. - -*** 2026-06-05 Fri @ 00:38:34 -0500 Phase 2 done: ai-vterm→ai-term on ghostel -=modules/ai-vterm.el= → =modules/ai-term.el=: 6 vterm call sites swapped to ghostel (buffer named via let-bound ghostel-buffer-name + pinned ghostel-buffer-name-function so OSC titles don't rename agent buffers); F9/C-F9/M-F9 on global + ghostel-mode-map; refuse-in-terminal guard removed (D4 — F9 launches in TTY frames); tmux-suppression invariant preserved (cj/--ai-term-suppress-tmux). 23 ai-vterm tests renamed → test-ai-term--* (terminal-guard test deleted, obsolete); show-or-create + f9-in-term rewritten for ghostel; all green. ui-config cursor-state ported (ghostel-mode + ghostel--input-mode; copy/emacs = read-only, else writeable) + its test. init.el now requires term-config + ai-term; vterm-config.el + ai-vterm.el deleted. Full suite green except the 5 documented pre-existing failures (4 dupre-theme, 1 init-module-headers/popper-config-missing — both unrelated). validate-modules ✓; full early-init+init smoke clean (no ghostel/term/ai-term errors). vterm package still installed (Phase 4) — dashboard "Launch VTerm" + dormant auto-dim still reference it until Phase 3/4. Restart Emacs to pick up ghostel (load-order + use-package :config change). - -*** 2026-06-05 Fri @ 00:50:58 -0500 Phase 3 done: satellites ported to ghostel -Deleted auto-dim's vterm color-advice + redraw integration (~165 lines; D1 — terminals don't dim, ghostel bakes its palette per-terminal so there's no per-window color hook); dashboard launcher → =(ghostel)= + "Launch Terminal" label; cj-window-geometry/toggle-lib doc comments; module-inventory + init-load-graph doc refs. (ui-config cursor-state + init.el requires landed in Phase 2.) Trimmed test-auto-dim-config (dropped the 6 vterm tests) + updated the dashboard-launcher test stub. Incidental: removed the stale =popper-config= entry from the test-init-module-headers allowlist (the file doesn't exist + isn't required) — fixes the long-standing pre-existing test failure. - -*** 2026-06-05 Fri @ 00:50:58 -0500 Phase 4 done: vterm + vterm-toggle removed -=package-delete='d vterm + vterm-toggle from elpa. No vterm refs remain in modules/init except intentional historical comments. Suite green except the 4 pre-existing dupre-theme failures (the popper-config one is now fixed). validate-modules ✓; full early-init+init batch smoke = INIT-SMOKE-OK. The migration parent stays DOING until Craig restarts Emacs and walks the ghostel manual-verify matrix under "Emacs Manual Testing and Validation". - -*** 2026-06-05 Fri @ 14:24:02 -0500 Auto-dim revisit cancelled — current no-dim behavior is fine -Craig confirmed the shipped auto-dim setup works fine as-is: terminal buffers don't participate in unfocused-window dimming (D1), and the rest of auto-dim behaves. That is the measured decision the original task asked for — option (a), keep no-dim — so no rework (the focus-loss palette-blend in option (b) or an upstream per-window hook in option (c)) is needed. Closing without further investigation. Context: [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][migration spec]] D1. - -*** 2026-05-26 Tue @ 15:15:43 -0500 Direction confirmed; Claude Code in eat needs a caveat -Craig confirmed the consolidation: one terminal engine everywhere — eat for standalone terminal buffers (replacing vterm) plus =eat-eshell-mode= as eshell's visual backend, keeping eshell as the shell. Not dropping eshell for eat + zsh. - -Researched whether Claude Code runs cleanly in eat (Craig runs it in his Emacs terminal). Verdict: mostly, with caveats. eat is the default backend for claude-code.el and renders the TUI with color and full key handling, but there is an eat-specific bug where Claude Code's input handling makes the buffer scroll-pop to the top on window-buffer changes and the input box can get stuck mid-buffer (recoverable, but it does not happen in vterm or ghostel), and eat runs about 1.5x slower than vterm on heavy streaming output. claude-code.el's own docs name ghostel as the most faithful Claude TUI renderer. - -Recommendation: consolidate everyday terminals onto eat, but keep ghostel (or vterm) for the Claude Code workflow specifically — the scroll-pop / stuck-input bug and the slower heavy-stream handling are exactly what bites a long Claude session. Sources: [[https://github.com/cpoile/claudemacs][claudemacs]], [[https://github.com/stevemolitor/claude-code.el][claude-code.el]], [[https://codeberg.org/akib/emacs-eat][emacs-eat]]. - -Eval plan (from the research doc): install EAT alongside vterm, run the same workloads through both, decide. Test matrix: Claude Code TUI, lazygit, htop/btop, yazi, a heavy-output build, ssh to a remote, and eshell with =eat-eshell-mode=. Assess rendering fidelity, stability under heavy output, and Emacs-native line editing. Switch only if it covers every workflow without regression. - -*** 2026-06-02 Tue @ 14:12:48 -0500 Audit: eval plan not yet run; back to TODO -Task audit found no eval work recorded since the 2026-05-26 direction-confirmed note. The test matrix above is unrun, so the task isn't actively in progress — moved DOING back to TODO until the eval starts. - -*** 2026-06-04 Thu @ 22:40:27 -0500 Pivot: ghostel as the single engine (not eat) -Direction changed from eat-everyday + ghostel-for-Claude to ghostel-for-everything, and the task is now a migration rather than an eval. Rationale: ghostel is claude-code.el's most-faithful Claude TUI renderer and the fastest engine (81 vs vterm 34 vs eat 4.9 MB/s), and an audit confirmed it exposes an analog for every vterm primitive this config uses (=ghostel-send-string=, =ghostel-keymap-exceptions=, =ghostel-copy-mode=, =ghostel-clear-scrollback=, =ghostel-send-next-key=, =ghostel-next-prompt= / =ghostel-previous-prompt=, =ghostel-max-scrollback=, =ghostel-kill-buffer-on-exit=). eat's washed colors, the scroll-pop / stuck-input bug under Claude Code, and slowest throughput made it the weaker single-engine pick; one engine beats running two. Surface audited: 2 main modules (=vterm-config.el=, =ai-vterm.el=) + 4 satellites (=auto-dim-config.el= is the heavy one) + ~35 test files + init.el. Next: spike ghostel read-only to answer the open migration questions (auto-dim rework — ARCHITECTURE.md forbids the around-redraw color advice vterm uses; tmux pane-id via =process-tty-name= on a ghostel process; buffer naming; TTY-frame behavior; copy-mode keybinding parity), then write the migration spec under =docs/design/= and review it. - -*** 2026-06-04 Thu @ 23:17:54 -0500 Spec review: not ready until decisions and handoff shape are closed -Ran the spec-review workflow against [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghostel-migration-spec-implemented.org]] and wrote a companion review file (incorporated and deleted 2026-06-04). Verdict: =Not ready=. Direction is sound, but the draft still has open D1-D5 decisions, lacks the workflow-required =Implementation phases= section and acceptance criteria, and needs explicit ghostel package/native-module failure behavior before implementation tasks can be emitted. - -*** 2026-06-04 Thu @ 23:24:28 -0500 Spec-response: review incorporated, raised to READY -Folded the external review via spec-response. Craig accepted D1-D5; baked them plus D6 (module-failure = degrade-with-warning, modifying the reviewer's fail-loud) and D7 (=ghostel-max-scrollback= 10 MB) into a new Agreed-decisions section. Added Implementation phases (0-4), Acceptance criteria, Dependency/module-failure behavior, Test strategy, per-phase key/menu ownership, the tmux-suppression contract, and an Implementation-tasks drop-in block. Status DRAFT → READY; review file deleted. Build is now unblocked. - -*** 2026-06-04 Thu @ 23:30:18 -0500 External re-review: ready -Re-reviewed [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghostel-migration-spec-implemented.org]] after incorporation. Verdict: =Ready=. No further blocking review notes; implementation can start from the phase plan and acceptance criteria in the spec. - ** PROJECT [#B] Module-by-module hardening :PROPERTIES: :LAST_REVIEWED: 2026-06-05 @@ -3617,16 +3453,24 @@ Ask: Reference values -- modus-vivendi: refine-changed bg #4a4a00 fg #efef80, changed bg #363300 fg #efef80. modus-operandi: refine-changed bg #fac090 fg #553d00, changed bg #ffdfa9 fg #553d00. Side-by-side legibility render: [[file:assets/2026-06-07-dupre-diff-face-legibility-compare.png][assets/2026-06-07-dupre-diff-face-legibility-compare.png]]. -** DONE [#A] erc-yank silently publishes >5-line pastes as public gists :bug:quick:solo: -CLOSED: [2026-06-20 Sat] -Dropped erc-yank 2026-06-20 (Craig's call: drop, not harden). The package turned a >5-line paste into a PUBLIC gist (=gist -P=, the clipboard-paste flag, no =--private=) behind a single y-or-n-p, with no executable-find guard for =gist=. It also gisted the system clipboard rather than the kill-ring text being yanked. No replacement binding needed: =erc-mode-map= defines no C-y of its own, so removing the package lets C-y fall through to the ordinary global =yank=. Verified live: effective C-y in an ERC buffer = =yank=. (Audit's "no confirmation" was slightly off — the package did prompt — but public-by-default + one-keystroke confirm + no guard made dropping it the clean fix.) - ** TODO [#B] F7 diff-aware coverage classifies every changed file "not tracked" :bug:solo: :PROPERTIES: :LAST_REVIEWED: 2026-06-20 :END: =modules/coverage-core.el:252= — =cj/--coverage-intersect= joins covered×changed by exact string key, but simplecov.json keys are ABSOLUTE paths while the git-diff parser returns repo-RELATIVE ones — zero matches ever, so working-tree/staged/branch scopes report ":tracked nil" for everything and F7's main feature is inert (whole-project scope works, same-source keys). Unit tests hand-build matching keys so they pass; add one integration test feeding a real undercover report + real diff. Normalize both sides to repo-relative. From the 2026-06 config audit. +** TODO [#C] Migrate tests off mocking primitives (native-comp robustness) :test:refactor: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-21 +:END: +Long-term test-quality work surfaced by re-enabling native-comp (2026-06-20). When a test redefines a C primitive or a native-compiled function (=cl-letf=/=fset=/=advice-add=), native-comp routes native callers through a trampoline, which interacts badly with mocks in three ways: trampoline-build failure under =--batch=, silent mock-bypass (native callers ignore the redefinition), and arity mismatch (the trampoline calls the mock with the primitive's max arity). + +Done 2026-06-21 (the immediate fix): swept every arity-narrow subr mock to =(lambda (&rest _) ...)= (188 sites) and added =tests/test-meta-subr-mock-arity.el=, which fails =make test= on any arity-narrow subr mock. That kills the arity mode (the only one we've hit) and enforces it going forward. + +This task is the durable fix the ecosystem and =elisp-testing.md= point to: restructure tests so they don't redefine primitives at all — inject dependencies, drive real state (temp-file fixtures, real buffers), or test pure helpers. That closes the two latent modes (build-failure, silent-bypass) the variadic sweep leaves open. Big, incremental, low-urgency. + +Full mechanism, the three failure modes, the research (Emacs bug#51140, bug#61880, buttercup #230, Debian #1021842, the emacs-29 redefine-primitive warning, the manual on =native-comp-enable-subr-trampolines=), and the decision: [[file:docs/native-comp-subr-mocking.org][docs/native-comp-subr-mocking.org]]. + ** TODO [#B] Fix up test runner :bug: :PROPERTIES: :LAST_REVIEWED: 2026-06-06 @@ -8750,3 +8594,178 @@ the agent returns to its own window and the others are untouched. The flag only window case (2-window slot-reuse unchanged). TDD regression =test-ai-term--reuse-edge-window-3win-toggle-restores-own-window=; full =make test= green; live-reloaded. Commit 64916462. GUI sign-off is a VERIFY under Manual testing and validation. +** DONE [#B] Codebase refactoring program — remaining batch :refactor:solo: +CLOSED: [2026-06-20 Sat] +Complete 2026-06-20: all 13 scan findings addressed across the day's sessions (see +=.ai/sessions/= for the logs). 5 medium extractions + 2 big single-file refactors + +6 theme-studio items including the browser-gates harness rewrite. The only item not +done is the item-8 plan() factory, consciously skipped as premature abstraction +(heterogeneous call sites — see "Remaining — item-8 plan() factory" below). +The original scan: full-codebase 8-agent fan-out over modules/ + scripts/theme-studio/, +one focused refactor per commit, won't-do items excluded. + +*** Working protocol (apply to every item) +- TDD: write/keep a failing-then-green test; harvest new test seams the refactor opens. +- Behavior-preserving only. If a "dedup" would delete a real test seam or couple + dissimilar code, SKIP it and record why (see skips below). +- Per refactor, verify in this order, then commit + push (no-approvals mode): + 1. =make test-file FILE=<basename.el>= for touched + new tests. + 2. =make validate-modules= (loads all 123 modules; catches load/paren errors). + 3. Init-launch smoke on a throwaway daemon: =emacs --daemon=cj-sNN=, then + =emacsclient -s cj-sNN -e '(emacs-pid)'= to capture the PID, check + =(length features)= = 807 and no init errors in the log, then kill by that + PID (the emacsclient kill-emacs is flaky; pkill -f 'daemon=cj-sNN' + self-matches its own shell — kill the captured PID). + 4. Live-reload the edited module into Craig's running daemon + (=emacsclient -e '(load "/home/cjennings/.emacs.d/modules/<m>.el")'=); skip + the live reload for big use-package modules whose :config restacks (verify via + the fresh smoke daemon instead, as with mail-config). +- Tab-heavy files: =sed -n 'A,Bp' FILE | cat -A= to get exact bytes before an Edit; + write NEW code in the documented 2-space style. +- Shared asset already created: =cj/format-region-with-program= in system-lib.el + (the run-a-formatter-over-the-buffer helper). Reuse it for any further + format-region duplicates. + +*** DONE — medium extractions (2026-06-20 afternoon) +All five shipped: calibredb-epub nov re-render/centering helpers (fccf29b0); +ai-term toggle-off teardown + working-buffer swap (62fee96b); calendar-sync +per-event exception parser (23f405b4); dirvish playlist-target resolution +(a1ca2fb0); custom-case per-word title-case decision (4cc9ca0b). + +*** DONE — big single-file + theme-studio (2026-06-20 afternoon, no-approvals run) +Both big single-file items shipped: dwim-shell branching command builders +(f93b4615); custom-comments divider/box generator dedup (42f0c88a). Five of the +six theme-studio items shipped: face_coverage path_kind (9a52370b), +capture-default-faces condition_matches unify (28b4d1cf), dropdownRowTextColor +delete (10a56789), test-file inline-integrity dedup — subTest loop + shared +inline-strip.mjs (13969c70), generate.py lazy _build()/__getattr__ (6df4ebdc), +browser-gates assertPreviewFaces for the 3 preview gates (5627f137). + +*** DONE — browser-gates harness rewrite (with Craig's go-ahead, 2026-06-20) +- =gate(id, body)= helper (05697e83): the 38 standard gates' ok/notes/A + title + + result-div boilerplate, note format standardized to " fails=". Each call site keeps + its literal =location.hash==='#NAMEtest'=. 6 custom gates stay inline. First automated + attempt deleted gates (a closing-finder spanned boundaries) — caught by a gate-count + guard, reverted, redone anchored on each gate's unique =d.id=. Verified all 44 green + + a forced A(false) in a converted gate still FAILs. +- =withSavedState(keys, body)= (a473aa7c): wraps the 7 restore-nothing gates, scoped to + the globals each mutates; JSON-clone snapshot + finally-restore (structuredClone threw + on the studio objects — caught by the gate run as "no verdict", switched to JSON like + the gates' own local saves). The 14 self-restoring gates left as-is. Verified 44 green, + restore round-trip holds, broken assertion in a wrapped gate still FAILs. + +*** Remaining — item-8 plan() factory (deferred, low value) +The =plan(overrides)= factory for the ~30 planPaletteGenerator calls (test-app-core.mjs ++ test-palette-generator-core.mjs) was deferred. The calls pass heterogeneous options +(scheme/accentCount/sourceMode/vibe/intent vary per call); a factory only dedups the +constant spanCount:0/rng and would hide which options each test actually exercises — +premature abstraction over varying calls. The other two item-8 parts (subTest loop + +shared stripExports) shipped in 13969c70. + +*** WON'T-DO (do not re-attempt — assessed and rejected) +- theme-studio buildTable/buildUITable/buildPkgTable merge: genuine per-tier divergence + (column order, syntax dual fg/bg dropdowns, ui preview cell, pkg nd markers) + the + =.cells[N]= positional sort coupling make a unified builder MORE complex than the + three explicit ones. Close as won't-do. +- Cross-language test overlap (browser-gates preview gate vs test_generate.py + PackageFaceCoverage): don't merge — would couple a fast Python test to a headless + browser run. A one-line comment in each noting the split is the most that's worth it. + +*** Skipped this run (with reasons — don't redo) +- eshell-config ssh-alias "merge the two helpers": =cj/--eshell-ssh-alias-commands= is + a deliberate pure/effectful split with 3 dedicated tests; merging deletes the seam. +- prog-*-setup boilerplate: only python+webdev share the full pattern; shell/c/elisp/ + common-lisp differ materially. A keyword-arg helper would be less readable. No + premature abstraction. +- erc join-command =cj/erc--ensure-active-connection= extraction: nesting-only on + untestable UI (call-interactively/switch-to-buffer), no test seam, risky tab-rewrite. +- coverage-core =simplecov-executable-lines= vs =parse-simplecov= clone: borderline + MEDIUM, differs only by a =(> hits 0)= predicate; parameterize with a keep-line-p + only if revisiting. Low priority. +** CANCELLED [#A] calendar-sync drops final occurrences, resurrects cancelled meetings :bug:solo:next: +CLOSED: [2026-06-20 Sat 22:51] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-13 +:END: +Needs from Craig: a real .ics fixture (or two) that reproduces both symptoms — a recurring event missing its final occurrence, and a cancelled meeting that reappears. This is RFC-5545 recurrence handling (RRULE/UNTIL/EXDATE/STATUS:CANCELLED); I won't guess-patch the parser without a failing case to test against. Drop a sanitized .ics and I'll write the characterization test + fix. +RFC 5545 conformance holes in =modules/calendar-sync.el=, all agenda-visible (from the 2026-06 config audit): +- =:973,1015,1024= — UNTIL treated as exclusive (strict =calendar-sync--before-date-p=); RFC and Google make it inclusive, so the LAST instance of every UNTIL-bounded series vanishes. Tests assert loose count ranges, so it's unpinned. Allow equality. +- =:578= — comma-separated EXDATE lists (Google emits them) never parse; the exclusion drops silently and cancelled occurrences reappear on the agenda. Split on "," before parsing; no comma-case test exists. +- =:902= — timed events without DTEND render as all-day (time lost); multi-day all-day spans collapse to one day (end date unused, exclusive-DTEND unhandled). Emit start-time-only stamps and org date ranges. +----- + +2026-06-20 Sat @ 22:52:51 -0400 Can't reproduce. closing +** DONE [#A] Native compilation disabled config-wide; GC at stock 800KB :bug:next: +CLOSED: [2026-06-20 Sat] +Both fixed 2026-06-20. =early-init.el:69= was =(setq native-comp-deferred-compilation nil)= — the obsolete alias of =native-comp-jit-compilation= — which turned JIT native-comp OFF entirely (not "synchronous"); replaced with =(setq native-comp-jit-compilation t)= + =native-comp-async-report-warnings-errors 'silent=. The old "Selecting deleted buffer" async race was an Emacs 28/29 issue; this is 30.2. GC: dropped the early-init post-startup restore to stock 800KB and the system-defaults minibuffer setup/exit hooks, replaced with gcmh (idle-delay 'auto, 1GB high threshold) — keeps the threshold high during activity, collects on idle. Verified via a clean throwaway-daemon launch (native-comp-jit t, gcmh-mode t, no backtrace) and a batch proof of gcmh's threshold cycle; applied live to the running daemon. Restart confirmation filed under Manual testing and validation. +** DONE [#C] Dirvish: free D for hard-delete, move duplicate :feature:quick:next: +CLOSED: [2026-06-20 Sat] +Decided with Craig 2026-06-20: remove delete-to-trash entirely, bind =d= = =cj/dirvish-duplicate-file= and =D= = =cj/dirvish-hard-delete= (sudo rm -rf after a =yes-or-no-p= naming the exact targets). Built in =modules/dirvish-config.el= (=cj/--dirvish-hard-delete-command= pure builder + =cj/dirvish-hard-delete= command; keymap =d=/=D= swap). 4 ERT tests for the command builder; full suite green; live-reloaded into the daemon (=dirvish-mode-map= =d=/=D= rebinding confirmed). Manual keypress + sudo-flow check filed under Manual testing and validation. +** DONE [#C] Pull a fullscreen terminal window away with C-; b + arrow :feature:next: +CLOSED: [2026-06-20 Sat] +Decided with Craig 2026-06-20: when the selected window is the sole window, =C-; b= + arrow keeps that window on the arrow's edge and slivers =other-buffer= in on the opposite side (=minimize-window=, so the current window keeps almost the whole frame), focus staying put; each further arrow then shrinks it step by step via =windsize=, reading the same as resizing an existing split. Generalizes to any sole window, not just terminals — resize was a no-op there before. Built in =modules/ui-navigation.el= (=cj/window-pull-side= pure mapping + =cj/window--pull-away= + a =one-window-p= branch in =cj/window-resize-sticky=). ERT tests for the mapping and both sticky paths; geometry verified in a headless frame (down -> terminal 37/40 at the bottom, reveal 2 lines slivered on top via window-min-height=1, windsize-down then steps it down); full suite green; live-reloaded into the daemon. Refined from a first cut that split toward the arrow and jumped to 50%, per Craig's feedback. Manual gesture check filed under Manual testing and validation. +** DONE [#B] Migrate All Terminals From Vterm to Ghostel +CLOSED: [2026-06-20 Sat 22:50] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-04 +:END: +Replace vterm with ghostel (libghostty-vt) as the single terminal engine across every workflow, and rename ai-vterm → ai-term. References: [[file:docs/2026-05-25-emacs-terminal-comparison.org][docs/2026-05-25-emacs-terminal-comparison.org]] (vterm vs eat vs ghostel research); migration spec [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghostel-migration-spec-implemented.org]] (READY; external review incorporated 2026-06-04, D1-D7 agreed). Build in 5 phases (0-4); see the spec's Implementation tasks block. + +Decisions D1-D7 are settled in the spec's Agreed-decisions section. Build order below; each phase stays green (suite + byte-compile) at every step. + +*** 2026-06-20 Sat @ 22:49:41 -0400 Follow-up: theme ghostel ANSI faces in dupre +D2 — set the 16 ghostel-color-* + ghostel-default faces in dupre-faces/palette. +Roam-inbox note (2026-06-14): theme-studio assignments don't reach ghostel — it paints from its own ANSI palette, not the theme. Also investigate ghostel's property-file color mechanism as an alternative and surface the options for working with that limitation. + +*** 2026-06-20 Sat @ 22:50:28 -0400 CANCELLED [#B] Follow-up: evaluate ghostel-eshell + ghostel-compile +CLOSED: [2026-06-20 Sat 22:49] +D3 — ghostel-eshell as eshell visual backend; ghostel-compile against F4 dev-fkeys. + +*** 2026-06-20 Sat @ 22:50:32 -0400 DONE [#B] Investigate ghostel selection/highlight color +CLOSED: [2026-06-20 Sat 22:50] +Look at how selected text is highlighted in a ghostel buffer — the region face in =ghostel-copy-mode= and any live selection — surfaced during the copy-mode debugging. Check whether the highlight is legible against the dupre background and consistent with the rest of the config; if it needs theming, fold it in with D2 (theming the ghostel faces in dupre). + +*** 2026-06-04 Thu @ 23:57:09 -0500 Phase 0 done: characterization baseline green +=make test= green except the 5 documented pre-existing failures (4 test-dupre-theme, 1 test-init-module-headers), none terminal-related. Characterization coverage already present + green for all six must-survive behaviors: vterm-toggle--dispatch/display/buffer-filter, vterm-tmux-history, ai-vterm--show-or-create/launch-command/f9-in-vterm, ui-config--buffer-cursor-state + vterm-copy-mode-cursor, dashboard-config-launchers. Add a characterization test before any behavior change in later phases if a gap appears. + +*** 2026-06-05 Fri @ 00:38:34 -0500 Phase 1 done: ghostel + term-config.el +=modules/term-config.el= written (full port of vterm-config: tmux history/copy-mode-dwim preserved via process-tty-name + ghostel-send-string; F12 toggle + display rule + geometry; cj/term-map C-; x menu → ghostel commands; which-key "terminal menu"; ghostel-max-scrollback 10MB; C-; added to ghostel-keymap-exceptions; F12 + C-; in ghostel-mode-map; use-package ghostel guarded per D6). Dropped: mouse-wheel SGR forwarding, vterm-timer-delay hacks, copy-mode cursor hook, goto-address hook. ghostel installed into elpa (MELPA + auto-downloaded native module). Tests: test-term-toggle--{dispatch,display,buffer-filter} + test-term-tmux-history (16) ported with a ghostel stub in testutil-ghostel-buffers; all green. + +*** 2026-06-05 Fri @ 00:38:34 -0500 Phase 2 done: ai-vterm→ai-term on ghostel +=modules/ai-vterm.el= → =modules/ai-term.el=: 6 vterm call sites swapped to ghostel (buffer named via let-bound ghostel-buffer-name + pinned ghostel-buffer-name-function so OSC titles don't rename agent buffers); F9/C-F9/M-F9 on global + ghostel-mode-map; refuse-in-terminal guard removed (D4 — F9 launches in TTY frames); tmux-suppression invariant preserved (cj/--ai-term-suppress-tmux). 23 ai-vterm tests renamed → test-ai-term--* (terminal-guard test deleted, obsolete); show-or-create + f9-in-term rewritten for ghostel; all green. ui-config cursor-state ported (ghostel-mode + ghostel--input-mode; copy/emacs = read-only, else writeable) + its test. init.el now requires term-config + ai-term; vterm-config.el + ai-vterm.el deleted. Full suite green except the 5 documented pre-existing failures (4 dupre-theme, 1 init-module-headers/popper-config-missing — both unrelated). validate-modules ✓; full early-init+init smoke clean (no ghostel/term/ai-term errors). vterm package still installed (Phase 4) — dashboard "Launch VTerm" + dormant auto-dim still reference it until Phase 3/4. Restart Emacs to pick up ghostel (load-order + use-package :config change). + +*** 2026-06-05 Fri @ 00:50:58 -0500 Phase 3 done: satellites ported to ghostel +Deleted auto-dim's vterm color-advice + redraw integration (~165 lines; D1 — terminals don't dim, ghostel bakes its palette per-terminal so there's no per-window color hook); dashboard launcher → =(ghostel)= + "Launch Terminal" label; cj-window-geometry/toggle-lib doc comments; module-inventory + init-load-graph doc refs. (ui-config cursor-state + init.el requires landed in Phase 2.) Trimmed test-auto-dim-config (dropped the 6 vterm tests) + updated the dashboard-launcher test stub. Incidental: removed the stale =popper-config= entry from the test-init-module-headers allowlist (the file doesn't exist + isn't required) — fixes the long-standing pre-existing test failure. + +*** 2026-06-05 Fri @ 00:50:58 -0500 Phase 4 done: vterm + vterm-toggle removed +=package-delete='d vterm + vterm-toggle from elpa. No vterm refs remain in modules/init except intentional historical comments. Suite green except the 4 pre-existing dupre-theme failures (the popper-config one is now fixed). validate-modules ✓; full early-init+init batch smoke = INIT-SMOKE-OK. The migration parent stays DOING until Craig restarts Emacs and walks the ghostel manual-verify matrix under "Emacs Manual Testing and Validation". + +*** 2026-06-05 Fri @ 14:24:02 -0500 Auto-dim revisit cancelled — current no-dim behavior is fine +Craig confirmed the shipped auto-dim setup works fine as-is: terminal buffers don't participate in unfocused-window dimming (D1), and the rest of auto-dim behaves. That is the measured decision the original task asked for — option (a), keep no-dim — so no rework (the focus-loss palette-blend in option (b) or an upstream per-window hook in option (c)) is needed. Closing without further investigation. Context: [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][migration spec]] D1. + +*** 2026-05-26 Tue @ 15:15:43 -0500 Direction confirmed; Claude Code in eat needs a caveat +Craig confirmed the consolidation: one terminal engine everywhere — eat for standalone terminal buffers (replacing vterm) plus =eat-eshell-mode= as eshell's visual backend, keeping eshell as the shell. Not dropping eshell for eat + zsh. + +Researched whether Claude Code runs cleanly in eat (Craig runs it in his Emacs terminal). Verdict: mostly, with caveats. eat is the default backend for claude-code.el and renders the TUI with color and full key handling, but there is an eat-specific bug where Claude Code's input handling makes the buffer scroll-pop to the top on window-buffer changes and the input box can get stuck mid-buffer (recoverable, but it does not happen in vterm or ghostel), and eat runs about 1.5x slower than vterm on heavy streaming output. claude-code.el's own docs name ghostel as the most faithful Claude TUI renderer. + +Recommendation: consolidate everyday terminals onto eat, but keep ghostel (or vterm) for the Claude Code workflow specifically — the scroll-pop / stuck-input bug and the slower heavy-stream handling are exactly what bites a long Claude session. Sources: [[https://github.com/cpoile/claudemacs][claudemacs]], [[https://github.com/stevemolitor/claude-code.el][claude-code.el]], [[https://codeberg.org/akib/emacs-eat][emacs-eat]]. + +Eval plan (from the research doc): install EAT alongside vterm, run the same workloads through both, decide. Test matrix: Claude Code TUI, lazygit, htop/btop, yazi, a heavy-output build, ssh to a remote, and eshell with =eat-eshell-mode=. Assess rendering fidelity, stability under heavy output, and Emacs-native line editing. Switch only if it covers every workflow without regression. + +*** 2026-06-02 Tue @ 14:12:48 -0500 Audit: eval plan not yet run; back to TODO +Task audit found no eval work recorded since the 2026-05-26 direction-confirmed note. The test matrix above is unrun, so the task isn't actively in progress — moved DOING back to TODO until the eval starts. + +*** 2026-06-04 Thu @ 22:40:27 -0500 Pivot: ghostel as the single engine (not eat) +Direction changed from eat-everyday + ghostel-for-Claude to ghostel-for-everything, and the task is now a migration rather than an eval. Rationale: ghostel is claude-code.el's most-faithful Claude TUI renderer and the fastest engine (81 vs vterm 34 vs eat 4.9 MB/s), and an audit confirmed it exposes an analog for every vterm primitive this config uses (=ghostel-send-string=, =ghostel-keymap-exceptions=, =ghostel-copy-mode=, =ghostel-clear-scrollback=, =ghostel-send-next-key=, =ghostel-next-prompt= / =ghostel-previous-prompt=, =ghostel-max-scrollback=, =ghostel-kill-buffer-on-exit=). eat's washed colors, the scroll-pop / stuck-input bug under Claude Code, and slowest throughput made it the weaker single-engine pick; one engine beats running two. Surface audited: 2 main modules (=vterm-config.el=, =ai-vterm.el=) + 4 satellites (=auto-dim-config.el= is the heavy one) + ~35 test files + init.el. Next: spike ghostel read-only to answer the open migration questions (auto-dim rework — ARCHITECTURE.md forbids the around-redraw color advice vterm uses; tmux pane-id via =process-tty-name= on a ghostel process; buffer naming; TTY-frame behavior; copy-mode keybinding parity), then write the migration spec under =docs/design/= and review it. + +*** 2026-06-04 Thu @ 23:17:54 -0500 Spec review: not ready until decisions and handoff shape are closed +Ran the spec-review workflow against [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghostel-migration-spec-implemented.org]] and wrote a companion review file (incorporated and deleted 2026-06-04). Verdict: =Not ready=. Direction is sound, but the draft still has open D1-D5 decisions, lacks the workflow-required =Implementation phases= section and acceptance criteria, and needs explicit ghostel package/native-module failure behavior before implementation tasks can be emitted. + +*** 2026-06-04 Thu @ 23:24:28 -0500 Spec-response: review incorporated, raised to READY +Folded the external review via spec-response. Craig accepted D1-D5; baked them plus D6 (module-failure = degrade-with-warning, modifying the reviewer's fail-loud) and D7 (=ghostel-max-scrollback= 10 MB) into a new Agreed-decisions section. Added Implementation phases (0-4), Acceptance criteria, Dependency/module-failure behavior, Test strategy, per-phase key/menu ownership, the tmux-suppression contract, and an Implementation-tasks drop-in block. Status DRAFT → READY; review file deleted. Build is now unblocked. + +*** 2026-06-04 Thu @ 23:30:18 -0500 External re-review: ready +Re-reviewed [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghostel-migration-spec-implemented.org]] after incorporation. Verdict: =Ready=. No further blocking review notes; implementation can start from the phase plan and acceptance criteria in the spec. +** DONE [#A] erc-yank silently publishes >5-line pastes as public gists :bug:quick:solo: +CLOSED: [2026-06-20 Sat] +Dropped erc-yank 2026-06-20 (Craig's call: drop, not harden). The package turned a >5-line paste into a PUBLIC gist (=gist -P=, the clipboard-paste flag, no =--private=) behind a single y-or-n-p, with no executable-find guard for =gist=. It also gisted the system clipboard rather than the kill-ring text being yanked. No replacement binding needed: =erc-mode-map= defines no C-y of its own, so removing the package lets C-y fall through to the ordinary global =yank=. Verified live: effective C-y in an ERC buffer = =yank=. (Audit's "no confirmation" was slightly off — the package did prompt — but public-by-default + one-keystroke confirm + no guard made dropping it the clean fix.) |
