<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/modules/vterm-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-05T10:28:58+00:00</updated>
<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>fix(vterm): never reopen the F9/F12 windows from the top</title>
<updated>2026-05-28T01:48:12+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-28T01:48:12+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=7833fb8bc0e3f9ece01ab2fe6fe07ded0efc4af4'/>
<id>urn:sha1:7833fb8bc0e3f9ece01ab2fe6fe07ded0efc4af4</id>
<content type='text'>
F9 brought the agent window down from the top of the frame. The toggle remembers where the window last sat and replays it, and "above" was a position it could capture and replay: move the window to the top with the buffer-move keys, toggle off, and the next toggle reopened it up there. The host default never picks the top, so a remembered "above" was the only way in.

I added an optional allowed-directions list to cj/window-toggle-capture-state, the helper both F9 (ai-vterm) and F12 (vterm-config) share. When the captured direction isn't in the list, it falls back to the default direction and clears the saved size, since that size was measured on the disallowed axis and wouldn't transfer. Both dispatchers now pass (right below left), so neither can remember a top placement. They go through the same helper, so the rule stays in one place.

Three tests cover the new branch: a permitted direction is kept, a disallowed one falls back with the size cleared, and an omitted list preserves the old unconstrained behavior so existing callers are unaffected.
</content>
</entry>
<entry>
<title>refactor(load-graph): route C-; registration through the keymap API</title>
<updated>2026-05-25T00:59:28+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-25T00:59:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=08014b2f15e099a1c5e662a17a41290f37aeebf4'/>
<id>urn:sha1:08014b2f15e099a1c5e662a17a41290f37aeebf4</id>
<content type='text'>
Migrated all 31 cj/custom-keymap registration sites across 24 modules from direct (keymap-set cj/custom-keymap ...) calls to cj/register-prefix-map and cj/register-command. Consumers no longer reference cj/custom-keymap directly, so keybindings.el is the sole owner of the C-; prefix and modules reach it only through the API (each already requires keybindings from Phase 2).

Behavior-preserving: I dumped every C-; binding before and after the migration and they're identical: 279 bindings, each resolving to the same command. The which-key label blocks are untouched, since they use string key descriptions and never assumed the keymap existed. I byte-compiled all 24 files (no new free-variable warnings, because the cj/custom-keymap references are gone), and make test, validate-modules, and an init load all pass.
</content>
</entry>
<entry>
<title>docs(load-graph): classify remaining domain and optional modules</title>
<updated>2026-05-24T22:01:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-24T22:01:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=f84bba186dca1543f9c5f02c03af355188b6e87c'/>
<id>urn:sha1:f84bba186dca1543f9c5f02c03af355188b6e87c</id>
<content type='text'>
Final classification batch: the last 19 modules — linear-config, local-repository, lorem-optimum, mail-config, markdown-config, music-config, pdf-config, quick-video-capture, reconcile-open-repos, restclient-config, slack-config, system-commands, telega-config, tramp-config, transcription-config, video-audio-recording, vterm-config, weather-config, wrap-up. I annotated each header, added a Batch 9 table to the inventory, and extended the validation allowlist. 101 of 102 modules are now classified; only elfeed-config remains, deferred on its test fix.

Two more hidden dependencies turned up. video-audio-recording uses the boundp shim for its C-; r binding, and mail-config registers C-; e directly without requiring keybindings, so it errors standalone rather than degrading. Both recorded for Phase 2.
</content>
</entry>
<entry>
<title>fix(vterm): stop wheel/escape forwarders from blocking Emacs</title>
<updated>2026-05-19T00:28:27+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-19T00:28:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=016068349e0ebbd470e337440a1ef378248e0edb'/>
<id>urn:sha1:016068349e0ebbd470e337440a1ef378248e0edb</id>
<content type='text'>
vterm-send-string ends with (accept-process-output ... vterm-timer-delay
...). The global vterm-timer-delay is nil in this config, so the call
blocks forever when the pty's program consumes the event without producing
output -- a common pattern for TUIs like Claude Code reacting to mouse
wheel or Escape. The symptom is a spinning cursor until C-g.

cj/vterm--send-mouse-wheel and cj/vterm-send-escape now wrap the send in a
let-binding that pins vterm-timer-delay to 0, so accept-process-output
returns immediately. A top-level (defvar vterm-timer-delay) declaration
goes alongside so the let is dynamic. Without it, lexical-binding-t in
this file makes the binding lexical, invisible to vterm-send-string across
files. The backtrace from the failing case confirmed the lookup was still
receiving nil before the declaration.
</content>
</entry>
<entry>
<title>feat(vterm): forward &lt;escape&gt; to the pty in vterm-mode</title>
<updated>2026-05-18T21:00:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-18T21:00:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=55fb9102c70dc272d4267aec30eed4860f3abdf5'/>
<id>urn:sha1:55fb9102c70dc272d4267aec30eed4860f3abdf5</id>
<content type='text'>
`&lt;escape&gt;' is bound globally to `keyboard-escape-quit' in
modules/keybindings.el, so Emacs swallows the key before it can reach the
pty. Bind it in vterm-mode-map to cj/vterm-send-escape, which writes a
literal ESC byte via vterm-send-string. tmux's copy-mode `cancel' binding
then fires; vi-mode exits, fzf cancel, etc., also work as expected.
</content>
</entry>
<entry>
<title>feat(vterm): forward wheel events and route C-; x c into tmux copy-mode</title>
<updated>2026-05-18T20:49:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-18T20:49:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=0202a65825441562eb9903ccc28939367e29c274'/>
<id>urn:sha1:0202a65825441562eb9903ccc28939367e29c274</id>
<content type='text'>
vterm-mode-map binds only mouse-1 and mouse-yank-primary, so wheel events
fall through to Emacs scrolling and never reach the pty. tmux's `set -g
mouse on' never sees them. Bind wheel-up / wheel-down (and X11 mouse-4 /
mouse-5) to send SGR mouse-wheel escapes via vterm-send-string. tmux's
existing WheelUpPane / WheelDownPane bindings route into copy-mode from
there.

For keyboard parity, route C-; x c through cj/vterm-copy-mode-dwim, which
sends C-b [ when a tmux client is attached and falls back to vterm-copy-mode
otherwise. tmux's history-limit is now reachable from either entry point.
The matching copy-mode keys (M-w stays, C-g / q / Escape exit, Enter
unbound) land in the dotfiles repo alongside.
</content>
</entry>
<entry>
<title>feat(vterm): show tmux scrollback history in place</title>
<updated>2026-05-13T20:39:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-13T20:39:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=c3ec8508a8ff414d099d9e567eb5ffd43a9c93f2'/>
<id>urn:sha1:c3ec8508a8ff414d099d9e567eb5ffd43a9c93f2</id>
<content type='text'>
`cj/vterm-tmux-history' previously used `pop-to-buffer', which routed the history view through display-buffer-alist -- in an agent window that often meant a split or a hand-off to another window, costing the agent its frame slot. `switch-to-buffer' instead drops the history into the selected window directly; the existing quit handler already restores the origin in that same window via `set-window-buffer'.

New test asserts the in-place behavior: starting single-window with a vterm origin, invoking the command leaves `(one-window-p)` t with the history buffer in the original slot. The existing render test no longer needs its `pop-to-buffer' stub since `switch-to-buffer' works in batch.
</content>
</entry>
<entry>
<title>feat(vterm): unify the keys in vterm copy-mode and tmux history</title>
<updated>2026-05-11T15:02:17+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-11T15:02:17+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=949bdeb3ac4d7b027cdd88efd54cc8c121f8c5d3'/>
<id>urn:sha1:949bdeb3ac4d7b027cdd88efd54cc8c121f8c5d3</id>
<content type='text'>
`vterm-copy-mode' and the `C-; x h' tmux-history buffer now share one key story. `M-w' copies the active region and stays put, so I can copy several things in a row. `C-g', `&lt;escape&gt;', or `q' leaves (resuming the live terminal, or closing the history buffer) without copying. `RET' is unbound (no special "copy and exit"). In copy-mode that meant removing vterm's default `RET' -&gt; `vterm-copy-mode-done' binding.

Before, `M-w' exited and copied as it went, which made grabbing more than one selection awkward. The history buffer's `cj/vterm-tmux-history-copy-and-quit' was the copy-and-exit one-shot. It's gone. `M-w' then `q' is the equivalent.

I also moved `cj/vterm-tmux-history' from `C-; x C' to `C-; x h' (unshifted, and it frees `C') and refreshed the file's stale commentary header, which still referenced the old `C-; V' prefix and `&lt;pause&gt;'.
</content>
</entry>
<entry>
<title>refactor(ai-vterm): rename Claude-specific names to a generic "agent"</title>
<updated>2026-05-11T12:18:20+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-11T12:18:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=59b0854464ef29511a0d09f1e76fd1140e675833'/>
<id>urn:sha1:59b0854464ef29511a0d09f1e76fd1140e675833</id>
<content type='text'>
I may add other terminal agents to this launcher (aider, an open-source LLM TUI), so the buffer prefix, the user knob, and the internal helpers shouldn't say "Claude". The module name (ai-vterm) and the `cj/ai-vterm-*` customs were already generic. This finishes the job:

- buffer prefix `claude [&lt;basename&gt;]` -&gt; `agent [&lt;basename&gt;]` (the `defconst` and the matching display-buffer-alist regex move together)
- `cj/ai-vterm-claude-command` -&gt; `cj/ai-vterm-agent-command` (the default still runs the `claude` CLI, with a docstring note on swapping it)
- `cj/--ai-vterm-claude-buffers` / `-displayed-claude-window` / `-reuse-existing-claude` -&gt; `-agent-*`, and their test files renamed to match
- prose in the module commentary and docstrings, plus the matching test docstrings and buffer-name literals

`vterm-config.el` hardcodes the same buffer prefix in `cj/--vterm-toggle-buffer-p` (F12 excludes agent buffers from its candidate set), so that literal moved too. Collapsing it into the shared `cj/--ai-vterm-name-prefix` is a cleanup for another day.

After a reload, a project's buffer opens as `agent [foo]` instead of `claude [foo]`. Old buffers keep their names until killed. I also corrected two stale `eshell-vterm-config.el` references in ai-vterm.el docstrings (that module was split into `vterm-config.el`).

Two things keep saying "Claude": the `cj/ai-vterm-agent-command` default value (the actual CLI), and the "Claude Code" example in `vterm-config.el`'s cursor-restore docstring (a concrete TUI example, not branding).

90 tests pass. `make validate-modules` clean.
</content>
</entry>
</feed>
