<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/modules/dirvish-config.el, branch main</title>
<subtitle>My Emacs configuration
</subtitle>
<id>https://git.cjennings.net/dotemacs/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/dotemacs/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/'/>
<updated>2026-06-03T01:38:06+00:00</updated>
<entry>
<title>feat(ui): name the operation in completing-read prompts</title>
<updated>2026-06-03T01:38:06+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-03T01:38:06+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=13b053c2a99d30c1131d920a62febde6ee9a628b'/>
<id>urn:sha1:13b053c2a99d30c1131d920a62febde6ee9a628b</id>
<content type='text'>
A picker prompt is the last thing shown before a command commits, so a bare noun leaves a mis-keyed command ambiguous. Hitting C-f8 (project agenda) instead of C-f9 (AI-vterm picker) gave the same "Project:" prompt with no signal which one was about to run.

Reworded 17 prompts across 8 modules so each names the operation rather than just the thing being chosen: "Project:" becomes "Show agenda for project:", "F6:" becomes "Run tests:", the dwim-shell sub-prompts gain their context (checksum algorithm, PDF compression quality, text-to-speech voice, run dwim-shell command), the two contact pickers split into "Find contact:" and "Insert contact email:", and the dirvish ediff, org finalize, and custom-comments length/box-style prompts get the same treatment.

I audited all ~124 completing-read / read-* call sites; the rest already named their operation and were left alone. These are prompt-string changes only, no logic touched.
</content>
</entry>
<entry>
<title>docs(load-graph): classify domain, integration, and optional modules</title>
<updated>2026-05-24T21:57:56+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-24T21:57:56+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=cad351ec00c3f78cfb6e203d87c7309a620e485c'/>
<id>urn:sha1:cad351ec00c3f78cfb6e203d87c7309a620e485c</id>
<content type='text'>
Eighth classification batch: 17 domain/integration/optional modules — ai-config, ai-vterm, browser-config, calendar-sync, calibredb-epub-config, chrono-tools, dirvish-config, dwim-shell-config, erc-config, eshell-config, eww-config, flyspell-and-abbrev, games-config, gloss-config, httpd-config, jumper, latex-config. I annotated each header, added a Batch 8 table to the inventory, and extended the validation allowlist. 82 of 102 modules are now classified.

Almost all are eager only by init order and become command/hook/mode-loaded. calendar-sync stays eager when its .local.el is present. One new hidden dependency: calendar-sync guards its C-; g registration with a boundp shim and doesn't require keybindings, so the binding drops standalone.

I deferred elfeed-config rather than annotate it. Its header edit triggers byte-compilation, and the existing tests only pass when the module loads as interpreted source — the compiled cj/elfeed-process-entries inlines an elfeed struct accessor the stubs can't intercept, and the batch test environment has no elfeed package to build real structs. It needs its tests rewritten first, recorded in the inventory and a new todo task.

Also made the header allowlist scoping test durable: it used games-config (now classified) as its unclassified example; switched to a sentinel name plus a duplicate-entry guard.
</content>
</entry>
<entry>
<title>fix(dirvish): guard nil file and reject path-traversal playlist names</title>
<updated>2026-05-23T08:31:04+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-23T08:31:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=8fc6432d44e41787fb7f69ad792f50cc906393d5'/>
<id>urn:sha1:8fc6432d44e41787fb7f69ad792f50cc906393d5</id>
<content type='text'>
cj/set-wallpaper passed `(dired-file-name-at-point)` straight to `expand-file-name`, so running it with no file at point raised a bare `wrong-type-argument` instead of a clear error. cj/dired-create-playlist-from-marked expanded the raw playlist name under `music-dir` without checking it, so a name like "../foo" or "/etc/foo" would write outside the music directory.

I added a nil-file guard to set-wallpaper and a `cj/--playlist-name-safe-p` check that rejects any name carrying a directory separator before the path is built. Both paths now fail cleanly with a user-error. Regression tests went into the existing wrapper and playlist test files.
</content>
</entry>
<entry>
<title>fix(dirvish): declare runtime constant/util deps with plain require</title>
<updated>2026-05-23T08:21:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-23T08:21:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=b63c4f83081ec50074307cdbaff68525869d7294'/>
<id>urn:sha1:b63c4f83081ec50074307cdbaff68525869d7294</id>
<content type='text'>
dirvish-config builds `dirvish-quick-access-entries` from `code-dir`, `music-dir`, `pix-dir`, and the recording dirs at load time, and binds keys to `cj/xdg-open` and `cj/open-file-with-command`. Those come from user-constants and system-utils, but the module only required them under `eval-when-compile`, so the compiled module carries no runtime require and leans on init order having loaded them first.

I switched both to plain requires, matching host-environment, system-lib, and external-open-lib right below. Added a dependency-contract smoke test that fails if the requires are dropped.
</content>
</entry>
<entry>
<title>feat(dirvish): start org-drill on the .org file at point with S</title>
<updated>2026-05-12T17:49:33+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-12T17:49:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=a15c6b3e92b8d7be6be6dc4dd6802a18ccf52326'/>
<id>urn:sha1:a15c6b3e92b8d7be6be6dc4dd6802a18ccf52326</id>
<content type='text'>
`S` ("study") in `dirvish-mode-map` opens the `.org` file at point and runs `cj/drill-this-file` on it, so I can drill any deck straight from the file list. It `user-error`s on no file, on a directory, or on a non-`.org` file.

`D` and `O` were already taken (duplicate-file, open-with-command), so I went with `S`. It shadows dired's `dired-do-symlink`, which I never use from dirvish and which stays on `M-x` anyway. New `tests/test-dirvish-config-drill.el`: 6 ERT tests with `dired-get-filename`, `find-file`, and `cj/drill-this-file` mocked. I also fixed the stale `P` line in the module commentary — `P` is the print command now, not copy-path.
</content>
</entry>
<entry>
<title>feat(dirvish): print the file at point with P</title>
<updated>2026-05-12T09:45:47+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-12T09:45:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=2063e9dffff751d5f2cbca2817aee5daaa463cf3'/>
<id>urn:sha1:2063e9dffff751d5f2cbca2817aee5daaa463cf3</id>
<content type='text'>
`P' in dirvish/dired now runs `cj/dirvish-print-file': it sends the file at point to the default printer via CUPS (`lp', falling back to `lpr'), after a confirmation prompt. It refuses directories and file types outside `cj/dirvish-print-extensions' (pdf, txt, org, images, source files, ...). CUPS filters handle PDFs directly, so `lp &lt;file&gt;' covers everything in the list, and no separate print dialog is needed.

`P' was the project/home-relative variant of `cj/dired-copy-path-as-kill', now dropped (the `p' absolute-path and `l' org-link bindings stay, and `M-x cj/dired-copy-path-as-kill' still works). Plain dired's built-in `P' was `dired-do-print', which `dirvish-mode-map' had already shadowed, so this just replaces it with a type-aware version. Tests cover the predicates and the command's confirm / decline / no-file / directory / non-printable / no-printer / print-failure paths.
</content>
</entry>
<entry>
<title>refactor(external-open): extract external-open-lib for shared helpers</title>
<updated>2026-05-10T20:37:36+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T20:37:36+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=618bc7813b9acfcf1dfccc9c6590f6f5aece86cf'/>
<id>urn:sha1:618bc7813b9acfcf1dfccc9c6590f6f5aece86cf</id>
<content type='text'>
Same shared-helpers split-pattern that ai-vterm/vterm-config use
through cj-window-toggle-lib and that calendar-sync uses through
cj-org-text-lib.  Pull the two pure dispatch helpers out of the
external-open feature module into a sibling library so consumers
that only need the dispatch don't have to require the whole feature.

New `modules/external-open-lib.el' carries:
- `cj/external-open-command'
- `cj/external-open-launcher-p'

`modules/external-open.el' stays as the feature module: the
`default-open-extensions' defcustom, the `find-file' advice
(`cj/find-file-auto'), and the interactive commands (`cj/xdg-open',
`cj/open-this-file-with').  It now requires external-open-lib for
the dispatch helpers.

Migrate consumers:
- system-utils.el used to require `external-open' for
  `cj/external-open-launcher-p' alone -- now requires
  `external-open-lib' directly.
- dirvish-config.el calls `cj/external-open-command' from
  `cj/dirvish-open-file-manager-here' -- add an explicit
  `(require \='external-open-lib)'.

Test files renamed to match the system-lib naming pattern
(test-&lt;library&gt;-&lt;feature&gt;.el):
- test-external-open-command.el -&gt; test-external-open-lib-command.el
- test-external-open-launcher-p.el -&gt; test-external-open-lib-launcher-p.el

No behavior change.
</content>
</entry>
<entry>
<title>refactor(external-open): consolidate OS-open dispatch in external-open.el</title>
<updated>2026-05-10T19:42:04+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T19:42:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=16396d25c2795bd7f8822a695de111d07f588b26'/>
<id>urn:sha1:16396d25c2795bd7f8822a695de111d07f588b26</id>
<content type='text'>
Phase 4 of utility-consolidation. Three previously-overlapping helpers (system-utils' `cj/identify-external-open-command' and `cj/--open-with-is-launcher-p', plus the dirvish-only `cj/--file-manager-program-for' shipped earlier today) all answered "which OS-open program should I run?".  Pull the answer into one place: external-open.el.

Move and rename:

- `cj/--open-with-is-launcher-p' (system-utils) -&gt; `cj/external-open-launcher-p' (external-open). Public name now matches its module.
- `cj/identify-external-open-command' (system-utils) -&gt; `cj/external-open-command' (external-open). Returns nil for unsupported hosts instead of signaling -- callers that need a command must handle nil explicitly. The wrapper `cj/xdg-open' (also moved into external-open) converts nil to a `user-error' with a clear message, preserving the user-facing failure shape.
- Delete dirvish's `cj/--file-manager-program-for' helper. `cj/dirvish-open-file-manager-here' now calls `cj/external-open-command' directly. The shell-command fallback for nil-program preserves the previous escape hatch.

Break the system-utils &lt;-&gt; external-open recursive require by moving `cj/xdg-open' (the only system-utils function that external-open used) into external-open along with the dispatch.

Tests reorganized to match the move. Two new test files (`test-external-open-command.el', `test-external-open-launcher-p.el') replace the two system-utils-named test files. The dirvish file-manager-program test goes away with the helper. 11 tests covering Normal/Boundary/Error for the dispatch (plus the new "unsupported host returns nil" contract).

Add `(require \='external-open)' to system-utils.el and `(require \='system-lib)' to external-open.el (for `cj/file-from-context' which xdg-open uses).
</content>
</entry>
<entry>
<title>refactor(dirvish): use cj/executable-find-or-warn in cj/set-wallpaper</title>
<updated>2026-05-10T19:08:01+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T19:08:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=12c2cb141fcfd7ec14e04ce59e6294ffea4e2df9'/>
<id>urn:sha1:12c2cb141fcfd7ec14e04ce59e6294ffea4e2df9</id>
<content type='text'>
Migrate `cj/set-wallpaper' to the new helper.  The old code messaged a bare `"%s not found"' when feh or swww was missing -- the helper produces a better message ("feh not found; wallpaper setter unavailable") that lands in *Warnings* instead of flashing once in the echo area.

Add `(require \='system-lib)' to dirvish-config.el per the Phase 2 exit criterion.

Audit notes for the other `executable-find' call sites in prog-shell, prog-c, prog-go, prog-python, browser-config: all silent `:if' / LSP-availability checks (spec says keep silent), or interactive commands with platform-specific install hints in their existing messages (`shellcheck`, `mypy`, `delve`).  Migrating those would lose the install hints; leaving them.
</content>
</entry>
<entry>
<title>refactor(dirvish): extract cj/--dired-line-is-directory-p</title>
<updated>2026-05-10T18:44:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-10T18:44:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=2b19eb175d0664908f76bf7cc8dcc1eb5c140ce1'/>
<id>urn:sha1:2b19eb175d0664908f76bf7cc8dcc1eb5c140ce1</id>
<content type='text'>
`cj/dired-mark-all-visible-files' classified the current line as a directory via `(looking-at "^. d")' inline.  Lift the classification into `cj/--dired-line-is-directory-p', a string predicate that takes a line and returns non-nil when it's a directory listing.  The wrapper still walks the dired buffer line by line and calls `dired-mark' -- that iteration is dired-coupled and stays untested -- but the format-aware predicate is now isolated and verified.

Six Normal/Boundary tests cover unmarked directories, marked directories (`*' prefix), regular files (`-' instead of `d'), symlinks (`l'), empty lines, and dired header lines (`  /path:' and `  total N').
</content>
</entry>
</feed>
