<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/tests, 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-05T14:55:41+00:00</updated>
<entry>
<title>fix(term): forward C-SPC and window-nav keys in ghostel buffers</title>
<updated>2026-06-05T14:55:41+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-05T14:55:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=81ececd67b23e42163c970edb4e9a4c0d824612e'/>
<id>urn:sha1:81ececd67b23e42163c970edb4e9a4c0d824612e</id>
<content type='text'>
Two keystrokes weren't reaching Emacs inside a ghostel terminal, both because of how ghostel routes keys in semi-char mode.

C-SPC was the worse one. ghostel forwards the `C-@' event but not the distinct `C-SPC' event GUI Emacs produces, so C-Space fell through to the global `set-mark-command' and set an Emacs region in the terminal buffer. That region followed point as output streamed (a stuck "selection" Escape and C-g couldn't clear), and it meant tmux copy-mode's begin-selection never started, so M-w copied nothing. I bind C-SPC to cj/term-send-C-SPC, which forwards NUL like a terminal key.

The C-M-arrows (buffer-move, window swap) were being forwarded to the terminal program the same way the F9 family was. I added the windmove S-arrows and buffer-move C-M-arrows to ghostel-keymap-exceptions and rebuilt the semi-char map. The S-arrows already reached Emacs by keymap precedence, but listing them makes the window-nav contract explicit rather than accidental.

Regression tests cover all three: C-SPC bound to the forwarder, and the window-nav keys in the exceptions with the semi-char map no longer forwarding them.
</content>
</entry>
<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>refactor(linear): reduce to a vanilla pearl setup</title>
<updated>2026-06-03T01:46:46+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-03T01:46:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=09b3b136a007b592aef708b208c81e9453e848c2'/>
<id>urn:sha1:09b3b136a007b592aef708b208c81e9453e848c2</id>
<content type='text'>
The config carried a full custom command surface: a hand-built C-; L keymap, which-key labels, a lazy API-key loader wired in as advice, and a pinned DeepSat team id. That's worth keeping only if the out-of-box pearl experience isn't good enough on its own, and the point now is to find out.

Stripped it back to exactly what pearl's README documents for a first install: pearl owns its own keymap (pearl-mode binds the command surface under pearl-keymap-prefix, default C-; L, in any Linear buffer), no global binding, no advice. The API key comes from authinfo.gpg via auth-source-pick-first-password, and the synced file moves to gtd/linear.org under org-directory. The only deviation from the README is loading from the local checkout at ~/code/pearl instead of an archive.

Deleted tests/test-linear-config.el — it covered the custom helpers (the key-loader advice, the keymap wiring) that no longer exist. What's left is declarative use-package config with nothing of its own to test.
</content>
</entry>
<entry>
<title>fix(ai-vterm): make F9 a faithful toggle of the agent split</title>
<updated>2026-06-02T18:04:37+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-02T18:04:37+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=38dad925528e05f2084474ead5519a6a6ceb70f7'/>
<id>urn:sha1:38dad925528e05f2084474ead5519a6a6ceb70f7</id>
<content type='text'>
F9 toggle-off used quit-restore-window to dismiss the agent. With several agents alive sharing one slot, switching among them (C-F9) reuses the window via set-window-buffer, which leaves the window's quit-restore parameter pointing at the first agent shown. Once stale, quit-restore-window falls back to switch-to-prev-buffer and surfaces a different agent instead of removing the window, so F9 appeared to "show another agent" rather than hide the split.

Toggle-off now collapses the split with delete-window, which is independent of the slot's buffer history, so the working buffer reclaims the frame. Geometry is captured first so the next toggle-on re-splits at the same width.

Toggle-on reopens the exact agent that was hidden (new cj/--ai-vterm-last-hidden-buffer), falling back to the most-recent agent only when that buffer has been killed. Hide-then-show is now a faithful round trip, not a jump to whichever agent is most-recent in buffer-list.

Sole-window toggle-off returns to the most-recent non-agent buffer instead of other-buffer, which could land on another agent.

I updated the two reuse-edge-window tests that asserted the old restore-displaced-into-a-kept-slot behavior to match the new always-collapse behavior.
</content>
</entry>
<entry>
<title>fix(prog-general): repoint daily-prep opener to root symlink</title>
<updated>2026-06-01T13:44:03+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-01T13:44:03+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=4772d49efd4b0b6031496caac1777915da6204a0'/>
<id>urn:sha1:4772d49efd4b0b6031496caac1777915da6204a0</id>
<content type='text'>
The daily-prep workflow now keeps its stable symlink at the project root as daily-prep.org instead of inbox/today-prep.org. I repointed cj/open-project-daily-prep (C-c p d) to match, updating its docstring, not-found message, and test for the new path.
</content>
</entry>
<entry>
<title>feat(ai-vterm): gate the F9 launcher to GUI frames</title>
<updated>2026-05-31T21:20:34+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T21:20:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=b72e794be60c5d4e94c61e5af8c08245773e3393'/>
<id>urn:sha1:b72e794be60c5d4e94c61e5af8c08245773e3393</id>
<content type='text'>
AI-vterm launches a graphical vterm side window, so F9 / C-F9 / M-F9 now decline with a message in a terminal frame instead of opening a vterm. The guard checks the current frame at command time rather than at load. That matters under the daemon, which serves GUI and terminal frames both with display-graphic-p nil at load, so a load-time gate would have disabled the launcher in its GUI frames too.

Routed the three window-behavior tests through a GUI-frame stub, since a batch run is itself a terminal frame.
</content>
</entry>
<entry>
<title>feat(signal): dock chat buffer to bottom 30% and add cancel binding</title>
<updated>2026-05-28T08:08:05+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-28T08:08:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=998e9c7a08a57f343a3ac2423597ec7e5556f329'/>
<id>urn:sha1:998e9c7a08a57f343a3ac2423597ec7e5556f329</id>
<content type='text'>
I added a display-buffer-alist entry matching "*Signel:" chat buffers and routing them through display-buffer-at-bottom with window-height 0.3. The signel fork's signel-chat now uses pop-to-buffer instead of switch-to-buffer, which is what makes the rule apply. Without that switch the buffer replaces the current window and skips display-buffer entirely.

Two new tests in test-signal-config.el lock the entry shape and the regex's buffer-name match set. A new test-signel-cancel-input.el covers the fork's C-c C-k handler. It clears the editable region between signel--input-marker and point-max, then quit-windows so the buffer survives the dismiss.

Closes the "Chat buffer placement + exit keys" task filed during the 2026-05-28 manual-verify walk.
</content>
</entry>
<entry>
<title>fix(signal): require signel before reading its private variables</title>
<updated>2026-05-28T07:45:31+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-28T07:45:31+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=a00628ac96d5f09f96be868755304f7bdfdc0d99'/>
<id>urn:sha1:a00628ac96d5f09f96be868755304f7bdfdc0d99</id>
<content type='text'>
cj/signel--ensure-started in modules/signal-config.el was reading signel--process-name in the first branch of its cond before the use-package autoload of signel had fired. The forward-declared (defvar signel--process-name) at L137 silences the byte-compile warning but doesn't actually bind the variable. Its value comes from signel.el's defconst, which doesn't run until signel is loaded.

The first call to cj/signel-connect (C-; M SPC) after Emacs launch produced "Symbol's value as variable is void: signel--process-name" instead of starting the daemon. Surfaced tonight during the manual verify walk of the initiate-message workflow.

I added (require 'signel) at the top of cj/signel--ensure-started so signel loads before any of its variables get read. The require is idempotent, so callers that hit the function after signel is already loaded pay nothing.

The new ERT test test-signal-config-ensure-started-requires-signel-first asserts ordering: require must be the first call inside the function, not just called somewhere. A future refactor that moves the require below the cond would fail this test instead of passing silently.
</content>
</entry>
<entry>
<title>feat(signal): initiate-message workflow (picker, guard, cache, keymap)</title>
<updated>2026-05-28T03:11:24+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-28T03:11:24+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=4daf2328756fa29b087870a2b1f672bc01aa6b51'/>
<id>urn:sha1:4daf2328756fa29b087870a2b1f672bc01aa6b51</id>
<content type='text'>
I built pieces 2-7 of the initiate-message workflow from docs/design/signal-client.org and added tests covering the fork's clobber-fix (commit 5ec56c0 over there). The picker is the user-facing change: a single key opens a name-based completing-read for any contact, with "Note to Self" pinned first.

The picker stack from the bottom up:

cj/signel--ensure-started is the daemon guard. With a live process it's a no-op. With signel-account set but no process it calls signel-start and pre-warms the contact cache. With signel-account nil it user-errors naming the remedy. Pre-warming on start means the picker feels instant on first use.

cj/signel--fetch-contacts issues a listContacts RPC through the new request-callback contract (signel--send-rpc with a success-callback). The callback runs the result through the verified cj/signal--parse-contacts and stores the (LABEL . RECIPIENT) alist in cj/signel--contact-cache, a cj-owned variable kept separate from signel's receive-time contact-map. An empty result populates the cache as nil, distinct from an RPC failure (which never invokes the callback so the prior cache survives). cj/signel-refresh-contacts is the user-facing command that clears and refetches.

cj/signel-message is the picker. Warm cache opens completing-read immediately. Cold cache kicks off a fetch and accept-process-outputs up to cj/signel-fetch-timeout seconds (3s default), then user-errors if the daemon hasn't responded so a wedged process can't hang Emacs. The candidate list pins "Note to Self" first (resolves to signel-account) with a display-sort metadata function that preserves the given order rather than alphabetizing.

cj/signel-message-self skips the picker and goes straight to signel-account. cj/signel-connect is the friendly verb on the prefix key.

cj/signel-prefix-map binds m / s / d / q / SPC and attaches under C-; M via with-eval-after-load keybindings so the binding survives load-order. l stays unbound for the future link command.

15 new ERT tests cover the ensure-started branches, the fetch + cache contract (issued, populated, empty), refresh-contacts, the picker's four scenarios (warm-cache contact, warm-cache Note to Self, cold-cache resolves in time, cold-cache timeout), message-self, and the keymap bindings. Plus 4 new tests in tests/test-signel-input-preservation.el for the fork's clobber fix: pending-input captures typed text and returns nil when empty; both signel--insert-msg and signel--insert-system-msg redraw the prompt without clobbering "halfwritten".

todo.org closes three tasks as dated event-log entries: the contact picker, the input clobber, and the use-package wiring.
</content>
</entry>
</feed>
