diff options
Diffstat (limited to 'todo.org')
| -rw-r--r-- | todo.org | 388 |
1 files changed, 189 insertions, 199 deletions
@@ -55,13 +55,6 @@ Tags are additive. For example, a small wrong-behavior fix can be =:bug:quick:=, and a feature that requires internal restructuring can be =:feature:refactor:=. * Emacs Open Work -** DONE [#B] C-<left>/<right>/<down> wrongly enter terminal copy-mode :bug:quick: -CLOSED: [2026-06-24 Wed] -Fixed 2026-06-24: per Craig, only C-<up> enters copy-mode now — all other arrows (C-<down>/<left>/<right> and the M-arrows) were dropped from both the ghostel-mode-map binding and ghostel-keymap-exceptions in modules/term-config.el, so C-<left>/C-<right> reach the shell as readline word-motion again. Also per Craig: C-<up> pressed while already in copy-mode just moves up — cj/term-copy-mode-up checks tmux pane_in_mode (and ghostel--input-mode without tmux) and skips re-entry, which would otherwise reset the cursor. 6 ERT tests rewritten; byte-compile clean; the live daemon was stripped of the stale bindings/exceptions and reloaded (C-<up> bound + an exception, C-<left> forwarded to the pty). Real-terminal scroll is the VERIFY under Manual testing and validation. -** DONE [#B] ai-term wrap-teardown + shutdown functions :feature: -CLOSED: [2026-06-24 Wed] -Done 2026-06-24: added the three headless functions to =modules/ai-term.el= per the rulesets contract — =cj/ai-term-quit= (kill aiv- session + agent buffer + restore layout, idempotent), =cj/ai-term-live-count= (integer gate), =cj/ai-term-shutdown-countdown= (gate re-check → abort-able run-at-time countdown → =cj/ai-term-shutdown-command=, a defcustom). Reused the existing kill/close helpers. 13 ERT tests (live-count parsing, quit kill+idempotency, gate-abort/cancel/tick); byte-compile + validate-modules + launch smoke clean; headless contracts verified live in the daemon (live-count→3, quit no-op returns the session name, countdown aborted with sessions live — no shutdown). The tmux/shutdown side effects and the both-sides end-to-end are a VERIFY under Manual testing and validation. Original task body: -The .emacs.d half of the rulesets wrap-it-up teardown / shutdown feature. Implement three functions in =modules/ai-term.el=, all callable headlessly via =emacsclient -e= (no interactive frame): =cj/ai-term-quit "<project>"= (teardown a project's aiv- tmux session + buffer + geometry restore), =cj/ai-term-live-count= (integer, the safety gate), =cj/ai-term-shutdown-countdown= (run-at-time timer). Craig's 2026-06-23 decisions: non-destructive qualifier = "with summary"/"and summarize"; countdown is a run-at-time timer (not a tty writer); safety gate uses cj/ai-term-live-count. Lands with the rulesets half (workflow + Stop hook already built/pushed). Spec: =inbox/PROCESSED-2026-06-23-2331-from-rulesets-ai-term-teardown-companion.org= (rulesets proposal: docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org). Own focused session. ** TODO [#C] ai-term multi-LLM support — Claude / Codex / ollama :feature: Allow creating an ai-term that launches any of Claude, Codex, or a local LLM via ollama, switchable at session start. From rulesets/Craig via the roam inbox. Spec note: =inbox/PROCESSED-2026-06-23-2123-from-rulesets-ai-term-multi-llm-support-from-craig.org=. ** TODO [#C] theme-studio: package coverage for pearl, wttrin, chime :feature:studio: @@ -74,78 +67,10 @@ Was hardcoded "gray60"; now four customizable faces (branch =feature/themeable-f Four modeline faces shipped (081d76e). =inbox/PROCESSED-2026-06-23-2326-from-chime-chime-added-four-themeable-modeline.org=. ** TODO [#D] Evaluate google-keep Emacs package :quick: From the roam inbox. Look at the google-keep Emacs package — worth adding for in-editor Keep, or does the existing google-keep MCP cover it? Triage / shortlist, not a commitment. -** DONE [#C] README holistic pass -CLOSED: [2026-06-24 Wed] -Holistic pass over README.org, changes approved by Craig: bumped the Emacs floor to 30 (developed on 30.2); corrected the module count (~100 → ~120); added docs/ to the layout and reworded scripts/ (now also theme-studio); added Theme Studio, the ghostel native terminal, and ai-term to Features; added make coverage-summary to the dev targets. From the roam inbox. -** DONE [#B] Theme-driven nerd-icons colors + filetype legend :feature: -CLOSED: [2026-06-24 Wed] -Dropped the runtime nerd-icons tint so icon color is theme-driven, and added a -theme-studio filetype-legend representation over the 34 =nerd-icons-*= color -faces. Spec: -[[file:docs/specs/theme-studio-nerd-icons-colors-spec.org][theme-studio-nerd-icons-colors-spec.org]]. -Three Codex spec-review rounds (3 + 6 + 1 findings) incorporated; findings -[10/10], decisions [6/6]. Ready confirmed 2026-06-24 and implemented in a -no-approvals speedrun as the four dated phases below — full run-tests.sh and -=make test= green, all pushed. Live visual confirmation is a VERIFY under -Manual testing and validation. vNext follow-ups promoted to their own [#D] task. -*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 1 — legend capture shipped -=scripts/theme-studio/build-nerd-icons-legend.el= resolves the 13 v1 rows from the live nerd-icons alists into =nerd-icons-legend.json= (committed); =generate.py='s =load_nerd_icons_legend= validates and falls back to the generic app on absent/malformed/empty/bad-row, with a warning. 7 Python tests. Committed (feat phase 1). -*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 2 — bespoke legend preview shipped -nerd-icons registers as a bespoke app whenever the legend is valid (=add_nerd_icons_app=); =renderNerdIconsPreview= draws each row's glyph in its mapped face color through the shared registry, so recolor repaints live; the 34 faces stay editable. =#nerdiconstest= gate covers the wiring, the dir-row owner, and the recolor-repaint. Committed (feat phase 2). -*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 3 — tint removed, theme drives color -Removed =cj/nerd-icons-tint-color= + =cj/--nerd-icons-color-faces= + =cj/nerd-icons-apply-tint= and both call sites from =nerd-icons-config.el=; the WIP theme already owned the 34 faces (theme-studio auto-discovered them), so color is theme-driven now. Kept =cj/--nerd-icons-color-dir=. Deleted the apply-tint test. validate-modules + launch smoke clean. Committed (feat phase 3). -*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 4 — dir-precedence probe + round-trip -ERT probe locks the dir-precedence decision (prepended =nerd-icons-yellow= is first in the face list, wins over =nerd-icons-completion-dir-face=); =#nerdiconstest= extended with the export/import round-trip over an assigned nerd-icons color and a dir-face-stays-out check. Full run-tests.sh + =make test= green. Committed (test phase 4). Live visual is the VERIFY under Manual testing. ** TODO [#D] Theme Studio nerd-icons vNext follow-ups :feature: Deferred from [[file:docs/specs/theme-studio-nerd-icons-colors-spec.org][theme-studio-nerd-icons-colors-spec.org]]: extend the legend to buffer-mode and command/symbol categories if the file set proves insufficient; add a "reset to nerd-icons native palette" button. -** DONE [#B] ai-term keybinding home :feature: -CLOSED: [2026-06-23 Tue] -Done 2026-06-23 (commit be772bc0): family moved to C-; a (a toggle, s select/launch, n next, k kill), swap also on M-SPC, F9 family retired, jumper's M-SPC binding removed (rehome pending). cj/ai-term-next now opens the picker when no agent is running instead of erroring. Bindings verified live in the daemon; Craig's hands-on check is filed under Manual testing and validation. -Move the ai-term commands off the F9 family. F9 sits somewhere semi-dangerous -to hit, and F8 (org-agenda) is slow to load, which reads as Emacs being -unresponsive. Craig wants three commands on an easy near-home-row chord: open -the ai-term selection menu, switch to the next agent, and kill the current one -(=cj/ai-term=, =cj/ai-term-next=, =cj/ai-term-close=). Explore C-, M-, and C-M- -with SPC. Likely collides with jumper, but ai-term is used far more, so jumper -yields. Archiving gptel this session freed the =C-; a= prefix, so the whole -ai-term family could live under =C-; a= (or another near-home-row key). -Related: the s-F9 detached-agent landing task and the tmux copy-mode binding -task elsewhere in this section. From the roam inbox. -** DONE [#C] Face coloring completion-read icons :quick:solo: -CLOSED: [2026-06-23 Tue] -Answered 2026-06-23 (investigation, no code change). There is no single -"completion icon" face — each icon inherits a per-type =nerd-icons-*= color -face (a .el file icon inherits =nerd-icons-purple=, an M-x command icon -=nerd-icons-blue=, etc.; nerd-icons picks the face per glyph/filetype). What -makes every completion icon render the SAME color here is this config's bulk -tint: =cj/nerd-icons-tint-color= (defcustom in =nerd-icons-config.el=, default -"darkgoldenrod") sets the foreground of all ~33 =nerd-icons-*= color faces via -=cj/nerd-icons-apply-tint=, applied in the =nerd-icons= =:config=. Verified live: -=nerd-icons-icon-for-file "init.el"= -> =:inherit nerd-icons-purple=, and that -face's foreground is "darkgoldenrod". Directory icons additionally get -=nerd-icons-yellow= layered on by =cj/--nerd-icons-color-dir= advice -(=nerd-icons-completion-dir-face= is unset, so it isn't the driver here). -To theme: change =cj/nerd-icons-tint-color= (one color for all icons, then call -=cj/nerd-icons-apply-tint=), or drop the bulk tint and set the individual -=nerd-icons-*= color faces for per-filetype colors. For theme-studio, the knob -to expose is =cj/nerd-icons-tint-color= plus the =nerd-icons-*= face family. -** DONE [#C] Org formatting inside cj comments :feature: -CLOSED: [2026-06-23 Tue] -Done 2026-06-23: mapped the "cj:" src-block language to org-mode via -=org-src-lang-modes= in =org-babel-config.el=. Effect: a cj comment block's -prose now gets org font-lock in place (links, *bold*, lists styled — verified -live, the link inside a block carries the =org-link= face), and =C-c '= opens a -full org-mode buffer to edit it. Approach A from the design walk: non-breaking, -the =cj:= grep marker and the whole cj-processing pipeline are unchanged. The -block stays a src block, so org's parser still treats its body as code — links -are followed from the =C-c '= buffer rather than clicked in place. If that -in-place limitation bites, Approach B (migrate to a =#+begin_cj= special block) -is the documented escalation. -Craig writes free-form prose inside cj comment blocks (=#+begin_src cj: ...=) -and wants org formatting available there. -From the roam inbox. ** TODO [#C] VAMP — extract music-config into a standalone player :feature:refactor: :PROPERTIES: :LAST_REVIEWED: 2026-06-21 @@ -175,15 +100,6 @@ Allow creating an ai-term backed by any of Claude, Codex, or a local LLM via oll :END: Research how completely each of EAT, vterm, and ghostel can be themed — in particular how far theme studio can theme each terminal and what it leaves out. Produce a comparison document, then review it with an eye to whether ai-term should move off ghostel (current) to EAT or vterm. Connects to the chime/emacs-wttrin/pearl face-exposure theme-studio thread. From the roam inbox. -** DONE [#C] term: M-<arrow> enters tmux copy-mode :feature: -CLOSED: [2026-06-24 Wed] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-22 -:END: -Done 2026-06-24: C-<up>/<down>/<left>/<right> and M-<arrow> in =ghostel-mode-map= enter copy-mode and carry their direction in one stroke (=cj/term-copy-mode-up= & friends -> =cj/term-copy-mode-move= -> =cj/term-copy-mode-dwim= then =cj/--term-copy-mode-move-step=). tmux path writes the arrow escape sequence into the pty; non-tmux path moves point in =ghostel-copy-mode=. All 8 keys added to =ghostel-keymap-exceptions= + =ghostel--rebuild-semi-char-keymap= (the gotcha). Ghostel-only. 6 new ERT tests; bindings + exceptions + the dwim sequence verified live in the daemon. The real tmux copy-mode scroll is a VERIFY under Manual testing and validation. - -Folded 2026-06-23 from the roam inbox: Craig also wants C-<up> (control + up arrow) to enter tmux copy-mode and move up in one stroke — i.e. a modified arrow both enters copy-mode and passes the movement (copy-mode + arrow). So the binding set is the modified arrow keys (M-arrow and/or C-arrow), each entering copy-mode and carrying its own direction. - ** TODO [#B] Un-pin ghostel from 0.33.0 once upstream fixes #422/#423 :bug: :PROPERTIES: :LAST_REVIEWED: 2026-06-20 @@ -218,13 +134,6 @@ From the 2026-06 config audit, =modules/transcription-config.el=: - =:210= — =make-process :stderr= with a file PATH creates a BUFFER named like the path (verified by probe); the "Errored. Logs in <file>" notification points at a log without the error text, and the hidden stderr buffer leaks per transcription. Route stderr into the process buffer or write it out in the sentinel. - =:370-374= — video path derives txt/log from the temp mp3's /tmp path; the transcript lands in /tmp and dies on reboot, contradicting the "alongside the source" docstring. Pass the video's path as the output base. -** CANCELLED [#C] page-signal pager account deregistered — re-registration needs your hands -CLOSED: [2026-06-21 Sun] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-12 -:END: -Reported by .emacs.d 2026-06-12 01:01: the dedicated pager number (+15045173983, the Claude Pager Google Voice number on signal-cli) returns "User ... is not registered" on every send — Signal appears to have deregistered it (GV numbers get periodically re-verified). Re-registration requires captcha/SMS, which only you can do. Until then every page-signal call fails; .emacs.d's config-audit page fell back to email. Wrapper lives at claude-templates/bin/page-signal. - ** VERIFY [#C] Remove unused system-power keybindings :refactor:quick:next: :PROPERTIES: :LAST_REVIEWED: 2026-06-20 @@ -232,21 +141,6 @@ Reported by .emacs.d 2026-06-12 01:01: the dedicated pager number (+15045173983, Needs from Craig: the task says "confirm the exact set to keep before unbinding." Under C-; ! the bindings are shutdown (s), reboot (r), restart-Emacs (e), and friends. Tell me which to keep bound and which to drop (the completing-read menu still reaches the rare ones), and I'll unbind the rest. =modules/system-commands.el= binds shutdown (=C-; ! s=), reboot (=C-; ! r=), restart-Emacs (=C-; ! e=) and friends under the =C-; != prefix. Craig rarely uses them and wants the key real-estate back. Drop the bindings he doesn't use; the completing-read menu can still reach the rare ones. Confirm the exact set to keep before unbinding. From the roam inbox. -** DONE [#B] mu4e: cmail can't trash, no account can refile :bug:quick:solo: -CLOSED: [2026-06-24 Wed] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-13 -:END: -=modules/mail-config.el:217-220= — the cmail context (primary account) sets only drafts/sent, so D falls back to default "/trash" which doesn't exist under ~/.mail (=/cmail/Trash= does); and NO context sets =mu4e-refile-folder=, so r targets nonexistent "/archive" everywhere. Accepting mu4e's offer to create the maildir strands mail in a directory mbsync never syncs — messages silently vanish from the server's view. Add =mu4e-trash-folder= to cmail + per-context =mu4e-refile-folder=. From the 2026-06 config audit. -Fixed 2026-06-13: cmail gets =mu4e-trash-folder= "/cmail/Trash"; refile is a per-message function (=cj/mu4e--refile-folder=) instead of a per-context string — mu4e context :vars are sticky, so a per-context refile leaks one account's archive folder into another. cmail → "/cmail/Archive"; gmail/dmail signal a =user-error= rather than move mail into an unsynced phantom folder (Craig chose the fail-safe over syncing [Gmail]/All Mail — the All Mail option means a multi-GB pull + cross-folder duplicates; revisit if local Gmail archiving is wanted). Applies on next mu4e open; pure dispatch helper covered by tests. - -** CANCELLED [#C] Lock screen silently fails — slock is X11-only :bug:quick: -CLOSED: [2026-06-21 Sun] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-13 -:END: -=modules/system-commands.el:105= binds the lockscreen command to =slock=, which can't grab a Wayland session; =cj/system-cmd= launches it detached with output silenced, so C-; ! l does nothing and the screen never locks. Security issue: Craig believes the screen locks when it doesn't. Fix: =hyprlock= (or =swaylock=), ideally resolved per session type via =env-wayland-p= so an X11 fallback survives for other machines. From the 2026-06 config audit. -Fixed 2026-06-13: lockscreen-cmd resolves to =loginctl lock-session= on Wayland (logind Lock → hypridle → hyprlock, the path idle/sleep locking already uses), =slock= on X11; also added the missing =(require 'host-environment)=. Live in the daemon; manual lock test under the Manual testing parent. ** PROJECT [#A] Manual testing and validation Exercised once the phases above land. *** VERIFY ai-term keybindings land on C-; a + M-SPC @@ -730,28 +624,6 @@ Phase 1. Palette anchors + OKLCH shade generation (reusing colormath.js), the RO Phase 2. Initial state from seed() plus seedPkgmap for the non-org packages; all-tier reseed button with a scope-named overwrite warning, resetting non-org to their APPS defaults; regenerate dupre-revised.json. Gate: #selftest PASS; default-on-open equals seed(); artifact round-trip (regenerated dupre-revised.json imports back to the same seeded state); Chrome eyeball. **** TODO Seeding-engine test surface :solo:test: Keep #seedtest, #selftest, the default-on-open check, the dupre-revised round-trip, node --check, and Chrome validation green. -** CANCELLED [#B] AI Open Work -CLOSED: [2026-06-23 Tue] -gptel archived 2026-06-23 to archive/gptel/ (rarely used). The child issues below — ai-rewrite directive plumbing, ai-conversations bugs, the stale-elpa / gptel-magit shadow, model-switch dedup — are all moot against archived code. Kept for reference; detail also in git history. -*** CANCELLED [#B] ai-rewrite: chosen directive never reaches the request :bug:solo: -=modules/ai-rewrite.el:64= — the directive is let-bound around =(call-interactively #'gptel-rewrite)=, but gptel-rewrite is a transient prefix that returns when the menu shows; the send resolves the directive AFTER the binding unwound (verified against ~/code/gptel/gptel-rewrite.el:780-799). The picker's choice is silently dropped — the module's core feature is inert. Set =gptel--rewrite-directive= buffer-locally (restore via =gptel-post-rewrite-functions=) or use a self-removing global hook entry. From the 2026-06 config audit. - -*** CANCELLED [#B] Stale elpa gptel shadows the local fork — likely the gptel-magit root :bug:quick:solo:next: -Needs from Craig: can't be done standalone. I tried deleting elpa/gptel-0.9.8.5 — the fork loaded fine and gptel-magit still worked via use-package autoloads, but package activation then printed "Unable to activate gptel-magit / Required gptel-0.9.8 unavailable" on every startup, so I reverted. To remove the shadow we must also resolve gptel-magit's package dependency: either drop gptel-magit's package dep (load it via load-path like the gptel fork), or repackage the fork into .localrepo as gptel. Tell me which and I'll do it; this pairs with the gptel-magit investigation. -=elpa/gptel-0.9.8.5= is still installed alongside the =~/code/gptel= fork (=ai-config.el:383=); package activation puts the elpa dir + autoloads on load-path, so which copy wins depends on ordering, and a mixed load (fork .el + elpa .elc) produces "impossible" bugs. =gptel-magit= (elpa) declares gptel as a dependency, so IT may be pulling the stale copy — check this first when working the open "[#B] Investigate gptel-magit not working properly" task. Fix: =package-delete= the elpa gptel + remove from .localrepo so the fork is the only copy on disk. From the 2026-06 config audit. - -2026-06-15: tried deleting =elpa/gptel-0.9.8.5= standalone. The fork loaded correctly and gptel-magit still worked via use-package =:commands= autoloads, BUT package activation then printed "Unable to activate package gptel-magit / Required package gptel-0.9.8 unavailable" on every startup and test run (gptel-magit declares gptel as a package dependency that no longer resolves). Reverted. This can't be done standalone — it must be paired with the gptel-magit dependency fix (drop gptel-magit's package dep, or repackage the fork into .localrepo as gptel). Do it together with the gptel-magit investigation task. - -*** CANCELLED [#C] ai-conversations: dead-buffer load, role flattening, non-atomic writes :bug:solo: -From the 2026-06 config audit, =modules/ai-conversations.el=: -- =:324= — load in a fresh session does =get-buffer-create "*AI-Assistant*"= (plain fundamental-mode buffer); =--ensure-ai-buffer= then sees it exists and never calls =(gptel)=. Sending doesn't work, autosave self-cancels (requires gptel-mode). Use =get-buffer= for the check; let ensure create. The browser RET/l path inherits this. -- =:240= — persistence drops gptel's =response= text properties, so a reloaded history replays to the model as ONE user message (model re-reads its own answers as Craig's words). Adopt gptel's native bounds persistence or re-mark on load from the "* Backend:" headings. -- =:248= — =write-region= straight at the target; crash mid-write truncates the only copy of the history (autosave hits this constantly). Temp + rename. -- =:140= — three overlapping autosave mechanisms (after-send advice that fires before the response exists, post-response hook, 60s timer). Keep the hook; drop the advice (and likely the timer). - -*** CANCELLED [#C] Dedup gptel model-switch commands — keep switch-backend or fold into change-model :bug: -=cj/gptel-change-model= (C-; a m) already does backend+model switching and interns correctly, so =cj/gptel-switch-backend= (C-; a B) is arguably redundant now that its crash is fixed. Decision for Craig: keep both, or delete =cj/gptel-switch-backend= plus its C-; a B binding and keep one model-switch command. From the 2026-06 config-audit follow-up. - ** PROJECT [#B] Architecture review follow-up from 2026-05-03 :refactor: High-level pass over =init.el=, =early-init.el=, and all 104 files in @@ -3119,15 +2991,6 @@ one config-wide policy. From the roam inbox. (B) Messenger unification (first application of the policy above). Spec: [[file:docs/specs/messenger-unification-spec.org][messenger-unification-spec.org]] ([[id:4bfc2011-8ffc-4765-8886-91df12141171][by id]], Draft, 2026-06-11; keybinding-alphabet section + smoke-first parity added 2026-06-16). One library (=cj-messenger-lib.el=) gives every messenger the same shape: chat windows rise from the bottom (the signel rule, generalized), C-c C-c confirms, C-c C-k cancels, C-c C-a attaches — dispatched per backend through a registry + minor mode. Signel already conforms (reference backend); telega and slack join in phases 2-3; ERC later. All eight decisions settled 2026-06-11 (cancel closes an idle window; telega's filter-cancel shadow accepted; slack rooms join the bottom rule). Spec held open — Craig has more ideas to fold in before it's marked Ready. -** DONE [#B] agenda sources: roam Projects missing, no existence filtering :bug:solo: -CLOSED: [2026-06-24 Wed] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -Done 2026-06-24, both parts: (1) per Craig, corrected the docs rather than implementing roam-Project agenda scanning — the commentary + two docstrings claimed org-roam "Project" nodes are agenda sources, but they were never scanned; roam Project/Topic notes are refile targets (org-refile-config.el), not agenda sources. (2) =cj/--org-agenda-base-files= now drops non-existent files and =org-agenda-skip-unavailable-files= is set as a backstop, in the one shared helper so the agenda builders, single-project view, and chime initializer all get it. base-files tests reworked to drive real temp files (+ a drops-missing case); byte-compile clean; live-verified (skip var t, base-files returns only existing). From the 2026-06 config audit, =modules/org-agenda-config.el=: -- =:182-191= — commentary and docstrings promise org-roam nodes tagged "Project" as agenda sources, but =cj/--org-agenda-scan-files= never scans them, and files added by the roam finalize-hook are wiped on the next =cj/build-org-agenda-list= cache rebuild (≤1h). Add a roam Project pass (mirror =org-refile-config.el:101-109=) or correct the docs. -- =:186,456= — agenda file list built unconditionally (inbox/calendars may not exist on a fresh machine) and =org-agenda-skip-unavailable-files= is unset — the exact interactive-prompt class that once hung the chime daemon. Filter with =file-exists-p= + set the var as backstop. - ** TODO [#B] Auto-dim: org headings, links, and tags do not dim in unfocused windows :bug: :PROPERTIES: :LAST_REVIEWED: 2026-06-20 @@ -3154,14 +3017,6 @@ Ask: Reference values -- modus-vivendi: refine-changed bg #4a4a00 fg #efef80, changed bg #363300 fg #efef80. modus-operandi: refine-changed bg #fac090 fg #553d00, changed bg #ffdfa9 fg #553d00. Side-by-side legibility render: [[file:assets/2026-06-07-dupre-diff-face-legibility-compare.png][assets/2026-06-07-dupre-diff-face-legibility-compare.png]]. -** DONE [#B] F7 diff-aware coverage classifies every changed file "not tracked" :bug:solo: -CLOSED: [2026-06-22 Mon] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -Fixed 2026-06-22: simplecov keys are absolute, git-diff keys repo-relative, so the exact-key intersect never matched. Added =cj/--coverage-relativize-keys= and normalize both tables to repo-relative in =cj/--coverage-read-and-display= before the intersect; intersect unchanged. New =test-coverage-core--relativize-keys.el= (5 unit + 1 integration through the real parsers). Full suite green. -=modules/coverage-core.el:252= — =cj/--coverage-intersect= joins covered×changed by exact string key, but simplecov.json keys are ABSOLUTE paths while the git-diff parser returns repo-RELATIVE ones — zero matches ever, so working-tree/staged/branch scopes report ":tracked nil" for everything and F7's main feature is inert (whole-project scope works, same-source keys). Unit tests hand-build matching keys so they pass; add one integration test feeding a real undercover report + real diff. Normalize both sides to repo-relative. From the 2026-06 config audit. - ** TODO [#C] Migrate tests off mocking primitives (native-comp robustness) :test:refactor: :PROPERTIES: :LAST_REVIEWED: 2026-06-21 @@ -3678,17 +3533,6 @@ Tie this into the existing coverage work: - Tests cover adapter detection, command building, scope resolution, result storage, and key interactive paths. -** DONE [#B] jumper: register collisions and dead-marker errors :bug:solo: -CLOSED: [2026-06-22 Mon] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-13 -:END: -Fixed 2026-06-22: (1) store now allocates the first unused register char in the live slice (=jumper--first-free-register=) instead of by next-index, and removal clears the freed register, so a store after a removal no longer overwrites a surviving slot's marker; (2) =jumper--with-marker-at= guards =(buffer-live-p (marker-buffer marker))= so killed-buffer entries are skipped instead of signaling wrong-type errors; (3) the single-location toggle jumps back to the last-location register when set (returns =jumped-back=). New =test-jumper--register-hygiene.el= (8 tests); all 42 jumper tests green. Pre-existing unused-lexical =i= warning in =jumper--location-exists-p= left alone (separate nit). -Two related defects from the 2026-06 config audit: -- =modules/jumper.el:155= — removal shifts the vector without renumbering registers, so a later store allocates a register still held by a surviving location and silently overwrites it. Allocate the first free register char in the live slice; =set-register nil= on removal so freed markers don't pin buffers. -- =modules/jumper.el:117,132= — guards check =(markerp marker)= but not =(buffer-live-p (marker-buffer marker))=; after killing a buffer holding a location, M-SPC SPC and M-SPC j signal wrong-type errors. Treat dead entries as skippable/removable. -Also =jumper.el:178= — the promised single-location toggle never toggles back ('already-there branch should =jump-to-register= z when set). - ** TODO [#B] Keymap consolidation — resolve decisions, run Phase 1-2 :feature:refactor:solo: :PROPERTIES: :LAST_REVIEWED: 2026-06-13 @@ -3742,12 +3586,6 @@ F2 is the universal preview key. Currently bound only in markdown-mode (markdown Keep the binding mode-local so F2 stays available as a global candidate where no preview makes sense. -** DONE [#C] face-diagnostic: face-name buttons + header allowlist :feature:quick:solo: -CLOSED: [2026-06-24 Wed] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-21 -:END: -Done 2026-06-24: (a) =cj/--face-diag-face-button= renders each real face name in the report as a =buttonize='d button that runs =describe-face= on it (carries the face as button-data); anonymous specs and non-faces stay plain. Routed through the stack, overlay, remap, and provenance render sites. (b) Added =face-diagnostic= to =test-init-header--classified-modules= (it's required in init.el and already carries the header contract). 5 new ERT tests; button text properties confirmed live in a rendered *Face Diagnosis* buffer. Click/RET sign-off is a VERIFY under Manual testing and validation. Spec: [[id:98f065cf-8bd5-46a0-ac24-da94d66855ad][face-font-diagnostic-popup-spec-implemented.org]]. ** TODO [#C] Gold text in auto-dimmed buffers :bug: :PROPERTIES: :LAST_REVIEWED: 2026-06-21 @@ -3765,13 +3603,6 @@ From the 2026-06-11 brainstorm. Goal: keep [[file:~/sync/org/contacts.org][conta :END: From the 2026-06-11 messenger-unification brainstorm. Google Voice has no official API; the viable routes ride the Matrix bridge ecosystem's reverse engineering (mautrix-gvoice). Research pass to establish the 2026 state of play: (1) is mautrix-gvoice healthy and what does its auth flow look like now; (2) any better-maintained alternative (CLI/daemon) for the signel-pattern architecture (external daemon + JSON-RPC + thin Emacs chat client); (3) does call initiation (ring-linked-phone-then-connect, Emacs as dialer) survive in the current protocol — two-way audio in Emacs is out of scope (WebRTC); (4) ToS/account-flag risk assessment for Craig's account. Output: a recommendation doc in docs/design/ naming the architecture (signel-pattern daemon vs Matrix bridge + ement.el) or a no-go with reasons. If go, GV becomes a registered backend under the messenger-unification convention (see the [#B] task below). -** DONE [#C] latexmk workflow never activates (two breaks) :bug:quick:solo: -CLOSED: [2026-06-24 Wed] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-21 -:END: -Done 2026-06-24: changed the :hook key from =TeX-mode-hook= to =TeX-mode= (use-package appends "-hook" only to non-"-mode" symbols, so this now registers on the real =TeX-mode-hook= instead of the unbound =TeX-mode-hook-hook=), and auctex-latexmk from =:defer t= to =:after tex= so =auctex-latexmk-setup= runs when AUCTeX loads. Confirmed both breaks via macroexpand (the dump showed =add-hook 'TeX-mode-hook-hook= before, =TeX-mode-hook= after). 2 new regression ERT tests; live-verified in a real .tex buffer: =TeX-command-default= is "latexmk" and "LatexMk" is in =TeX-command-list=. Actual C-c C-c compile is a VERIFY under Manual testing and validation. From the 2026-06 config audit. - ** TODO [#C] Org-noter custom workflow — fix and finish :feature:bug: :PROPERTIES: :LAST_REVIEWED: 2026-06-21 @@ -3878,36 +3709,6 @@ These may override useful defaults - review and pick better bindings: :END: Display slack.el message and thread buffers in a dedicated popup window (side or bottom) and reuse that one window instead of spawning a new window per buffer. Likely a =display-buffer-alist= rule (or popper integration) in =modules/slack-config.el=. -** CANCELLED [#C] the preview splits an already split window into 3 temporarily. :bug: -CLOSED: [2026-06-21 Sun] -looks strange. potentially problematic for ai-terms. - -** CANCELLED [#C] TRAMP/dirvish "?" for remote dates — verify the fix per host :bug: -CLOSED: [2026-06-21 Sun] -:PROPERTIES: -:LAST_REVIEWED: 2026-06-02 -:END: - -Root cause is traced (see the dated investigation entry below). What's left needs a live remote: open each remote host in dirvish and run the three diagnostic evals to find which gate is closed, then close it. - -Diagnostics (run with point in a remote dirvish buffer): -- =M-: (dirvish-prop :remote-async)= — nil means =tramp-direct-async-process-p= is failing for this method/host, so dirvish's remote attribute fetch never runs. -- =M-: (dirvish-prop :gnuls)= — nil means the remote has no GNU =ls= (the =ls --version= probe failed), so the parser gate stays shut. Likely on truenas (FreeBSD). -- =M-: (tramp-direct-async-process-p)= — confirms whether direct-async is actually active for the connection. - -Likely fixes, by which gate is closed: -- =:gnuls= nil → install GNU coreutils on the remote (FreeBSD: =pkg install coreutils=) and make =ls= resolve to GNU on the TRAMP path, or accept "?" on that host. - - - Constraint: nothing gets installed on the remote host, so the =:gnuls= gate is resolved by accepting "?" on that host rather than installing coreutils. -- =:remote-async= nil → the scp/sshx method isn't advertising direct-async; switch to a method that supports it or check =tramp-direct-async-process= is taking effect for that protocol. - -Files involved: =modules/tramp-config.el=, =modules/dirvish-config.el=. - -*** 2026-05-22 Fri @ 20:24:44 -0500 Traced the root cause through dirvish source -Remote dates/sizes don't come from the dired =ls= listing or =dired-listing-switches=. They come from =dirvish-data-for-dir= (=dirvish-tramp.el:95=), which runs =ls -1lahi= on the remote and parses the columns into the attribute cache. That method only fires when both =(dirvish-prop :remote-async)= is a number and =(dirvish-prop :gnuls)= is a string. When either gate is shut, dirvish falls back to its default, which deliberately skips =(file-attributes f-name)= for remote files (=dirvish.el:904=, a perf guard) — leaving attrs nil, so the file-size and file-time widgets render "?" (=dirvish-widgets.el:216,247=). - -That explains why every prior fix missed: dired-listing-switches feed a different code path entirely, and disabling =tramp-direct-async-process= shuts the =:remote-async= gate, which is the one path that populates remote attributes — exactly backwards. The config already enables direct-async for ssh/sshx (=tramp-config.el:79-88=), so the remaining closed gate is per-host: =:gnuls= (no GNU ls on FreeBSD-based truenas) or direct-async not taking effect for the method. Could not verify on a live remote from the work session — handed the per-host diagnostics up into the task body. - ** TODO [#D] Dashboard over-scroll: pin last line to window bottom :bug: :PROPERTIES: :LAST_REVIEWED: 2026-05-22 @@ -9033,3 +8834,192 @@ Re-reviewed [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghost ** DONE [#A] erc-yank silently publishes >5-line pastes as public gists :bug:quick:solo: CLOSED: [2026-06-20 Sat] Dropped erc-yank 2026-06-20 (Craig's call: drop, not harden). The package turned a >5-line paste into a PUBLIC gist (=gist -P=, the clipboard-paste flag, no =--private=) behind a single y-or-n-p, with no executable-find guard for =gist=. It also gisted the system clipboard rather than the kill-ring text being yanked. No replacement binding needed: =erc-mode-map= defines no C-y of its own, so removing the package lets C-y fall through to the ordinary global =yank=. Verified live: effective C-y in an ERC buffer = =yank=. (Audit's "no confirmation" was slightly off — the package did prompt — but public-by-default + one-keystroke confirm + no guard made dropping it the clean fix.) +** DONE [#B] C-<left>/<right>/<down> wrongly enter terminal copy-mode :bug:quick: +CLOSED: [2026-06-24 Wed] +Fixed 2026-06-24: per Craig, only C-<up> enters copy-mode now — all other arrows (C-<down>/<left>/<right> and the M-arrows) were dropped from both the ghostel-mode-map binding and ghostel-keymap-exceptions in modules/term-config.el, so C-<left>/C-<right> reach the shell as readline word-motion again. Also per Craig: C-<up> pressed while already in copy-mode just moves up — cj/term-copy-mode-up checks tmux pane_in_mode (and ghostel--input-mode without tmux) and skips re-entry, which would otherwise reset the cursor. 6 ERT tests rewritten; byte-compile clean; the live daemon was stripped of the stale bindings/exceptions and reloaded (C-<up> bound + an exception, C-<left> forwarded to the pty). Real-terminal scroll is the VERIFY under Manual testing and validation. +** DONE [#B] ai-term wrap-teardown + shutdown functions :feature: +CLOSED: [2026-06-24 Wed] +Done 2026-06-24: added the three headless functions to =modules/ai-term.el= per the rulesets contract — =cj/ai-term-quit= (kill aiv- session + agent buffer + restore layout, idempotent), =cj/ai-term-live-count= (integer gate), =cj/ai-term-shutdown-countdown= (gate re-check → abort-able run-at-time countdown → =cj/ai-term-shutdown-command=, a defcustom). Reused the existing kill/close helpers. 13 ERT tests (live-count parsing, quit kill+idempotency, gate-abort/cancel/tick); byte-compile + validate-modules + launch smoke clean; headless contracts verified live in the daemon (live-count→3, quit no-op returns the session name, countdown aborted with sessions live — no shutdown). The tmux/shutdown side effects and the both-sides end-to-end are a VERIFY under Manual testing and validation. Original task body: +The .emacs.d half of the rulesets wrap-it-up teardown / shutdown feature. Implement three functions in =modules/ai-term.el=, all callable headlessly via =emacsclient -e= (no interactive frame): =cj/ai-term-quit "<project>"= (teardown a project's aiv- tmux session + buffer + geometry restore), =cj/ai-term-live-count= (integer, the safety gate), =cj/ai-term-shutdown-countdown= (run-at-time timer). Craig's 2026-06-23 decisions: non-destructive qualifier = "with summary"/"and summarize"; countdown is a run-at-time timer (not a tty writer); safety gate uses cj/ai-term-live-count. Lands with the rulesets half (workflow + Stop hook already built/pushed). Spec: =inbox/PROCESSED-2026-06-23-2331-from-rulesets-ai-term-teardown-companion.org= (rulesets proposal: docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org). Own focused session. +** DONE [#C] README holistic pass +CLOSED: [2026-06-24 Wed] +Holistic pass over README.org, changes approved by Craig: bumped the Emacs floor to 30 (developed on 30.2); corrected the module count (~100 → ~120); added docs/ to the layout and reworded scripts/ (now also theme-studio); added Theme Studio, the ghostel native terminal, and ai-term to Features; added make coverage-summary to the dev targets. From the roam inbox. +** DONE [#B] Theme-driven nerd-icons colors + filetype legend :feature: +CLOSED: [2026-06-24 Wed] +Dropped the runtime nerd-icons tint so icon color is theme-driven, and added a +theme-studio filetype-legend representation over the 34 =nerd-icons-*= color +faces. Spec: +[[file:docs/specs/theme-studio-nerd-icons-colors-spec.org][theme-studio-nerd-icons-colors-spec.org]]. +Three Codex spec-review rounds (3 + 6 + 1 findings) incorporated; findings +[10/10], decisions [6/6]. Ready confirmed 2026-06-24 and implemented in a +no-approvals speedrun as the four dated phases below — full run-tests.sh and +=make test= green, all pushed. Live visual confirmation is a VERIFY under +Manual testing and validation. vNext follow-ups promoted to their own [#D] task. +*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 1 — legend capture shipped +=scripts/theme-studio/build-nerd-icons-legend.el= resolves the 13 v1 rows from the live nerd-icons alists into =nerd-icons-legend.json= (committed); =generate.py='s =load_nerd_icons_legend= validates and falls back to the generic app on absent/malformed/empty/bad-row, with a warning. 7 Python tests. Committed (feat phase 1). +*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 2 — bespoke legend preview shipped +nerd-icons registers as a bespoke app whenever the legend is valid (=add_nerd_icons_app=); =renderNerdIconsPreview= draws each row's glyph in its mapped face color through the shared registry, so recolor repaints live; the 34 faces stay editable. =#nerdiconstest= gate covers the wiring, the dir-row owner, and the recolor-repaint. Committed (feat phase 2). +*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 3 — tint removed, theme drives color +Removed =cj/nerd-icons-tint-color= + =cj/--nerd-icons-color-faces= + =cj/nerd-icons-apply-tint= and both call sites from =nerd-icons-config.el=; the WIP theme already owned the 34 faces (theme-studio auto-discovered them), so color is theme-driven now. Kept =cj/--nerd-icons-color-dir=. Deleted the apply-tint test. validate-modules + launch smoke clean. Committed (feat phase 3). +*** 2026-06-24 Wed @ 05:54:34 -0400 Phase 4 — dir-precedence probe + round-trip +ERT probe locks the dir-precedence decision (prepended =nerd-icons-yellow= is first in the face list, wins over =nerd-icons-completion-dir-face=); =#nerdiconstest= extended with the export/import round-trip over an assigned nerd-icons color and a dir-face-stays-out check. Full run-tests.sh + =make test= green. Committed (test phase 4). Live visual is the VERIFY under Manual testing. +** DONE [#B] ai-term keybinding home :feature: +CLOSED: [2026-06-23 Tue] +Done 2026-06-23 (commit be772bc0): family moved to C-; a (a toggle, s select/launch, n next, k kill), swap also on M-SPC, F9 family retired, jumper's M-SPC binding removed (rehome pending). cj/ai-term-next now opens the picker when no agent is running instead of erroring. Bindings verified live in the daemon; Craig's hands-on check is filed under Manual testing and validation. +Move the ai-term commands off the F9 family. F9 sits somewhere semi-dangerous +to hit, and F8 (org-agenda) is slow to load, which reads as Emacs being +unresponsive. Craig wants three commands on an easy near-home-row chord: open +the ai-term selection menu, switch to the next agent, and kill the current one +(=cj/ai-term=, =cj/ai-term-next=, =cj/ai-term-close=). Explore C-, M-, and C-M- +with SPC. Likely collides with jumper, but ai-term is used far more, so jumper +yields. Archiving gptel this session freed the =C-; a= prefix, so the whole +ai-term family could live under =C-; a= (or another near-home-row key). +Related: the s-F9 detached-agent landing task and the tmux copy-mode binding +task elsewhere in this section. From the roam inbox. +** DONE [#C] Face coloring completion-read icons :quick:solo: +CLOSED: [2026-06-23 Tue] +Answered 2026-06-23 (investigation, no code change). There is no single +"completion icon" face — each icon inherits a per-type =nerd-icons-*= color +face (a .el file icon inherits =nerd-icons-purple=, an M-x command icon +=nerd-icons-blue=, etc.; nerd-icons picks the face per glyph/filetype). What +makes every completion icon render the SAME color here is this config's bulk +tint: =cj/nerd-icons-tint-color= (defcustom in =nerd-icons-config.el=, default +"darkgoldenrod") sets the foreground of all ~33 =nerd-icons-*= color faces via +=cj/nerd-icons-apply-tint=, applied in the =nerd-icons= =:config=. Verified live: +=nerd-icons-icon-for-file "init.el"= -> =:inherit nerd-icons-purple=, and that +face's foreground is "darkgoldenrod". Directory icons additionally get +=nerd-icons-yellow= layered on by =cj/--nerd-icons-color-dir= advice +(=nerd-icons-completion-dir-face= is unset, so it isn't the driver here). +To theme: change =cj/nerd-icons-tint-color= (one color for all icons, then call +=cj/nerd-icons-apply-tint=), or drop the bulk tint and set the individual +=nerd-icons-*= color faces for per-filetype colors. For theme-studio, the knob +to expose is =cj/nerd-icons-tint-color= plus the =nerd-icons-*= face family. +** DONE [#C] Org formatting inside cj comments :feature: +CLOSED: [2026-06-23 Tue] +Done 2026-06-23: mapped the "cj:" src-block language to org-mode via +=org-src-lang-modes= in =org-babel-config.el=. Effect: a cj comment block's +prose now gets org font-lock in place (links, *bold*, lists styled — verified +live, the link inside a block carries the =org-link= face), and =C-c '= opens a +full org-mode buffer to edit it. Approach A from the design walk: non-breaking, +the =cj:= grep marker and the whole cj-processing pipeline are unchanged. The +block stays a src block, so org's parser still treats its body as code — links +are followed from the =C-c '= buffer rather than clicked in place. If that +in-place limitation bites, Approach B (migrate to a =#+begin_cj= special block) +is the documented escalation. +Craig writes free-form prose inside cj comment blocks (=#+begin_src cj: ...=) +and wants org formatting available there. +From the roam inbox. +** DONE [#C] term: M-<arrow> enters tmux copy-mode :feature: +CLOSED: [2026-06-24 Wed] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-22 +:END: +Done 2026-06-24: C-<up>/<down>/<left>/<right> and M-<arrow> in =ghostel-mode-map= enter copy-mode and carry their direction in one stroke (=cj/term-copy-mode-up= & friends -> =cj/term-copy-mode-move= -> =cj/term-copy-mode-dwim= then =cj/--term-copy-mode-move-step=). tmux path writes the arrow escape sequence into the pty; non-tmux path moves point in =ghostel-copy-mode=. All 8 keys added to =ghostel-keymap-exceptions= + =ghostel--rebuild-semi-char-keymap= (the gotcha). Ghostel-only. 6 new ERT tests; bindings + exceptions + the dwim sequence verified live in the daemon. The real tmux copy-mode scroll is a VERIFY under Manual testing and validation. + +Folded 2026-06-23 from the roam inbox: Craig also wants C-<up> (control + up arrow) to enter tmux copy-mode and move up in one stroke — i.e. a modified arrow both enters copy-mode and passes the movement (copy-mode + arrow). So the binding set is the modified arrow keys (M-arrow and/or C-arrow), each entering copy-mode and carrying its own direction. +** CANCELLED [#C] page-signal pager account deregistered — re-registration needs your hands +CLOSED: [2026-06-21 Sun] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-12 +:END: +Reported by .emacs.d 2026-06-12 01:01: the dedicated pager number (+15045173983, the Claude Pager Google Voice number on signal-cli) returns "User ... is not registered" on every send — Signal appears to have deregistered it (GV numbers get periodically re-verified). Re-registration requires captcha/SMS, which only you can do. Until then every page-signal call fails; .emacs.d's config-audit page fell back to email. Wrapper lives at claude-templates/bin/page-signal. +** DONE [#B] mu4e: cmail can't trash, no account can refile :bug:quick:solo: +CLOSED: [2026-06-24 Wed] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-13 +:END: +=modules/mail-config.el:217-220= — the cmail context (primary account) sets only drafts/sent, so D falls back to default "/trash" which doesn't exist under ~/.mail (=/cmail/Trash= does); and NO context sets =mu4e-refile-folder=, so r targets nonexistent "/archive" everywhere. Accepting mu4e's offer to create the maildir strands mail in a directory mbsync never syncs — messages silently vanish from the server's view. Add =mu4e-trash-folder= to cmail + per-context =mu4e-refile-folder=. From the 2026-06 config audit. +Fixed 2026-06-13: cmail gets =mu4e-trash-folder= "/cmail/Trash"; refile is a per-message function (=cj/mu4e--refile-folder=) instead of a per-context string — mu4e context :vars are sticky, so a per-context refile leaks one account's archive folder into another. cmail → "/cmail/Archive"; gmail/dmail signal a =user-error= rather than move mail into an unsynced phantom folder (Craig chose the fail-safe over syncing [Gmail]/All Mail — the All Mail option means a multi-GB pull + cross-folder duplicates; revisit if local Gmail archiving is wanted). Applies on next mu4e open; pure dispatch helper covered by tests. +** CANCELLED [#C] Lock screen silently fails — slock is X11-only :bug:quick: +CLOSED: [2026-06-21 Sun] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-13 +:END: +=modules/system-commands.el:105= binds the lockscreen command to =slock=, which can't grab a Wayland session; =cj/system-cmd= launches it detached with output silenced, so C-; ! l does nothing and the screen never locks. Security issue: Craig believes the screen locks when it doesn't. Fix: =hyprlock= (or =swaylock=), ideally resolved per session type via =env-wayland-p= so an X11 fallback survives for other machines. From the 2026-06 config audit. +Fixed 2026-06-13: lockscreen-cmd resolves to =loginctl lock-session= on Wayland (logind Lock → hypridle → hyprlock, the path idle/sleep locking already uses), =slock= on X11; also added the missing =(require 'host-environment)=. Live in the daemon; manual lock test under the Manual testing parent. +** CANCELLED [#B] AI Open Work +CLOSED: [2026-06-23 Tue] +gptel archived 2026-06-23 to archive/gptel/ (rarely used). The child issues below — ai-rewrite directive plumbing, ai-conversations bugs, the stale-elpa / gptel-magit shadow, model-switch dedup — are all moot against archived code. Kept for reference; detail also in git history. +*** CANCELLED [#B] ai-rewrite: chosen directive never reaches the request :bug:solo: +=modules/ai-rewrite.el:64= — the directive is let-bound around =(call-interactively #'gptel-rewrite)=, but gptel-rewrite is a transient prefix that returns when the menu shows; the send resolves the directive AFTER the binding unwound (verified against ~/code/gptel/gptel-rewrite.el:780-799). The picker's choice is silently dropped — the module's core feature is inert. Set =gptel--rewrite-directive= buffer-locally (restore via =gptel-post-rewrite-functions=) or use a self-removing global hook entry. From the 2026-06 config audit. + +*** CANCELLED [#B] Stale elpa gptel shadows the local fork — likely the gptel-magit root :bug:quick:solo:next: +Needs from Craig: can't be done standalone. I tried deleting elpa/gptel-0.9.8.5 — the fork loaded fine and gptel-magit still worked via use-package autoloads, but package activation then printed "Unable to activate gptel-magit / Required gptel-0.9.8 unavailable" on every startup, so I reverted. To remove the shadow we must also resolve gptel-magit's package dependency: either drop gptel-magit's package dep (load it via load-path like the gptel fork), or repackage the fork into .localrepo as gptel. Tell me which and I'll do it; this pairs with the gptel-magit investigation. +=elpa/gptel-0.9.8.5= is still installed alongside the =~/code/gptel= fork (=ai-config.el:383=); package activation puts the elpa dir + autoloads on load-path, so which copy wins depends on ordering, and a mixed load (fork .el + elpa .elc) produces "impossible" bugs. =gptel-magit= (elpa) declares gptel as a dependency, so IT may be pulling the stale copy — check this first when working the open "[#B] Investigate gptel-magit not working properly" task. Fix: =package-delete= the elpa gptel + remove from .localrepo so the fork is the only copy on disk. From the 2026-06 config audit. + +2026-06-15: tried deleting =elpa/gptel-0.9.8.5= standalone. The fork loaded correctly and gptel-magit still worked via use-package =:commands= autoloads, BUT package activation then printed "Unable to activate package gptel-magit / Required package gptel-0.9.8 unavailable" on every startup and test run (gptel-magit declares gptel as a package dependency that no longer resolves). Reverted. This can't be done standalone — it must be paired with the gptel-magit dependency fix (drop gptel-magit's package dep, or repackage the fork into .localrepo as gptel). Do it together with the gptel-magit investigation task. + +*** CANCELLED [#B] ai-conversations: dead-buffer load, role flattening, non-atomic writes :bug:solo: +From the 2026-06 config audit, =modules/ai-conversations.el=: +- =:324= — load in a fresh session does =get-buffer-create "*AI-Assistant*"= (plain fundamental-mode buffer); =--ensure-ai-buffer= then sees it exists and never calls =(gptel)=. Sending doesn't work, autosave self-cancels (requires gptel-mode). Use =get-buffer= for the check; let ensure create. The browser RET/l path inherits this. +- =:240= — persistence drops gptel's =response= text properties, so a reloaded history replays to the model as ONE user message (model re-reads its own answers as Craig's words). Adopt gptel's native bounds persistence or re-mark on load from the "* Backend:" headings. +- =:248= — =write-region= straight at the target; crash mid-write truncates the only copy of the history (autosave hits this constantly). Temp + rename. +- =:140= — three overlapping autosave mechanisms (after-send advice that fires before the response exists, post-response hook, 60s timer). Keep the hook; drop the advice (and likely the timer). + +*** CANCELLED [#B] Dedup gptel model-switch commands — keep switch-backend or fold into change-model :bug: +=cj/gptel-change-model= (C-; a m) already does backend+model switching and interns correctly, so =cj/gptel-switch-backend= (C-; a B) is arguably redundant now that its crash is fixed. Decision for Craig: keep both, or delete =cj/gptel-switch-backend= plus its C-; a B binding and keep one model-switch command. From the 2026-06 config-audit follow-up. +** DONE [#B] agenda sources: roam Projects missing, no existence filtering :bug:solo: +CLOSED: [2026-06-24 Wed] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +Done 2026-06-24, both parts: (1) per Craig, corrected the docs rather than implementing roam-Project agenda scanning — the commentary + two docstrings claimed org-roam "Project" nodes are agenda sources, but they were never scanned; roam Project/Topic notes are refile targets (org-refile-config.el), not agenda sources. (2) =cj/--org-agenda-base-files= now drops non-existent files and =org-agenda-skip-unavailable-files= is set as a backstop, in the one shared helper so the agenda builders, single-project view, and chime initializer all get it. base-files tests reworked to drive real temp files (+ a drops-missing case); byte-compile clean; live-verified (skip var t, base-files returns only existing). From the 2026-06 config audit, =modules/org-agenda-config.el=: +- =:182-191= — commentary and docstrings promise org-roam nodes tagged "Project" as agenda sources, but =cj/--org-agenda-scan-files= never scans them, and files added by the roam finalize-hook are wiped on the next =cj/build-org-agenda-list= cache rebuild (≤1h). Add a roam Project pass (mirror =org-refile-config.el:101-109=) or correct the docs. +- =:186,456= — agenda file list built unconditionally (inbox/calendars may not exist on a fresh machine) and =org-agenda-skip-unavailable-files= is unset — the exact interactive-prompt class that once hung the chime daemon. Filter with =file-exists-p= + set the var as backstop. +** DONE [#B] F7 diff-aware coverage classifies every changed file "not tracked" :bug:solo: +CLOSED: [2026-06-22 Mon] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +Fixed 2026-06-22: simplecov keys are absolute, git-diff keys repo-relative, so the exact-key intersect never matched. Added =cj/--coverage-relativize-keys= and normalize both tables to repo-relative in =cj/--coverage-read-and-display= before the intersect; intersect unchanged. New =test-coverage-core--relativize-keys.el= (5 unit + 1 integration through the real parsers). Full suite green. +=modules/coverage-core.el:252= — =cj/--coverage-intersect= joins covered×changed by exact string key, but simplecov.json keys are ABSOLUTE paths while the git-diff parser returns repo-RELATIVE ones — zero matches ever, so working-tree/staged/branch scopes report ":tracked nil" for everything and F7's main feature is inert (whole-project scope works, same-source keys). Unit tests hand-build matching keys so they pass; add one integration test feeding a real undercover report + real diff. Normalize both sides to repo-relative. From the 2026-06 config audit. +** DONE [#B] jumper: register collisions and dead-marker errors :bug:solo: +CLOSED: [2026-06-22 Mon] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-13 +:END: +Fixed 2026-06-22: (1) store now allocates the first unused register char in the live slice (=jumper--first-free-register=) instead of by next-index, and removal clears the freed register, so a store after a removal no longer overwrites a surviving slot's marker; (2) =jumper--with-marker-at= guards =(buffer-live-p (marker-buffer marker))= so killed-buffer entries are skipped instead of signaling wrong-type errors; (3) the single-location toggle jumps back to the last-location register when set (returns =jumped-back=). New =test-jumper--register-hygiene.el= (8 tests); all 42 jumper tests green. Pre-existing unused-lexical =i= warning in =jumper--location-exists-p= left alone (separate nit). +Two related defects from the 2026-06 config audit: +- =modules/jumper.el:155= — removal shifts the vector without renumbering registers, so a later store allocates a register still held by a surviving location and silently overwrites it. Allocate the first free register char in the live slice; =set-register nil= on removal so freed markers don't pin buffers. +- =modules/jumper.el:117,132= — guards check =(markerp marker)= but not =(buffer-live-p (marker-buffer marker))=; after killing a buffer holding a location, M-SPC SPC and M-SPC j signal wrong-type errors. Treat dead entries as skippable/removable. +Also =jumper.el:178= — the promised single-location toggle never toggles back ('already-there branch should =jump-to-register= z when set). +** DONE [#C] face-diagnostic: face-name buttons + header allowlist :feature:quick:solo: +CLOSED: [2026-06-24 Wed] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-21 +:END: +Done 2026-06-24: (a) =cj/--face-diag-face-button= renders each real face name in the report as a =buttonize='d button that runs =describe-face= on it (carries the face as button-data); anonymous specs and non-faces stay plain. Routed through the stack, overlay, remap, and provenance render sites. (b) Added =face-diagnostic= to =test-init-header--classified-modules= (it's required in init.el and already carries the header contract). 5 new ERT tests; button text properties confirmed live in a rendered *Face Diagnosis* buffer. Click/RET sign-off is a VERIFY under Manual testing and validation. Spec: [[id:98f065cf-8bd5-46a0-ac24-da94d66855ad][face-font-diagnostic-popup-spec-implemented.org]]. +** DONE [#C] latexmk workflow never activates (two breaks) :bug:quick:solo: +CLOSED: [2026-06-24 Wed] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-21 +:END: +Done 2026-06-24: changed the :hook key from =TeX-mode-hook= to =TeX-mode= (use-package appends "-hook" only to non-"-mode" symbols, so this now registers on the real =TeX-mode-hook= instead of the unbound =TeX-mode-hook-hook=), and auctex-latexmk from =:defer t= to =:after tex= so =auctex-latexmk-setup= runs when AUCTeX loads. Confirmed both breaks via macroexpand (the dump showed =add-hook 'TeX-mode-hook-hook= before, =TeX-mode-hook= after). 2 new regression ERT tests; live-verified in a real .tex buffer: =TeX-command-default= is "latexmk" and "LatexMk" is in =TeX-command-list=. Actual C-c C-c compile is a VERIFY under Manual testing and validation. From the 2026-06 config audit. +** CANCELLED [#C] the preview splits an already split window into 3 temporarily. :bug: +CLOSED: [2026-06-21 Sun] +looks strange. potentially problematic for ai-terms. +** CANCELLED [#C] TRAMP/dirvish "?" for remote dates — verify the fix per host :bug: +CLOSED: [2026-06-21 Sun] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-02 +:END: + +Root cause is traced (see the dated investigation entry below). What's left needs a live remote: open each remote host in dirvish and run the three diagnostic evals to find which gate is closed, then close it. + +Diagnostics (run with point in a remote dirvish buffer): +- =M-: (dirvish-prop :remote-async)= — nil means =tramp-direct-async-process-p= is failing for this method/host, so dirvish's remote attribute fetch never runs. +- =M-: (dirvish-prop :gnuls)= — nil means the remote has no GNU =ls= (the =ls --version= probe failed), so the parser gate stays shut. Likely on truenas (FreeBSD). +- =M-: (tramp-direct-async-process-p)= — confirms whether direct-async is actually active for the connection. + +Likely fixes, by which gate is closed: +- =:gnuls= nil → install GNU coreutils on the remote (FreeBSD: =pkg install coreutils=) and make =ls= resolve to GNU on the TRAMP path, or accept "?" on that host. + + - Constraint: nothing gets installed on the remote host, so the =:gnuls= gate is resolved by accepting "?" on that host rather than installing coreutils. +- =:remote-async= nil → the scp/sshx method isn't advertising direct-async; switch to a method that supports it or check =tramp-direct-async-process= is taking effect for that protocol. + +Files involved: =modules/tramp-config.el=, =modules/dirvish-config.el=. + +*** 2026-05-22 Fri @ 20:24:44 -0500 Traced the root cause through dirvish source +Remote dates/sizes don't come from the dired =ls= listing or =dired-listing-switches=. They come from =dirvish-data-for-dir= (=dirvish-tramp.el:95=), which runs =ls -1lahi= on the remote and parses the columns into the attribute cache. That method only fires when both =(dirvish-prop :remote-async)= is a number and =(dirvish-prop :gnuls)= is a string. When either gate is shut, dirvish falls back to its default, which deliberately skips =(file-attributes f-name)= for remote files (=dirvish.el:904=, a perf guard) — leaving attrs nil, so the file-size and file-time widgets render "?" (=dirvish-widgets.el:216,247=). + +That explains why every prior fix missed: dired-listing-switches feed a different code path entirely, and disabling =tramp-direct-async-process= shuts the =:remote-async= gate, which is the one path that populates remote attributes — exactly backwards. The config already enables direct-async for ssh/sshx (=tramp-config.el:79-88=), so the remaining closed gate is per-host: =:gnuls= (no GNU ls on FreeBSD-based truenas) or direct-async not taking effect for the method. Could not verify on a live remote from the work session — handed the per-host diagnostics up into the task body. |
