<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/modules, 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>feat(linear): add global C-; L prefix and short assignee tags</title>
<updated>2026-06-03T20:43:48+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-03T20:43:48+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=a6cd143209dceb1a3c35f89952c87eeac9bf2133'/>
<id>urn:sha1:a6cd143209dceb1a3c35f89952c87eeac9bf2133</id>
<content type='text'>
I bound pearl-prefix-map globally under C-; L with :bind-keymap, so the full command surface is reachable from any buffer instead of only inside a pearl-rendered one or through M-x. The first press autoloads pearl. It's the same map pearl-mode binds in-buffer, so behavior is identical everywhere.

I also set pearl-assignee-tag-short, so the assignee @-tag renders as the first name only (@first instead of @first_last), trading disambiguation for a tighter tag line.

Both layer onto the prior vanilla setup after dogfooding the out-of-box experience. I updated the commentary to match.
</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>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>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>
</feed>
