<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/docs, 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-05T10:43:43+00:00</updated>
<entry>
<title>fix(term): make F9 and F12 reach Emacs inside ghostel buffers</title>
<updated>2026-06-05T10:43:43+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-05T10:43:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=854aa53a96476ccaae0c93bd9af0493ef8431e4b'/>
<id>urn:sha1:854aa53a96476ccaae0c93bd9af0493ef8431e4b</id>
<content type='text'>
F9 did nothing in an agent buffer: ghostel's semi-char mode forwards every key not in ghostel-keymap-exceptions to the pty, and ghostel-semi-char-mode-map outranks the major-mode map, so the F9-family and F12 bindings I'd put in ghostel-mode-map never fired. The keys went to Claude/the shell, which ignored them.

I added the F9 family (in ai-term) and F12 plus C-; (in term-config) to ghostel-keymap-exceptions and rebuilt the semi-char map with ghostel--rebuild-semi-char-keymap. add-to-list updates the list but not the already-built map, so the rebuild is what actually lets the keys through. C-; had the same latent bug for the same reason.

Two regression tests assert the keys are in the exceptions and that the rebuilt semi-char map no longer forwards them. I also corrected the spec note that claimed binding in ghostel-mode-map was enough (true for vterm, wrong for ghostel) and codified the gotcha.
</content>
</entry>
<entry>
<title>feat(term): replace vterm with ghostel as the terminal engine</title>
<updated>2026-06-05T10:28:58+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-05T10:28:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=ebdf9e466b0e1f86e9b7d76650ac32408273e7a7'/>
<id>urn:sha1:ebdf9e466b0e1f86e9b7d76650ac32408273e7a7</id>
<content type='text'>
I swapped the terminal engine from vterm to ghostel (libghostty-vt) everywhere. term-config replaces vterm-config (the F12 terminal, the C-; x menu, tmux history capture), and ai-term replaces ai-vterm (the F9 Claude-agent launcher). ghostel renders the agent TUI without vterm's flicker under heavy streaming, and one engine now covers every terminal workflow.

Two behavior changes fall out of the swap. F9 launches in a terminal frame now: ghostel renders in TTY frames, so the old GUI-only guard is gone. Terminal windows no longer dim when unfocused: ghostel resolves its palette into the native module per-terminal, so there's no per-window color hook to dim through the way vterm had.

auto-dim drops its vterm color-advice path, the dashboard Terminal button launches ghostel, and the vterm and vterm-toggle packages are removed. The tmux pane-history and copy-mode machinery carried over unchanged. It keys on the pty tty, which ghostel exposes.
</content>
</entry>
<entry>
<title>docs(design): file org-roam shared-KB brainstorm</title>
<updated>2026-05-31T19:49:39+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T19:49:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=93da00860bbc5a61663845d2785be20756983341'/>
<id>urn:sha1:93da00860bbc5a61663845d2785be20756983341</id>
<content type='text'>
</content>
</entry>
<entry>
<title>docs: reshape todo backlog and add buttercup evaluation</title>
<updated>2026-05-28T07:46:24+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-28T07:46:24+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=1c93820893a3aa831684e2322c606e54b0bba40c'/>
<id>urn:sha1:1c93820893a3aa831684e2322c606e54b0bba40c</id>
<content type='text'>
Walked the open-task backlog twice tonight. The first pass was a content audit (is each task still factually accurate?). The second was a relevance/priority review. Together they surfaced enough drift to be worth landing as one batch rather than dribbling into the next session.

The audit filed three new completion-task parents, each with an audit-finding body and child-task recommendations: F-key Completion (roughly 75% shipped per evidence), Terminal GPG pinentry Completion (no trace of the prior branch on this machine, treat as fresh), and Localrepo Documentation (build is shipped, docs land in three artifacts, four gap-fix tasks spin out as siblings). Headline-indicators-wrap and Buttercup closed DONE, Rework-dev-F-keys cancelled as superseded.

Manual Testing and Validation became its own top-level task with the 10 misfiled verify children moved in. Walk started tonight (tests 1 and 2 verified, two signel bugs surfaced and fixed in the same session), deferred to 2026-05-29 for the message-sending tests.

The Buttercup eval doc captures the rubric I came to during the brainstorm: adopt the moment a project crosses the "test reader is no longer the test author at write-time" threshold. ERT stays the right default until then. None of my projects have crossed yet.

Lint pass resolved all 21 org-lint follow-ups inline rather than letting them accumulate in a hidden inbox: 5 wrong-prefix design-doc links (../docs/design/X.org should have been docs/design/X.org), 4 file:line bare references wrapped in code formatting, 1 timestamp moved out of org-link brackets, 1 nested src block converted to begin_example, the wttrin diagnostic's stale link replaced with a note about where the surviving record lives, 8 markdown-bold patterns converted to org italics, 2 verbatim ** TODO references trimmed so the linter stops misreading them as headings.
</content>
</entry>
<entry>
<title>docs(signel): harden initiate-message spec to Ready</title>
<updated>2026-05-28T02:46:00+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-28T02:46:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=2a6ffb0e6a10fdf4fd42fe3e5689f300b25c7cf6'/>
<id>urn:sha1:2a6ffb0e6a10fdf4fd42fe3e5689f300b25c7cf6</id>
<content type='text'>
I wrote an initiate-message workflow spec on top of the existing Signal client design doc, covering the keymap, name-based picker, message-to-self, and the whole flow. A follow-up review caught three blockers I'd hand-waved: signel had no JSON-RPC success-result dispatch path (so cj/signel--fetch-contacts couldn't actually receive listContacts results), D4's "auto-connect when linked" didn't define what "linked" meant or how process death surfaced, and the contact cache had no ownership or invalidation story.

I verified the central one against the fork. signel--dispatch handles only "receive" and error responses, so success results were dropped. Then I folded all three into an Architecture additions subsection: a request-callback table keyed by JSON-RPC id with cleanup on success/error/reconnect, a cj/signel--ensure-started contract with branches for live process / account-set / account-nil, and a cj-owned cj/signel--contact-cache separate from signel's receive-time map.

A second review pass caught the remaining sync/async boundary. completing-read is synchronous and the fetch is async. I resolved it with pre-warm on connect plus a bounded accept-process-output fallback for cold caches, so the warm case feels instant and a dead daemon can't hang Emacs.

The follow-up re-review then converged to Ready-with-caveats and surfaced one concrete code finding I'd missed: the #2 input-clobber fix has to cover both signel--insert-msg AND signel--insert-system-msg, since both delete from the prompt line through point-max. The pieces-to-build entry and the prompt-preservation regression test now name both paths.

A few smaller tightenings landed in the same pass. The listContacts assumption is now a researched fact (verified on 94 contacts), the two open questions on account source and fork remote are marked decided (defcustom in the gitignored local config, local checkout with no remote for now), and a forward-flag in the scope summary names the four notification details to spec before that later slice starts.

docs/design/signal-client-review.org carries the review as the closure record. todo.org gets two tasks: a [#B] for the JSON-RPC success-result dispatch (the first build step), and a [#D] for groups in the picker as a vNext after the 1:1 flow is stable.

Spec is Ready. Implementation order is pinned to the Pieces-to-build list. RPC dispatch first, then the guard, then fetch/cache, then the picker and keymap, then the clobber fix.
</content>
</entry>
<entry>
<title>feat(signal): add Signal client foundation on a signel fork</title>
<updated>2026-05-27T01:24:58+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-27T01:24:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=049325f438817c0b4f4a443f71b8821b0bfd357a'/>
<id>urn:sha1:049325f438817c0b4f4a443f71b8821b0bfd357a</id>
<content type='text'>
I'm building a Signal client in Emacs on signal-cli (linked as a secondary device) with a fork of the signel package as the front end. signel is on MELPA but effectively abandoned, and the behavior I want needs internal edits, so owning a fork beats advising a dead package. Full rationale and the rejected alternatives are in docs/design/signal-client.org.

This lands the signal-cli-independent foundation: contact-list parsing for a completing-read picker, and the predicate that suppresses a notification for the chat being actively viewed. Both are pure and unit-tested without a linked account. cj/signal--parse-contacts was corrected against a live account (signal-cli 0.14 puts givenName/familyName at the top level, not under profile), and verified across all 94 real contacts.

The use-package wiring loads the fork from ~/code/signel, sources the account from a gitignored signal-config.local.el (a phone number is an identifier, not a credential, and this keeps it off the mirror without a GPG prompt), and turns off auto-open so an incoming message can't steal a window. Verified live: signel-start spawns the jsonRpc process, loads the account, and receives over the channel.

The fork edits (notify routing, the upstream input-clobber bug) and the contact-picker command are still to come.
</content>
</entry>
<entry>
<title>docs: add vterm/eat/ghostel terminal comparison</title>
<updated>2026-05-26T20:32:33+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-26T20:32:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=26fcf9e228765d2ed4cb09007d6958a201e24f56'/>
<id>urn:sha1:26fcf9e228765d2ed4cb09007d6958a201e24f56</id>
<content type='text'>
Research notes weighing the three Emacs terminal backends (vterm, eat, ghostel) on maintenance risk, rendering fidelity, and best-fit role. Referenced by the "Consolidate to EAT" task as the basis for that evaluation.
</content>
</entry>
<entry>
<title>feat(auto-dim): dim non-selected windows via auto-dim-other-buffers</title>
<updated>2026-05-25T14:25:32+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-25T14:25:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=56da3d940b26a51102bce39b3b82dfbbc2b391fd'/>
<id>urn:sha1:56da3d940b26a51102bce39b3b82dfbbc2b391fd</id>
<content type='text'>
I added auto-dim-config, a module that loads my local auto-dim-other-buffers fork and dims windows that don't have focus so the selected window stands out. A non-selected window drops to a pure-black background with faded gray text. The dimmed faces live in the dupre theme (themes/dupre-faces.el) so they track theme switches, and the module remaps default, the font-lock faces, and org-block onto them so syntax-highlighted code fades too rather than staying lit. Fringe is left out because dimming it forces a full-frame refresh that flickers on this non-pgtk build.

dim-on-focus-out is nil, so tabbing to a browser or terminal on Hyprland doesn't dim the whole frame. vterm and agent windows don't dim either, because the terminal paints its own per-cell colors past the face remap. I'm keeping that, since the agent's output stays readable while I work in code on the other side.

The module loads after the theme, carries a load-graph header, joins the header-contract allowlist, and the inventory moves to 103 of 103 classified.
</content>
</entry>
<entry>
<title>docs(load-graph): classify elfeed-config, the last init module</title>
<updated>2026-05-25T13:35:21+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-25T13:35:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=a522e5537ab9c94a45656b28e94a73b98f47d4b8'/>
<id>urn:sha1:a522e5537ab9c94a45656b28e94a73b98f47d4b8</id>
<content type='text'>
elfeed-config was the only init module without a load-graph header. It was deferred because annotating the header triggers a byte-compile, which broke its tests. With that test rewritten to use real structs, I added the header (Layer 4, optional, currently eager but a command-loaded deferral candidate, runtime requires user-constants, system-lib, media-utils), added elfeed-config to the header-contract allowlist, and moved it from the inventory's deferred and pending sections into the Batch 8 table.

That brings the inventory to 102 of 102 modules classified, completing the Phase 1 classification pass.
</content>
</entry>
<entry>
<title>refactor(load-graph): make hidden module dependencies explicit</title>
<updated>2026-05-24T23:36:19+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-24T23:36:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=36a453d2c1237b49f594b23433858a0146dbf31e'/>
<id>urn:sha1:36a453d2c1237b49f594b23433858a0146dbf31e</id>
<content type='text'>
Phase 2 of the load-graph project. I fixed the seven hidden dependencies the classification surfaced, so each module declares what it uses instead of relying on init order.

- system-defaults now requires host-environment and user-constants at runtime. They were eval-when-compile only, but env-bsd-p and user-home-dir are read at load, so the compiled module couldn't load standalone.
- custom-buffer-file, dev-fkeys, calendar-sync, and video-audio-recording require keybindings and drop their (when (boundp 'cj/custom-keymap) ...) shims. The shim silently dropped the C-; binding when the module loaded before keybindings. The explicit require makes the dependency real.
- flycheck-config and mail-config require keybindings for their cj/custom-keymap bindings (a use-package :map and a direct keymap-set).
- Removed a dead eval-when-compile (defvar cj/custom-keymap) in transcription-config; nothing there used the variable.

No init.el load-order change. keybindings and the foundation modules already load before these, so the requires are no-ops at startup and only fix standalone and test loading.

I verified each fix with a fresh emacs --batch (require 'X), then swept all modules standalone: every one loads or fails only with a clear missing-package message. Full make test, make validate-modules, and an init smoke all pass. Module headers and the inventory's hidden-dependency section are updated to mark the seven resolved.
</content>
</entry>
</feed>
