diff options
Diffstat (limited to 'todo.org')
| -rw-r--r-- | todo.org | 1731 |
1 files changed, 884 insertions, 847 deletions
@@ -55,263 +55,19 @@ 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 -** 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: -Three projects shipped themeable faces and asked theme-studio to render accurate previews. Data lives in the PROCESSED handoff files. -*** TODO pearl — 6 faces + overlay-driven appearance -Six faces in the =pearl= customize group plus overlay-driven appearance a raw buffer read won't show. =inbox/PROCESSED-2026-06-23-2239-from-pearl-theme-studio-pearl-spec.org= + cover + =sample-pearl-buffer.org=. -*** TODO emacs-wttrin — 4 new faces -Was hardcoded "gray60"; now four customizable faces (branch =feature/themeable-faces=). =inbox/PROCESSED-2026-06-23-2253-from-emacs-wttrin-wttrin-faces-handoff.org= + rendered sample. -*** TODO chime — 4 themeable modeline faces -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. -** 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. -** TODO [#C] VAMP — extract music-config into a standalone player :feature:refactor: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-21 -:END: -Build VAMP ("VAMP Audio Music Player"), a standalone, publishable Emacs music player at =~/code/vamp= — derived from a maintained subset of EMMS, depending on the EMMS package not at all, with MPV and mpd behind a generalized adapter API. =.emacs.d= keeps thin glue (=vamp-config.el=: keybindings, paths, dashboard); archsetup owns OS wiring (Super+/ launcher, m3u MIME). Models the =linear-config= → =pearl= migration. - -Brainstorm complete 2026-06-22 — validated design at [[file:docs/design/vamp-music-player.org][docs/design/vamp-music-player.org]]. It builds on the prior EMMS-removal work ([[file:docs/specs/music-config-without-emms-spec.org][spec]] + [[file:docs/design/music-config-without-emms-review.org][2026-05-15 review]]), confirming its B1/B2/B4/S3 decisions and pivoting four things (publishable-now, two adapters + generalized API, VAMP name, desktop integration). - -Next: (1) revise the spec to the new direction; (2) spike the risky assumptions (mpd dumb-single-file-player contract; m3u =.desktop= on Hyprland); (3) =/start-work= against the revised spec — pure-helper extraction (review Migration Plan step 1) is the safe first phase. Priority [#C] is a placeholder pending Craig's call. - - -** TODO [#C] ai-term: step between running ai-terms even when detached :feature: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-22 -:END: -The step-to-next-agent family (s-F9 and friends) should cycle to a running ai-term even when that ai-term is currently detached, instead of skipping it. Today the step only lands on attached/visible ai-terms, so a detached-but-running agent gets passed over and there's no keyboard path back to it — re-attach/display it on landing. From the roam inbox. - -** TODO [#C] ai-term: multi-backend (Claude / Codex / local ollama) :feature: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-22 -:END: -Allow creating an ai-term backed by any of Claude, Codex, or a local LLM via ollama, with the backend chosen seamlessly at the start of the session. ai-term currently assumes Claude; generalize the launch path so the agent backend is a selectable parameter and switching between them at session start is frictionless. Routed here from the rulesets roam-inbox item "multiple agent source improvements" (its bullet 3 asked to send emacs this note); the item's other bullets — naming the agent so non-Claude agents aren't called "Claude", and tightening workflow wording for Codex's more literal reading — stay with rulesets. - -** TODO [#C] Compare terminal themeability: EAT vs vterm vs ghostel :feature: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-22 -: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. - -** TODO [#B] Un-pin ghostel from 0.33.0 once upstream fixes #422/#423 :bug: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -ghostel is held at 0.33.0 (=ghostel-20260604.2049=, commit 5779a2adceb2) in =modules/term-config.el= to dodge the 0.35.x native-PTY crash. When dakra/ghostel ships a fix for #422 (Linux malloc/signal reentrancy) and #423 (macOS recursive lock), restore =:ensure t= (drop the pin comment) and =package-upgrade ghostel=, then re-run the open-ghostel-in-a-GUI-frame survival check. Watch the two issues for the fixing commit. - -archsetup automated the zig 0.15.2 pin (managed =install_zig_pin= step, sha-verified, unit-tested). If the un-pinned ghostel bumps its ghostty dependency to a newer zig, send archsetup the new version + sha256 so it bumps its =ZIG_VERSION= / =ZIG_SHA256= constants (=inbox-send archsetup=). - -** VERIFY [#B] calendar-sync robustness: atomic writes, curl --fail, zero-event false errors :bug:solo:next: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -Deferred, pairs with the calendar-sync recurrence VERIFY above. The mechanical parts (write to a temp file + rename, add curl --fail, guard the zero-event case) are doable, but any calendar-sync change needs verification against a real .ics feed to avoid masking a genuine empty/failed sync. Do this together with the recurrence fix once you provide a fixture / confirm the live feed. -From the 2026-06 config audit, =modules/calendar-sync.el=: -- =:1309= — agenda file written via =with-temp-file= directly on the target (truncate-in-place); org-agenda/chime reading mid-write sees a partial calendar, hourly. Write temp + =rename-file= (atomic same-fs). Same for =--save-state= :258. -- =:1284= — curl runs without =--fail=: an HTTP 404/500 error page exits 0 and the HTML proceeds into conversion. -- =:1229-1233= — =--parse-ics= returns nil for both garbage and a valid calendar with zero in-window events, so healthy near-empty calendars report "parse failed" in =calendar-sync-status=. Distinguish the cases. - -** VERIFY [#B] org-roam :config triggers the 15-20s refile scan synchronously at first idle :bug:solo:next: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -Needs from Craig: this is measurement-first (perf), not a blind fix — it's the same bottleneck as the "optimize org-capture target building" debug task. Run /debug with debug-profiling to measure what actually costs the 15-20s (file count? regex? agenda rebuild?), then fix from the data. I won't restructure the refile/agenda scan without a profile. Say "let's debug it" and I'll profile + fix. -=modules/org-roam-config.el:78-79= — org-roam is =:defer 1=, so its :config calls =cj/build-org-refile-targets= at 1s idle, BEFORE the 5s background timer (=org-refile-config.el:144-151=); on a cold cache the 30k-file scan runs inline and freezes Emacs at first idle. Drop the call — org-roam is loaded long before the 5s timer fires. Likely a player in the filed org-capture 15-20s perf task (=[#B] Optimize org-capture target building performance=) — check both together. From the 2026-06 config audit. - -** VERIFY [#B] transcription: stderr never reaches the log, video transcripts stranded in /tmp :bug:solo:next: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -Deferred from the batch (no blocker; needs a focused pass with live verification). Plan: (1) transcription-config.el:210 — make-process :stderr with a file path creates a buffer, not a file; route stderr into the process buffer and write the captured text out in the sentinel, then drop the leaked buffer. (2) :370-374 — derive the txt/log base from the VIDEO path, not the temp mp3's /tmp path, so transcripts land alongside the source. The path-derivation half is cleanly unit-testable; the stderr half needs a real transcription run to verify, which is why I held it for a focused session rather than the batch. -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. - -** VERIFY [#C] Remove unused system-power keybindings :refactor:quick:next: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -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. - ** PROJECT [#A] Manual testing and validation Exercised once the phases above land. -*** VERIFY ai-term keybindings land on C-; a + M-SPC -What we're verifying: the relocated ai-term keys work in a live frame, including from inside an agent buffer, and the no-agent fallback launches the picker. -- Press M-SPC from a normal buffer with at least one agent open. -- Press M-SPC again from inside an agent buffer (ghostel). -- With no agent running, press M-SPC. -- Walk C-; a a, C-; a s, C-; a n, C-; a k (which-key should show the ai-term menu under C-; a). -- Press F9, C-F9, s-F9, M-F9. -Expected: M-SPC swaps to the next agent (rotating, wrapping) both from a normal buffer and from inside an agent. With no agent running, M-SPC opens the project picker rather than erroring. C-; a a toggles the most-recent agent, s opens the picker, n swaps, k closes. The F9 family does nothing (unbound). Note: the running daemon still has gptel in memory from before the archive, so a full Emacs restart is the clean confirmation that nothing regressed at startup. *** TODO theme-studio preview-locate discoverability read What we're verifying: the locate hover/flash actually feels discoverable in a live frame — the subjective read the deterministic gates can't make. - Open theme-studio in Chrome (=make theme-studio-open=, or open theme-studio.html). - Hover several preview elements across the UI mock and a package pane. - Click an on-pane element, then click an off-pane element. Expected: hovering updates the preview-label info line immediately with "section > face — value" (no wait on the native tooltip); an on-pane click scrolls to and flashes the right assignment row; off-pane elements don't respond and their title explains why. The flow reads like a legend you can interrogate. If it feels broken or unclear, note where and reopen the relevant phase. -*** VERIFY deferred game commands still work after a restart (load-graph Phase 4) -What we're verifying: with games-config no longer eagerly required, malyon and 2048-game still launch from a fresh Emacs, and games-config loads on first use rather than at startup. Batch tests cover the autoload chain; this is the interactive confirmation the spec asks for after each deferral batch. -- Restart Emacs (daemon or standalone) so games-config is no longer pre-loaded from this session. -- Confirm it's not loaded at startup: -#+begin_src emacs-lisp -(featurep 'games-config) -#+end_src -- M-x malyon — it should load games-config and the malyon package, then start interactive fiction (stories under ~/sync/org/text.games/). -- M-x 2048-game — should start the 2048 puzzle. -- Re-check (featurep 'games-config) — now non-nil. -Expected: at startup (featurep 'games-config) is nil; both commands launch normally; after invoking one, games-config is loaded. If a command errors instead of launching, capture it and reopen the deferral as a TODO. -*** VERIFY native-comp + gcmh survive a daemon restart cleanly -What we're verifying: re-enabling JIT native compilation and switching GC to gcmh holds up across a full daemon restart and a real work session. The fix is live in the current daemon and a throwaway daemon launched clean, but the already-loaded modules only get natively compiled on a fresh start (a background async burst), and the old "Selecting deleted buffer" race needs a real GUI session to rule out on 30.2. -- Restart the Emacs daemon (clean state): kill it and start fresh, or reboot. -- Use Emacs normally for a while — the first session after restart triggers background native compilation of ~100 modules. Watch for any "Selecting deleted buffer" errors or compilation crashes (check the *Async-native-compile-log* buffer and comp-warnings.log). -- After things settle, confirm the settings are live: -#+begin_src emacs-lisp -(list :jit native-comp-jit-compilation - :gcmh gcmh-mode - :gcmh-high gcmh-high-cons-threshold) -#+end_src -- Edit normally (completion, agenda, AI buffers) and notice whether the periodic GC jank is gone. -Expected: restart is clean (no backtrace); the background native-comp burst finishes without "Selecting deleted buffer" errors; the form returns (:jit t :gcmh t :gcmh-high 1073741824); editing feels smoother with no frequent GC pauses. If the async race recurs on 30.2, capture the error and reopen as a TODO — the fallback is an AOT sweep or going back to JIT-off. -*** VERIFY mu4e buffers are themed (headers, main, message view) -What we're verifying: with the mu4e modes excluded from global font-lock, mu4e's manual face properties survive, so the buffers pick up the theme. The headers + main + view-headers are the ones global font-lock was stripping. -- Restart Emacs (cleanest), or kill and reopen the mu4e buffers -- Open mu4e, look at the headers list and the main menu -- Open a message and read the body -Expected: headers list shows unread/flagged/date/subject in their theme colors (mu4e-unread-face gold, mu4e-header-face green, etc.); the main menu and the message-view headers (From/To/Subject) are themed; the message body still renders correctly (gnus does the body, so it's unaffected). NOTE: a plain "g" refresh in an already-open *mu4e-headers* won't fix it on its own unless font-lock is off there; a restart is the reliable check. -*** VERIFY C-c ; reaches the custom command family in a real terminal frame -What we're verifying: the TTY mirror prefix C-c ; reaches the same cj/custom-keymap as the GUI C-; prefix, so the whole command family works in a terminal. The unit tests + a live daemon eval already confirm both prefixes resolve to the one keymap; this is the end-to-end in an actual TTY frame, which the batch harness can't drive. -- Open a terminal Emacs frame: emacsclient -nw (or emacs -nw, or Emacs inside vterm/tmux) -- Press C-c ; L (pearl), C-c ; a (AI), C-c ; g (calendar) — the same leaf keys you use under C-; in GUI -- Confirm which-key shows the custom prefix under C-c ; -Expected: each C-c ; <leaf> runs the same command its C-; <leaf> counterpart runs in GUI; which-key lists the family under C-c ;. C-; itself stays working in GUI frames (unchanged). -*** VERIFY theme-studio gnus view package themes the article headers -What we're verifying: gnus is now its own view package in theme-studio (it drives the mu4e article view), so the bright-green article headers can be themed and exported. #gnustest confirms the package is registered and its preview emits only real gnus faces; this is the visual read plus the live-green retirement. -- Reload theme-studio (or make theme-studio-open) -- Pick "gnus (mu4e article view)" from the view dropdown (sits among the g entries) -- Confirm the preview shows a header block, an emphasized body, an 11-level quoted reply chain, and a signature -- Theme a few gnus faces (e.g. gnus-header-name, gnus-header-from, gnus-cite-1) to obvious colors, export to WIP.json, then deploy -#+begin_src sh :results output -make -C /home/cjennings/.emacs.d deploy-wip -#+end_src -- Restart Emacs (or reload the theme), reopen a mu4e message -Expected: the studio preview renders each gnus face in its theme color; after export + deploy, the *mu4e-article* From/Subject/To/Date headers show the themed colors instead of the gnus green defaults. -*** VERIFY theme-studio markdown preview reads like a real README -What we're verifying: selecting markdown-mode in the view dropdown shows a realistic README (not the generic face-name list), and the markdown faces render legibly in context. #mdtest already confirms the wiring + that every element's face is real; this is the visual read. -- Reload theme-studio (or make theme-studio-open) -- Pick "markdown-mode" from the view dropdown -Expected: a README preview with headers, bold/italic, code, links, lists/checkboxes, blockquote, table, etc., each in its theme face. Clicking an element flashes its row in the faces table. -*** VERIFY dashboard theming — banner gold, headings themed, items show per-filetype icons -What we're verifying: with the dashboard out of global font-lock (Fix A) and file icons on (Fix C), the live dashboard shows the theme colors and icons. Eyeball it. -- Open the dashboard (F1) -Expected: the "Emacs:" banner title is gold, the "Projects:/Bookmarks:/Recent Files:" headings are themed blue, and the project/recent-file rows each show a colored per-filetype icon (org files greenish, dirs yellow; bookmarks a plain icon). -*** VERIFY org-faces color set in theme-studio reaches the agenda -What we're verifying: editing an org-faces-* row in theme-studio, exporting, and deploying lands the new color on the real agenda's keyword/priority. The build-theme -> deftheme half and the live org-todo-keyword-faces / org-priority-faces wiring are already verified mechanically; this confirms the visual end-to-end with a human eye. -- Open theme-studio in Chrome and pick "org-faces" from the application dropdown (it sits beside elfeed and mu4e) -- Confirm the preview shows the focused agenda block over the auto-dim block, and that the rows read "todo", "priority a", etc. -- Edit org-faces-todo to an obviously different color (e.g. bright magenta) and export the theme to WIP.json -#+begin_src sh :results output -make -C /home/cjennings/.emacs.d deploy-wip -#+end_src -- Open the org agenda (or any todo.org buffer) and look at a TODO keyword -Expected: the TODO keyword renders in the color just set; the priority cookies and other keywords keep their own colors; an unfocused window shows the dimmed variants. -*** VERIFY slack keys are safe before slack loads -What we're verifying: the C-; S slack keys don't error before slack has started, and the prefix shows in which-key. Fixed in modules/slack-config.el; restart to apply (not reloaded into the live session). -- Restart Emacs but do NOT run cj/slack-start -- Press C-; S Q (close all), and C-; S w / @ / # (these previously void-function'd or void-variable'd before load) -- Press C-; S and check which-key shows the "slack" prefix -Expected: C-; S Q reports "Closed 0 Slack buffers" with no error; w/@/# either run or autoload slack cleanly (no void-function); the which-key popup lists the slack prefix. -*** VERIFY ERC fires one mention notification and lists real servers -What we're verifying: a mention pops a single desktop notification (not two), and cj/erc-connected-servers lists only live server connections. Fixed in modules/erc-config.el; takes effect after an Emacs restart (not reloaded into the live IRC session). -- Restart Emacs and reconnect ERC -- Have someone mention your nick in a channel (or trigger erc-text-matched-hook) -- Run M-x cj/erc-connected-servers with one server connected and a few channels open -Expected: exactly one desktop notification per mention; cj/erc-connected-servers reports just the connected server(s), not every channel/query buffer. -*** VERIFY modeline still shows the git branch and state -What we're verifying: the VC-cache simplification didn't change what the modeline shows on a normal repo. Fixed in modules/modeline-config.el (live in the daemon after reload). -- Open a file inside a git repo -- Glance at the mode-line VC segment -Expected: the branch name and state still render as before (e.g. "main" with the usual state face). The change only drops a per-render stat and guards against git errors; normal display is unchanged. -*** VERIFY info-mode open is non-destructive and cancels cleanly -What we're verifying: opening a .info file no longer auto-kills the buffer, and the explicit cj/open-with-info-mode prompt cancels cleanly on decline. Fixed in modules/help-config.el; stale daemon state already cleared, so this also survives a fresh restart. -- find-file a .info file (e.g. one under elpa) — it should open as an ordinary buffer, not vanish into Info -- In that buffer, edit something, then M-x cj/open-with-info-mode; at the save prompt answer no -- Repeat M-x cj/open-with-info-mode on an unmodified .info buffer -Expected: find-file leaves the buffer intact (no auto-kill); declining the save prompt prints "Operation canceled" with no "No catch for tag" error; on an unmodified buffer it opens the file in Info. -*** VERIFY dwim-shell zip/backup/menu-key behave -What we're verifying: single-file zip makes a valid <name>.zip, the dated backup gets a real timestamp, and the dwim-shell menu is reachable on M-D in plain dired. Fixed in modules/dwim-shell-config.el, reloaded into the daemon. -- In dired, mark a single file, run the dwim-shell menu (M-D), pick Zip -- Mark a file, run the menu, pick "Backup with date" -- Open a plain dired buffer (not dirvish) and press M-D -Expected: zip produces foo.zip (a valid archive, openable); backup produces foo.ext.YYYYMMDD_HHMMSS.bak with a real date; M-D opens the dwim-shell command menu in plain dired (before the fix it did nothing there). -*** VERIFY markdown live preview renders in the browser -What we're verifying: F2 in a markdown buffer runs the custom cj/markdown-preview (not markdown-mode's own command) and the impatient-mode strapdown preview actually renders. Fixed in modules/markdown-config.el, reloaded into the daemon. -- Open a .md file with some markdown content -- M-x cj/markdown-preview-server-start (starts simple-httpd on :8080) -- Press F2 in the markdown buffer -Expected: a browser opens http://localhost:8080/imp showing the rendered markdown, and edits to the buffer update the preview live. Pressing F2 before starting the server gives a user-error telling you to start it. -*** VERIFY orderless matching works inside a vertico session -What we're verifying: vertico-prescient no longer overrides completion-styles, so orderless's space-separated, out-of-order matching is live in the minibuffer (prescient still sorts). Fixed in modules/selection-framework.el, applied live in the daemon. -- Run a command with a vertico minibuffer (e.g. M-x, or C-x b) -- Type two space-separated fragments out of order, e.g. "mode buf" to match "switch-to-buffer-other-... mode" style candidates -Expected: candidates match on both fragments regardless of order (orderless), and the ordering still reflects prescient frecency. Before the fix, space-separated out-of-order input would not match. -*** VERIFY C-; b d diffs, C-; b D deletes -What we're verifying: the buffer-and-file keymap now puts diff on the easy lowercase key and the destructive delete on the capital. Swapped in modules/custom-buffer-file.el and re-bound live in the daemon. -- Open a file buffer and edit it without saving -- Press C-; b d -- Press C-; b D, then cancel at the delete confirmation -Expected: C-; b d runs the diff (buffer vs saved file); C-; b D starts delete-buffer-and-file (offers to delete the file). Before the swap these were reversed. -*** TODO C-s C-s repeats the last search -What we're verifying: the second consecutive C-s repeats the previous consult-line search instead of erroring "No Vertico session". Fix in modules/selection-framework.el (vertico-repeat-save now on minibuffer-setup-hook), live in the daemon. -- Press C-s, type a search term, RET to dismiss (or just narrow then exit) -- Press C-s again, then C-s a second time without any command in between -Expected: the second C-s reopens the last search (vertico-repeat) rather than signalling "No Vertico session". *** TODO reconcile-open-repos includes dot-named repos What we're verifying: M-P (reconcile open repos) now visits repos whose directory name has a dot (mcp.el, capture.el, etc.), which the old "^[^.]+$" filter silently skipped. Fix in modules/reconcile-open-repos.el, live in the daemon; live-daemon check already confirmed discovery, this is the through-the-command spot-check. - Run M-P (or M-x cj/reconcile-open-repos) - Watch the per-repo progress / final summary Expected: dot-named repos under ~/code (mcp.el, gptel-mcp.el, capture.el, google-contacts.el, …) appear in the reconciliation pass, not just dot-free ones. -*** 2026-06-15 Mon @ 12:10:06 -0500 org-capture popup single-Task into inbox verified -Craig confirmed: Super+Shift+N pops straight into a Task capture (no menu), single full-frame window, files under "Inbox" in ~/org/roam/inbox.org, and the frame closes cleanly. Passed. -*** TODO Lock screen actually locks on Wayland -What we're verifying: C-; ! l locks the screen on Wayland. slock (X11-only) never worked here; the locker now runs loginctl lock-session, which logind turns into a Lock signal that hypridle handles by running hyprlock — the same path idle/sleep locking already uses. Fix in modules/system-commands.el, live in the daemon. -- Press C-; ! l (or run M-x cj/system-cmd-lock) -- The screen should lock with hyprlock -- Unlock with your password -Expected: the screen locks immediately and unlocks with your password. (Before the fix it printed "Running lockscreen-cmd..." and nothing happened.) -*** TODO Irreversible actions require a typed "yes" after a daemon restart -What we're verifying: the strong-confirm tier is restored for irreversible actions. The global (fset 'yes-or-no-p 'y-or-n-p) was removed and those sites now call cj/confirm-strong, which forces a typed "yes"/"no". The fset is baked into the running daemon and can't be cleared from Lisp, so this only takes effect after a restart. Ordinary yes-or-no-p prompts stay single-key (use-short-answers t). -- Restart the Emacs daemon (clean state) -- Trigger an irreversible action, e.g. M-x cj/system-cmd-shutdown (then abort), or attempt to overwrite a file via the rename/move commands -Expected: the irreversible prompt requires typing the full word "yes" (not a single y); a benign yes-or-no-p prompt elsewhere still accepts a single keystroke. -*** 2026-06-11 Thu @ 18:29:39 -0500 Verified UI-face preview and contrast survive a ground bg change -Craig walked the repro: mode-line with its own fg/bg kept its preview bg and ratio through a ground change; ground-dependent rows re-rated; package-faces contrast column updated. Pass. Closed the [#A] contrast-cell and [#B] preview-bg parents. -*** 2026-06-11 Thu @ 18:29:39 -0500 Verified seeded package-face defaults, with steel tuning -Craig read org/magit/elfeed against the ground. Pass with tuning: steel reads a bit dark — flipped to steel+1 on magit (better), but org wanted darker; these are updated selections, NOT final — he expects to adjust many more before the theme ships. His export saved to scripts/theme-studio/theme.json (replaced the 2026-06-09 state, prior version in git at 4f2d00eb). Side find: the org preview's heading-three ↔ headline-todo flash linkage is cross-wired — filed as its own bug task. -*** 2026-06-11 Thu @ 18:29:39 -0500 Verified large face tables stay usable -Craig scrolled the org table, filtered on "agenda", reassigned a face — grouping, narrowing, and live preview update all behaved. Pass. -*** 2026-06-11 Thu @ 18:29:39 -0500 Verified perceptual readouts in the picker -Craig validated the readouts against computed reference values (default fg #f0fef0 on ground #000000: APCA Lc -104.7 / WCAG 20.14; keyword blue #67809c: Lc -33.7 / WCAG 5.14 — negative polarity correct for light-on-dark). Legible, uncrowded. Pass. Side find filed separately: the picker panel itself blends into the page background ([#C] picker-visibility task). -*** 2026-06-11 Thu @ 18:29:39 -0500 Verified ΔE warnings read clearly -Craig built a near-duplicate pair and a well-spread palette: the close pair was named with its ΔE, sorted closest-first with the cap behaving; no warning on the spread palette. Pass. -*** TODO OKLCH editor feels right -What we're verifying: the OKLCH sliders / C×L plane edit cleanly and clamping is visible. -- Switch the picker to OKLCH mode and drag L, then C, then H -- Push chroma past the sRGB gamut, then toggle the AA/AAA mask -Expected: each axis moves independently; the C×L plane (once 4b lands) opens on the current color; "chroma clamped to sRGB" shows on clamp; toggling the mask does not reset OKLCH mode. -*** TODO Generated ramp harmonizes -What we're verifying: a ramp generated from a base color reads as one family, not a grab-bag (the aesthetic the math is meant to produce). -- Open =scripts/theme-studio/theme-studio.html= in Chrome -- Pick a mid-lightness base swatch (e.g. a blue) and generate its ramp at the defaults -- Read the row of steps left to right, then try a near-black and a near-white base -Expected: the steps share an obvious hue and step evenly in lightness; the chroma-ease keeps the extreme steps from going muddy or garish; nothing looks like it belongs to a different color. *** TODO Safe-lightness guidance reads clearly What we're verifying: the L_max marker and unsafe-band shade are legible and land in the right place when editing a covered face. - Open the picker in OKLCH mode on region (or hl-line), with syntax colors assigned @@ -324,31 +80,12 @@ What we're verifying: a background tint the tool calls safe really keeps every t - Build the theme and load it in Emacs, open a code buffer with varied syntax, and select a region spanning many token colors - Read every token through the region highlight, paying attention to the limiting foreground the tool named Expected: every token stays readable over the tint, including the limiting one; a tint pushed just past L_max (readout FAIL) shows a visibly strained or unreadable token, confirming the floor matches reality. -*** TODO Color families group the way the eye reads them -What we're verifying: the OKLCH hue clustering (25° gap) splits and merges families the way you'd expect, and renaming never moves a color. -- Open =scripts/theme-studio/theme-studio.html= in Chrome and load a real theme (e.g. sterling) -- Read the strips top to bottom: are "the blues" one strip, "the greens" another, neutrals and ground pinned at the top -- Find a pair you'd consider one family that landed in two strips (or two you'd consider separate that merged) -- Rename any swatch to something absurd and confirm it stays in the same strip -Expected: families match your mental grouping; the few that don't are the cue to revisit the 25° gap; renaming never regroups. *** TODO Regenerate-replace reads as deliberate What we're verifying: the count control clearly signals it rewrites the whole family, so replacing hand-added same-hue colors isn't a surprise. - Add two unrelated colors at a similar hue so they share a strip - Set that strip's count to 2 - Watch what happens to the two colors Expected: the strip becomes a clean base±2 ramp, the two loose colors are gone, and the control made it obvious that's what it would do before you committed. -*** TODO Removed-step references read clearly as "(gone)" -What we're verifying: lowering a family's count leaves a referencing face visibly stale, not silently re-pointed. -- Assign a UI or syntax element to an outer step of a family (e.g. region = a blue+3) -- Lower that family's count to 2 so blue+3 disappears -- Read the assignment's dropdown -Expected: the dropdown shows "(gone)" for the removed step, never a silent jump to a different color; re-pointing it is a deliberate choice. -*** TODO Calibre bookmark default name is "Author, Title" -What we're verifying: a new nov bookmark takes the "Author, Title" form parsed from the filename, not the raw EPUB filename. -- Open an EPUB in Calibre (nov buffer). -- Hit m to set a bookmark. -Expected: the default bookmark name is "Author, Title" (underscores stripped, colon restored), e.g. "Agatha Christie, The A.B.C. Murders". - *** TODO Calibre curated ? menu and docked description What we're verifying: the curated ? transient, the docked description, and the full dispatch all work in a live calibredb buffer. - In a calibredb search buffer, press ? and confirm the curated menu (library / filter / sort / open / describe) appears. @@ -368,26 +105,46 @@ What we're verifying: the suppression predicate gates the toast when you're read - Have the same sender message you again. Expected: the message renders in the buffer, but no desktop toast appears. -*** TODO Project-aware capture files into the right todo.org +*** DONE Project-aware capture files into the right todo.org +CLOSED: [2026-06-24 Wed 11:48] What we're verifying: C-c c t and C-c c b file into the current projectile project's todo.org under its "<Project> Open Work" header, and fall back to the global inbox outside a project. - Inside a projectile project that has a todo.org, run C-c c t (Task), capture a test entry, and confirm it lands under "<Project> Open Work". - Run C-c c b (Bug) similarly and confirm it lands as "* TODO [#C] ..." under the same header. - Run a capture from outside any project (or a project with no todo.org) and confirm the global-inbox fallback with a warning. Expected: in-project captures land in that project's Open Work; out-of-project captures fall back to the global inbox with a warning. -*** VERIFY Dirvish d duplicates, D force-deletes with a confirm -What we're verifying: in dirvish, d now duplicates the file at point (delete-to-trash removed), and D force-deletes the marked files via sudo rm -rf after a yes-or-no-p naming the targets. The pure command builder is unit-tested; this is the live keypress plus the guarded destructive path. -- Open dirvish on a scratch directory holding a couple of throwaway files -- Put point on a file and press d — confirm a "<name>-copy.<ext>" appears (a duplicate, nothing deleted) -- Mark one or two throwaway files, press D, and read the "Force-delete (sudo rm -rf, NO undo): <names>?" prompt -- Answer no first (confirm nothing happens), then press D again and answer yes -- Note whether sudo prompts for a password and whether the file actually disappears -Expected: d duplicates; D names the exact targets and only deletes on yes; the files are gone with no trash copy. If sudo needs a password that shell-command can't supply, flag it — the delete may need to route through a tty instead. -*** 2026-06-20 Sat @ 22:11:00 -0400 F9 agent toggle no longer shrinks after a C-; b pull-away -Craig confirmed in his live GUI frame: the agent window keeps its height across repeated F9 toggles after a C-; b pull-away, even under the WIP theme's near-zero mode-line-inactive. The total-height capture/replay fix holds (dbee95ae). -*** 2026-06-20 Sat @ 22:11:00 -0400 F9 toggle preserves all windows in a 3-window layout -Craig confirmed in his live GUI frame: toggling the agent off then on in a 3-window layout returns the same three windows — both working windows survive and the agent re-splits its own bottom strip. The reversible-toggle fix holds (64916462). -*** 2026-06-24 Wed @ 00:37:18 -0400 C-<up> copy-mode scroll verified in a real terminal -Craig confirmed in a live terminal: C-<up> enters copy-mode and scrolls up, repeated C-<up> keep scrolling without resetting, the other modified arrows are left alone (C-<left>/C-<right> still do readline word-motion at the prompt). The C-<up>-only fix + already-in-copy guard (commit 7696ff76) holds. +*** VERIFY C-c ; reaches the custom command family in a real terminal frame +What we're verifying: the TTY mirror prefix C-c ; reaches the same cj/custom-keymap as the GUI C-; prefix, so the whole command family works in a terminal. The unit tests + a live daemon eval already confirm both prefixes resolve to the one keymap; this is the end-to-end in an actual TTY frame, which the batch harness can't drive. +- Open a terminal Emacs frame: emacsclient -nw (or emacs -nw, or Emacs inside vterm/tmux) +- Press C-c ; L (pearl), C-c ; a (AI), C-c ; g (calendar) — the same leaf keys you use under C-; in GUI +- Confirm which-key shows the custom prefix under C-c ; +Expected: each C-c ; <leaf> runs the same command its C-; <leaf> counterpart runs in GUI; which-key lists the family under C-c ;. C-; itself stays working in GUI frames (unchanged). +*** VERIFY org-faces color set in theme-studio reaches the agenda +What we're verifying: editing an org-faces-* row in theme-studio, exporting, and deploying lands the new color on the real agenda's keyword/priority. The build-theme -> deftheme half and the live org-todo-keyword-faces / org-priority-faces wiring are already verified mechanically; this confirms the visual end-to-end with a human eye. +- Open theme-studio in Chrome and pick "org-faces" from the application dropdown (it sits beside elfeed and mu4e) +- Confirm the preview shows the focused agenda block over the auto-dim block, and that the rows read "todo", "priority a", etc. +- Edit org-faces-todo to an obviously different color (e.g. bright magenta) and export the theme to WIP.json +#+begin_src sh :results output +make -C /home/cjennings/.emacs.d deploy-wip +#+end_src +- Open the org agenda (or any todo.org buffer) and look at a TODO keyword +Expected: the TODO keyword renders in the color just set; the priority cookies and other keywords keep their own colors; an unfocused window shows the dimmed variants. +*** VERIFY ERC fires one mention notification and lists real servers +What we're verifying: a mention pops a single desktop notification (not two), and cj/erc-connected-servers lists only live server connections. Fixed in modules/erc-config.el; takes effect after an Emacs restart (not reloaded into the live IRC session). +- Restart Emacs and reconnect ERC +- Have someone mention your nick in a channel (or trigger erc-text-matched-hook) +- Run M-x cj/erc-connected-servers with one server connected and a few channels open +Expected: exactly one desktop notification per mention; cj/erc-connected-servers reports just the connected server(s), not every channel/query buffer. +*** VERIFY dwim-shell zip/backup/menu-key behave +What we're verifying: single-file zip makes a valid <name>.zip, the dated backup gets a real timestamp, and the dwim-shell menu is reachable on M-D in plain dired. Fixed in modules/dwim-shell-config.el, reloaded into the daemon. +- In dired, mark a single file, run the dwim-shell menu (M-D), pick Zip +- Mark a file, run the menu, pick "Backup with date" +- Open a plain dired buffer (not dirvish) and press M-D +Expected: zip produces foo.zip (a valid archive, openable); backup produces foo.ext.YYYYMMDD_HHMMSS.bak with a real date; M-D opens the dwim-shell command menu in plain dired (before the fix it did nothing there). +*** VERIFY orderless matching works inside a vertico session +What we're verifying: vertico-prescient no longer overrides completion-styles, so orderless's space-separated, out-of-order matching is live in the minibuffer (prescient still sorts). Fixed in modules/selection-framework.el, applied live in the daemon. +- Run a command with a vertico minibuffer (e.g. M-x, or C-x b) +- Type two space-separated fragments out of order, e.g. "mode buf" to match "switch-to-buffer-other-... mode" style candidates +Expected: candidates match on both fragments regardless of order (orderless), and the ordering still reflects prescient frecency. Before the fix, space-separated out-of-order input would not match. *** VERIFY face-name buttons open describe-face What we're verifying: the face names in the Face Diagnosis report are live buttons. The button text properties (action + face data) are confirmed in the daemon; this is the click/RET confirmation. - Put point on themed text and run =C-h F= (=cj/describe-face-at-point=). @@ -406,14 +163,19 @@ What we're verifying: the cmail trash-folder + per-message refile fix (shipped 2 - On a cmail message, press =r= (refile) then =x=; confirm it lands in cmail/Archive. - On a gmail (or dmail) message, press =r=. Expected: cmail trash → cmail/Trash, cmail refile → cmail/Archive, both real maildirs mbsync syncs. Refile on gmail/dmail signals a user-error (no move) rather than offering to create an unsynced phantom folder. If any move targets a folder mbsync doesn't sync, capture it and reopen. -*** VERIFY nerd-icons colors are theme-driven (legend + live icons) -What we're verifying: the nerd-icons v1 feature reads right end to end. The Python/Node/browser gates pass; this is the visual confirmation the gates can't make — the legend pane and the real per-filetype icon colors after the tint removal. -- In theme-studio, open the nerd-icons pane: the legend should show each filetype's real nerd-font glyph in its mapped color (el purple, py dark-blue, dir yellow, …), with the 34 color faces editable on the left. -- Recolor a face (e.g. nerd-icons-purple) and confirm every legend row mapped to it repaints immediately. -- Restart Emacs (the running daemon still has the old darkgoldenrod tint baked into the faces until restart). -- In a fresh frame, look at icons in completing-read (find-file), dirvish, the dashboard, and ibuffer. -Expected: the legend renders glyphs in their assigned colors and recolor repaints live; after restart, file/dir/buffer icons show nerd-icons' per-filetype multicolor palette driven by the theme (not a uniform darkgoldenrod), and directory icons are yellow. If icons are still uniform or uncolored, capture it and reopen. -*** VERIFY ai-term wrap-teardown + shutdown end-to-end +*** STALLED markdown live preview renders in the browser +What we're verifying: F2 in a markdown buffer runs the custom cj/markdown-preview (not markdown-mode's own command) and the impatient-mode strapdown preview actually renders. Fixed in modules/markdown-config.el, reloaded into the daemon. +- Open a .md file with some markdown content +- M-x cj/markdown-preview-server-start (starts simple-httpd on :8080) +- Press F2 in the markdown buffer +Expected: a browser opens http://localhost:8080/imp showing the rendered markdown, and edits to the buffer update the preview live. +Pressing F2 before starting the server gives a user-error telling you to start it. + +#+begin_src cj: comment + we should simply have the server start if it's not already started. +#+end_src + +*** STALLED ai-term wrap-teardown + shutdown end-to-end What we're verifying: the three headless functions drive the rulesets wrap-it-up workflow correctly, including the real tmux/shutdown side effects the ERT tests can't exercise. The .emacs.d functions are in and unit-verified; the rulesets half (workflow + Stop hook) is already built. Test the countdown with a stubbed shutdown command first — do not power off during the check. #+begin_src emacs-lisp ;; temporarily stub the shutdown so the countdown can't power off: @@ -429,6 +191,191 @@ What we're verifying: the three headless functions drive the rulesets wrap-it-up #+end_src Expected: teardown removes exactly the right session/buffer and restores layout; the with-summary variants keep the buffer; the multi-session shutdown refuses; the sole-session countdown renders, cancels on C-g, and fires only at zero. If any step misbehaves, capture it and reopen. Once the stubbed run looks right, a single real shutdown test confirms the live path. +#+begin_src cj: comment + I would like to test this in separate steps naturally as I need them across sessions. please add one child task for each item to test above. +#+end_src + +*** 2026-06-15 Mon @ 12:10:06 -0500 org-capture popup single-Task into inbox verified +Craig confirmed: Super+Shift+N pops straight into a Task capture (no menu), single full-frame window, files under "Inbox" in ~/org/roam/inbox.org, and the frame closes cleanly. Passed. +*** 2026-06-11 Thu @ 18:29:39 -0500 Verified UI-face preview and contrast survive a ground bg change +Craig walked the repro: mode-line with its own fg/bg kept its preview bg and ratio through a ground change; ground-dependent rows re-rated; package-faces contrast column updated. Pass. Closed the [#A] contrast-cell and [#B] preview-bg parents. +*** 2026-06-11 Thu @ 18:29:39 -0500 Verified seeded package-face defaults, with steel tuning +Craig read org/magit/elfeed against the ground. Pass with tuning: steel reads a bit dark — flipped to steel+1 on magit (better), but org wanted darker; these are updated selections, NOT final — he expects to adjust many more before the theme ships. His export saved to scripts/theme-studio/theme.json (replaced the 2026-06-09 state, prior version in git at 4f2d00eb). Side find: the org preview's heading-three ↔ headline-todo flash linkage is cross-wired — filed as its own bug task. +*** 2026-06-11 Thu @ 18:29:39 -0500 Verified large face tables stay usable +Craig scrolled the org table, filtered on "agenda", reassigned a face — grouping, narrowing, and live preview update all behaved. Pass. +*** 2026-06-11 Thu @ 18:29:39 -0500 Verified perceptual readouts in the picker +Craig validated the readouts against computed reference values (default fg #f0fef0 on ground #000000: APCA Lc -104.7 / WCAG 20.14; keyword blue #67809c: Lc -33.7 / WCAG 5.14 — negative polarity correct for light-on-dark). Legible, uncrowded. Pass. Side find filed separately: the picker panel itself blends into the page background ([#C] picker-visibility task). +*** 2026-06-11 Thu @ 18:29:39 -0500 Verified ΔE warnings read clearly +Craig built a near-duplicate pair and a well-spread palette: the close pair was named with its ΔE, sorted closest-first with the cap behaving; no warning on the spread palette. Pass. +*** 2026-06-20 Sat @ 22:11:00 -0400 F9 agent toggle no longer shrinks after a C-; b pull-away +Craig confirmed in his live GUI frame: the agent window keeps its height across repeated F9 toggles after a C-; b pull-away, even under the WIP theme's near-zero mode-line-inactive. The total-height capture/replay fix holds (dbee95ae). +*** 2026-06-20 Sat @ 22:11:00 -0400 F9 toggle preserves all windows in a 3-window layout +Craig confirmed in his live GUI frame: toggling the agent off then on in a 3-window layout returns the same three windows — both working windows survive and the agent re-splits its own bottom strip. The reversible-toggle fix holds (64916462). +*** 2026-06-24 Wed @ 00:37:18 -0400 C-<up> copy-mode scroll verified in a real terminal +Craig confirmed in a live terminal: C-<up> enters copy-mode and scrolls up, repeated C-<up> keep scrolling without resetting, the other modified arrows are left alone (C-<left>/C-<right> still do readline word-motion at the prompt). The C-<up>-only fix + already-in-copy guard (commit 7696ff76) holds. +*** DONE theme-studio markdown preview reads like a real README +CLOSED: [2026-06-24 Wed 11:47] +What we're verifying: selecting markdown-mode in the view dropdown shows a realistic README (not the generic face-name list), and the markdown faces render legibly in context. #mdtest already confirms the wiring + that every element's face is real; this is the visual read. +- Reload theme-studio (or make theme-studio-open) +- Pick "markdown-mode" from the view dropdown +Expected: a README preview with headers, bold/italic, code, links, lists/checkboxes, blockquote, table, etc., each in its theme face. Clicking an element flashes its row in the faces table. +*** DONE C-s C-s repeats the last search +CLOSED: [2026-06-24 Wed 11:37] +What we're verifying: the second consecutive C-s repeats the previous consult-line search instead of erroring "No Vertico session". Fix in modules/selection-framework.el (vertico-repeat-save now on minibuffer-setup-hook), live in the daemon. +- Press C-s, type a search term, RET to dismiss (or just narrow then exit) +- Press C-s again, then C-s a second time without any command in between +Expected: the second C-s reopens the last search (vertico-repeat) rather than signalling "No Vertico session". +*** DONE Irreversible actions require a typed "yes" after a daemon restart +CLOSED: [2026-06-24 Wed 11:36] +What we're verifying: the strong-confirm tier is restored for irreversible actions. The global (fset 'yes-or-no-p 'y-or-n-p) was removed and those sites now call cj/confirm-strong, which forces a typed "yes"/"no". The fset is baked into the running daemon and can't be cleared from Lisp, so this only takes effect after a restart. Ordinary yes-or-no-p prompts stay single-key (use-short-answers t). +- Restart the Emacs daemon (clean state) +- Trigger an irreversible action, e.g. M-x cj/system-cmd-shutdown (then abort), or attempt to overwrite a file via the rename/move commands +Expected: the irreversible prompt requires typing the full word "yes" (not a single y); a benign yes-or-no-p prompt elsewhere still accepts a single keystroke. +*** DONE Calibre bookmark default name is "Author, Title" +CLOSED: [2026-06-24 Wed 10:56] +What we're verifying: a new nov bookmark takes the "Author, Title" form parsed from the filename, not the raw EPUB filename. +- Open an EPUB in Calibre (nov buffer). +- Hit m to set a bookmark. +Expected: the default bookmark name is "Author, Title" (underscores stripped, colon restored), e.g. "Agatha Christie, The A.B.C. Murders". + +*** DONE theme-studio gnus view package themes the article headers +CLOSED: [2026-06-24 Wed 11:29] +What we're verifying: gnus is now its own view package in theme-studio (it drives the mu4e article view), so the bright-green article headers can be themed and exported. #gnustest confirms the package is registered and its preview emits only real gnus faces; this is the visual read plus the live-green retirement. +- Reload theme-studio (or make theme-studio-open) +- Pick "gnus (mu4e article view)" from the view dropdown (sits among the g entries) +- Confirm the preview shows a header block, an emphasized body, an 11-level quoted reply chain, and a signature +- Theme a few gnus faces (e.g. gnus-header-name, gnus-header-from, gnus-cite-1) to obvious colors, export to WIP.json, then deploy +#+begin_src sh :results output +make -C /home/cjennings/.emacs.d deploy-wip +#+end_src +- Restart Emacs (or reload the theme), reopen a mu4e message +Expected: the studio preview renders each gnus face in its theme color; after export + deploy, the *mu4e-article* From/Subject/To/Date headers show the themed colors instead of the gnus green defaults. +*** DONE dashboard theming — banner gold, headings themed, items show per-filetype icons +CLOSED: [2026-06-24 Wed 11:29] +What we're verifying: with the dashboard out of global font-lock (Fix A) and file icons on (Fix C), the live dashboard shows the theme colors and icons. Eyeball it. +- Open the dashboard (F1) +Expected: the "Emacs:" banner title is gold, the "Projects:/Bookmarks:/Recent Files:" headings are themed blue, and the project/recent-file rows each show a colored per-filetype icon (org files greenish, dirs yellow; bookmarks a plain icon). +*** DONE info-mode open is non-destructive and cancels cleanly +CLOSED: [2026-06-24 Wed 11:41] +What we're verifying: opening a .info file no longer auto-kills the buffer, and the explicit cj/open-with-info-mode prompt cancels cleanly on decline. Fixed in modules/help-config.el; stale daemon state already cleared, so this also survives a fresh restart. +- find-file a .info file (e.g. one under elpa) — it should open as an ordinary buffer, not vanish into Info +- In that buffer, edit something, then M-x cj/open-with-info-mode; at the save prompt answer no +- Repeat M-x cj/open-with-info-mode on an unmodified .info buffer +Expected: find-file leaves the buffer intact (no auto-kill); declining the save prompt prints "Operation canceled" with no "No catch for tag" error; on an unmodified buffer it opens the file in Info. +*** DONE C-; b d diffs, C-; b D deletes +CLOSED: [2026-06-24 Wed 11:43] +What we're verifying: the buffer-and-file keymap now puts diff on the easy lowercase key and the destructive delete on the capital. Swapped in modules/custom-buffer-file.el and re-bound live in the daemon. +- Open a file buffer and edit it without saving +- Press C-; b d +- Press C-; b D, then cancel at the delete confirmation +Expected: C-; b d runs the diff (buffer vs saved file); C-; b D starts delete-buffer-and-file (offers to delete the file). Before the swap these were reversed. +*** DONE nerd-icons colors are theme-driven (legend + live icons) +CLOSED: [2026-06-24 Wed 11:35] +What we're verifying: the nerd-icons v1 feature reads right end to end. The Python/Node/browser gates pass; this is the visual confirmation the gates can't make — the legend pane and the real per-filetype icon colors after the tint removal. +- In theme-studio, open the nerd-icons pane: the legend should show each filetype's real nerd-font glyph in its mapped color (el purple, py dark-blue, dir yellow, …), with the 34 color faces editable on the left. +- Recolor a face (e.g. nerd-icons-purple) and confirm every legend row mapped to it repaints immediately. +- Restart Emacs (the running daemon still has the old darkgoldenrod tint baked into the faces until restart). +- In a fresh frame, look at icons in completing-read (find-file), dirvish, the dashboard, and ibuffer. +Expected: the legend renders glyphs in their assigned colors and recolor repaints live; after restart, file/dir/buffer icons show nerd-icons' per-filetype multicolor palette driven by the theme (not a uniform darkgoldenrod), and directory icons are yellow. If icons are still uniform or uncolored, capture it and reopen. +*** DONE ai-term keybindings land on C-; a + M-SPC +CLOSED: [2026-06-24 Wed 10:36] +What we're verifying: the relocated ai-term keys work in a live frame, including from inside an agent buffer, and the no-agent fallback launches the picker. +- Press M-SPC from a normal buffer with at least one agent open. +- Press M-SPC again from inside an agent buffer (ghostel). +- With no agent running, press M-SPC. +- Walk C-; a a, C-; a s, C-; a n, C-; a k (which-key should show the ai-term menu under C-; a). +- Press F9, C-F9, s-F9, M-F9. +Expected: M-SPC swaps to the next agent (rotating, wrapping) both from a normal buffer and from inside an agent. With no agent running, M-SPC opens the project picker rather than erroring. C-; a a toggles the most-recent agent, s opens the picker, n swaps, k closes. The F9 family does nothing (unbound). Note: the running daemon still has gptel in memory from before the archive, so a full Emacs restart is the clean confirmation that nothing regressed at startup. +*** DONE deferred game commands still work after a restart (load-graph Phase 4) +CLOSED: [2026-06-24 Wed 10:37] +What we're verifying: with games-config no longer eagerly required, malyon and 2048-game still launch from a fresh Emacs, and games-config loads on first use rather than at startup. Batch tests cover the autoload chain; this is the interactive confirmation the spec asks for after each deferral batch. +- Restart Emacs (daemon or standalone) so games-config is no longer pre-loaded from this session. +- Confirm it's not loaded at startup: +#+begin_src emacs-lisp +(featurep 'games-config) +#+end_src +- M-x malyon — it should load games-config and the malyon package, then start interactive fiction (stories under ~/sync/org/text.games/). +- M-x 2048-game — should start the 2048 puzzle. +- Re-check (featurep 'games-config) — now non-nil. +Expected: at startup (featurep 'games-config) is nil; both commands launch normally; after invoking one, games-config is loaded. If a command errors instead of launching, capture it and reopen the deferral as a TODO. +*** DONE native-comp + gcmh survive a daemon restart cleanly +CLOSED: [2026-06-24 Wed 10:37] +What we're verifying: re-enabling JIT native compilation and switching GC to gcmh holds up across a full daemon restart and a real work session. The fix is live in the current daemon and a throwaway daemon launched clean, but the already-loaded modules only get natively compiled on a fresh start (a background async burst), and the old "Selecting deleted buffer" race needs a real GUI session to rule out on 30.2. +- Restart the Emacs daemon (clean state): kill it and start fresh, or reboot. +- Use Emacs normally for a while — the first session after restart triggers background native compilation of ~100 modules. Watch for any "Selecting deleted buffer" errors or compilation crashes (check the *Async-native-compile-log* buffer and comp-warnings.log). +- After things settle, confirm the settings are live: +#+begin_src emacs-lisp +(list :jit native-comp-jit-compilation + :gcmh gcmh-mode + :gcmh-high gcmh-high-cons-threshold) +#+end_src +- Edit normally (completion, agenda, AI buffers) and notice whether the periodic GC jank is gone. +Expected: restart is clean (no backtrace); the background native-comp burst finishes without "Selecting deleted buffer" errors; the form returns (:jit t :gcmh t :gcmh-high 1073741824); editing feels smoother with no frequent GC pauses. If the async race recurs on 30.2, capture the error and reopen as a TODO — the fallback is an AOT sweep or going back to JIT-off. +*** DONE mu4e buffers are themed (headers, main, message view) +CLOSED: [2026-06-24 Wed 10:38] +What we're verifying: with the mu4e modes excluded from global font-lock, mu4e's manual face properties survive, so the buffers pick up the theme. The headers + main + view-headers are the ones global font-lock was stripping. +- Restart Emacs (cleanest), or kill and reopen the mu4e buffers +- Open mu4e, look at the headers list and the main menu +- Open a message and read the body +Expected: headers list shows unread/flagged/date/subject in their theme colors (mu4e-unread-face gold, mu4e-header-face green, etc.); the main menu and the message-view headers (From/To/Subject) are themed; the message body still renders correctly (gnus does the body, so it's unaffected). NOTE: a plain "g" refresh in an already-open *mu4e-headers* won't fix it on its own unless font-lock is off there; a restart is the reliable check. +*** DONE slack keys are safe before slack loads +CLOSED: [2026-06-24 Wed 10:44] +What we're verifying: the C-; S slack keys don't error before slack has started, and the prefix shows in which-key. Fixed in modules/slack-config.el; restart to apply (not reloaded into the live session). +- Restart Emacs but do NOT run cj/slack-start +- Press C-; S Q (close all), and C-; S w / @ / # (these previously void-function'd or void-variable'd before load) +- Press C-; S and check which-key shows the "slack" prefix +Expected: C-; S Q reports "Closed 0 Slack buffers" with no error; w/@/# either run or autoload slack cleanly (no void-function); the which-key popup lists the slack prefix. +*** DONE modeline still shows the git branch and state +CLOSED: [2026-06-24 Wed 10:45] +What we're verifying: the VC-cache simplification didn't change what the modeline shows on a normal repo. Fixed in modules/modeline-config.el (live in the daemon after reload). +- Open a file inside a git repo +- Glance at the mode-line VC segment +Expected: the branch name and state still render as before (e.g. "main" with the usual state face). The change only drops a per-render stat and guards against git errors; normal display is unchanged. + +*** DONE Lock screen actually locks on Wayland +CLOSED: [2026-06-24 Wed 10:45] +What we're verifying: C-; ! l locks the screen on Wayland. slock (X11-only) never worked here; the locker now runs loginctl lock-session, which logind turns into a Lock signal that hypridle handles by running hyprlock — the same path idle/sleep locking already uses. Fix in modules/system-commands.el, live in the daemon. +- Press C-; ! l (or run M-x cj/system-cmd-lock) +- The screen should lock with hyprlock +- Unlock with your password +Expected: the screen locks immediately and unlocks with your password. (Before the fix it printed "Running lockscreen-cmd..." and nothing happened.) +*** DONE OKLCH editor feels right +CLOSED: [2026-06-24 Wed 10:47] +What we're verifying: the OKLCH sliders / C×L plane edit cleanly and clamping is visible. +- Switch the picker to OKLCH mode and drag L, then C, then H +- Push chroma past the sRGB gamut, then toggle the AA/AAA mask +Expected: each axis moves independently; the C×L plane (once 4b lands) opens on the current color; "chroma clamped to sRGB" shows on clamp; toggling the mask does not reset OKLCH mode. +*** DONE Generated ramp harmonizes +CLOSED: [2026-06-24 Wed 10:47] +What we're verifying: a ramp generated from a base color reads as one family, not a grab-bag (the aesthetic the math is meant to produce). +- Open =scripts/theme-studio/theme-studio.html= in Chrome +- Pick a mid-lightness base swatch (e.g. a blue) and generate its ramp at the defaults +- Read the row of steps left to right, then try a near-black and a near-white base +Expected: the steps share an obvious hue and step evenly in lightness; the chroma-ease keeps the extreme steps from going muddy or garish; nothing looks like it belongs to a different color. +*** DONE Color families group the way the eye reads them +CLOSED: [2026-06-24 Wed 10:51] +What we're verifying: the OKLCH hue clustering (25° gap) splits and merges families the way you'd expect, and renaming never moves a color. +- Open =scripts/theme-studio/theme-studio.html= in Chrome and load a real theme (e.g. sterling) +- Read the strips top to bottom: are "the blues" one strip, "the greens" another, neutrals and ground pinned at the top +- Find a pair you'd consider one family that landed in two strips (or two you'd consider separate that merged) +- Rename any swatch to something absurd and confirm it stays in the same strip +Expected: families match your mental grouping; the few that don't are the cue to revisit the 25° gap; renaming never regroups. +*** DONE Removed-step references read clearly as "(gone)" +CLOSED: [2026-06-24 Wed 10:46] +What we're verifying: lowering a family's count leaves a referencing face visibly stale, not silently re-pointed. +- Assign a UI or syntax element to an outer step of a family (e.g. region = a blue+3) +- Lower that family's count to 2 so blue+3 disappears +- Read the assignment's dropdown +Expected: the dropdown shows "(gone)" for the removed step, never a silent jump to a different color; re-pointing it is a deliberate choice. +*** DONE Dirvish d duplicates, D force-deletes with a confirm +CLOSED: [2026-06-24 Wed 10:52] +What we're verifying: in dirvish, d now duplicates the file at point (delete-to-trash removed), and D force-deletes the marked files via sudo rm -rf after a yes-or-no-p naming the targets. The pure command builder is unit-tested; this is the live keypress plus the guarded destructive path. +- Open dirvish on a scratch directory holding a couple of throwaway files +- Put point on a file and press d — confirm a "<name>-copy.<ext>" appears (a duplicate, nothing deleted) +- Mark one or two throwaway files, press D, and read the "Force-delete (sudo rm -rf, NO undo): <names>?" prompt +- Answer no first (confirm nothing happens), then press D again and answer yes +- Note whether sudo prompts for a password and whether the file actually disappears +Expected: d duplicates; D names the exact targets and only deletes on yes; the files are gone with no trash copy. If sudo needs a password that shell-command can't supply, flag it — the delete may need to route through a tty instead. ** PROJECT [#A] Theme-Studio Open Work Parent grouping the open theme-studio / theming issues; close each child independently. *** TODO [#A] theme-studio: consistent assignment-view table columns :feature:studio:next: @@ -624,6 +571,59 @@ 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. +** TODO [#A] Unified popup and messenger UX — placement, dismissal, one library :feature: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +Merged 2026-06-20 from the config-wide popup-policy task and the messenger-unification +task — they're the same policy at two scopes (the messenger windows are the first +concrete application of the general popup rules). Two parts: + +(A) Config-wide popup policy. All transient popups follow one set of principles. +Placement: when the Emacs frame is wider than tall, the popup rises from the right; +when square or taller, from the bottom — settle the aspect-ratio threshold and the +pop-out percentage. Dismissal: C-c C-c when there's an accept action, C-c C-k when +there's a cancel, otherwise =q= closes the window. Generalizes ai-term adaptive +placement (the aspect-ratio docking) and the messenger window/key rules below into +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. + +** TODO [#B] Un-pin ghostel from 0.33.0 once upstream fixes #422/#423 :bug: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +ghostel is held at 0.33.0 (=ghostel-20260604.2049=, commit 5779a2adceb2) in =modules/term-config.el= to dodge the 0.35.x native-PTY crash. When dakra/ghostel ships a fix for #422 (Linux malloc/signal reentrancy) and #423 (macOS recursive lock), restore =:ensure t= (drop the pin comment) and =package-upgrade ghostel=, then re-run the open-ghostel-in-a-GUI-frame survival check. Watch the two issues for the fixing commit. + +archsetup automated the zig 0.15.2 pin (managed =install_zig_pin= step, sha-verified, unit-tested). If the un-pinned ghostel bumps its ghostty dependency to a newer zig, send archsetup the new version + sha256 so it bumps its =ZIG_VERSION= / =ZIG_SHA256= constants (=inbox-send archsetup=). + +** VERIFY [#B] calendar-sync robustness: atomic writes, curl --fail, zero-event false errors :bug:solo:next: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +Deferred, pairs with the calendar-sync recurrence VERIFY above. The mechanical parts (write to a temp file + rename, add curl --fail, guard the zero-event case) are doable, but any calendar-sync change needs verification against a real .ics feed to avoid masking a genuine empty/failed sync. Do this together with the recurrence fix once you provide a fixture / confirm the live feed. +From the 2026-06 config audit, =modules/calendar-sync.el=: +- =:1309= — agenda file written via =with-temp-file= directly on the target (truncate-in-place); org-agenda/chime reading mid-write sees a partial calendar, hourly. Write temp + =rename-file= (atomic same-fs). Same for =--save-state= :258. +- =:1284= — curl runs without =--fail=: an HTTP 404/500 error page exits 0 and the HTML proceeds into conversion. +- =:1229-1233= — =--parse-ics= returns nil for both garbage and a valid calendar with zero in-window events, so healthy near-empty calendars report "parse failed" in =calendar-sync-status=. Distinguish the cases. + +** VERIFY [#B] org-roam :config triggers the 15-20s refile scan synchronously at first idle :bug:solo:next: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +Needs from Craig: this is measurement-first (perf), not a blind fix — it's the same bottleneck as the "optimize org-capture target building" debug task. Run /debug with debug-profiling to measure what actually costs the 15-20s (file count? regex? agenda rebuild?), then fix from the data. I won't restructure the refile/agenda scan without a profile. Say "let's debug it" and I'll profile + fix. +=modules/org-roam-config.el:78-79= — org-roam is =:defer 1=, so its :config calls =cj/build-org-refile-targets= at 1s idle, BEFORE the 5s background timer (=org-refile-config.el:144-151=); on a cold cache the 30k-file scan runs inline and freezes Emacs at first idle. Drop the call — org-roam is loaded long before the 5s timer fires. Likely a player in the filed org-capture 15-20s perf task (=[#B] Optimize org-capture target building performance=) — check both together. From the 2026-06 config audit. + +** VERIFY [#B] transcription: stderr never reaches the log, video transcripts stranded in /tmp :bug:solo:next: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +Deferred from the batch (no blocker; needs a focused pass with live verification). Plan: (1) transcription-config.el:210 — make-process :stderr with a file path creates a buffer, not a file; route stderr into the process buffer and write the captured text out in the sentinel, then drop the leaked buffer. (2) :370-374 — derive the txt/log base from the VIDEO path, not the temp mp3's /tmp path, so transcripts land alongside the source. The path-derivation half is cleanly unit-testable; the stderr half needs a real transcription run to verify, which is why I held it for a focused session rather than the batch. +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. + ** 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 @@ -2332,6 +2332,598 @@ From the color-assignment guide work (2026-06-08): make the tool support the gui [[id:b70b37f2-37df-4c8e-ac2f-1f20d12e33dd][theme-studio-seeding-engine-spec-doing.org]] — role table + face→role maps for syntax/UI/org, OKLCH shade generation, reseed dupre-revised to the compact mapping. Codex-reviewed, Ready. Implementation tracked under the seeding-engine parent below. *** TODO Guide-support views and advisories spec Five optional surfaces, all dismissible and non-blocking, in one collapsible panel where they advise: (1) CVD-simulation toggle on previews (deuteranopia/protanopia/tritanopia); (2) squint/blur preview toggle; (3) lightness-ramp view + palette advisories (accent count over 6-8, roles separated only by red/green) — depends on the OKLCH/ΔE core; (4) definition-vs-call / weight advisories; (5) state-over-syntax preview (region/search/diff tint over real syntax-colored text). Sequence: rewritten guide reviewed → seeding-engine spec → this. Advisories (3, 4) layer on the perceptual-metrics feature. +** TODO [#B] Auto-dim: org headings, links, and tags do not dim in unfocused windows :bug: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +auto-dim-other-buffers-affected-faces (auto-dim-config.el) remaps font-lock and a few org faces to the flat dim face, but not org-level-1..8, org-link, or org-tag, so headings, links (seen in daily-prep.org), and tags like :solo: stay lit when the window loses focus. Decide the dim approach: a flat-dim remap like font-lock (quick) versus dedicated -dim variants surfaced through org-faces / theme-studio (richer, matches the keyword work; Craig flagged org-tags may want the org-faces treatment). Consolidates three roam-inbox captures. +** TODO [#B] "? = curated help menu" convention across modes :feature: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +From the calibredb keybindings work 2026-06-06. The pattern that worked: in a modal/major-mode buffer (calibredb), bind =?= to a curated transient of the frequent workflows, and move the package's own full dispatch to =H=. It fixes the "I can't discover the keys" problem that which-key can't help with (which-key only pops up after a prefix, not for top-level single keys in a mode-map). + +Task: survey the modes/modules Craig works in and identify where a =?= -> curated-help-menu (transient) makes sense. Candidates: any major-mode buffer with single-key bindings and no good discovery affordance -- calibredb (done), nov, dirvish, mu4e, ghostel/term, signel, pearl/linear, ELFeed, etc. For each, note whether =?= is free or already a help dispatch, and whether a curated menu (vs the package's own) adds value. Establish it as a convention (and maybe a small helper/macro to define a curated =?= menu consistently). + +** TODO [#B] Dupre diff-changed / diff-refine-changed legibility :bug: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-21 +:END: +Surfaced 2026-06-07 from a pearl session designing its modified-ticket indicator (pearl marks a changed field by inheriting =diff-changed=). dupre's =diff-refine-changed= is bright gold (#ffd700) under near-white text (#f0fef0) -- WCAG contrast ~1.35, unreadable as a plain background. It only looks fine inside diff-mode because diff-mode overlays its own dark foreground. =diff-changed= (#875f00 amber) is ~5.49, readable but off the modus model. Every modus variant keeps both faces legible (contrast 9-16) by pairing a dark low-saturation background with a hue-matched foreground. + +Ask: +1. Rework dupre's =diff-changed= and =diff-refine-changed= on modus lines: dark low-saturation background, legible foreground (plain default fg for simplicity, or hue-tinted per modus -- decide), and keep refine slightly stronger than changed (refine is the word-level emphasis inside a changed region; modus keeps them distinct). +2. While there, audit dupre's broader diff/palette faces against modus conventions (background/foreground tinting, contrast targets) and flag where it diverges. + +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]]. +** TODO [#B] Fix up test runner :feature:refactor: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-21 +:END: +*** 2026-05-16 Sat @ 11:15:51 -0500 Ideas +**** Current State +=modules/test-runner.el= is a solid first pass for an Emacs-config-specific ERT +workflow: +- project-scoped focus lists +- run all vs focused mode +- run ERT test at point +- load all test files +- clear ERT tests from other project roots +- keybindings under =C-; t= + +The universal test-running direction is currently split across modules: +- =test-runner.el= owns ERT focus/state/UI. +- =dev-fkeys.el= owns F6 language detection and command generation for Elisp, + Python, Go, and partial TypeScript. + +That split is the biggest architectural pressure point. The test runner should +eventually own runner discovery, scopes, command construction, result handling, +and UI. F6 should become a thin entry point into the runner. + +**** Critical Design Issues +***** Too ERT-specific at the core +The current state model is named generically, but most operations assume: +- test files live in =test/= or =tests/= +- files match =test-*.el= +- tests are ERT forms +- individual tests can be selected by ERT selector regex +- loading tests into the current Emacs process is acceptable + +This makes the module hard to extend cleanly to pytest, Jest, Vitest, Go, Rust, +or shell test runners. The common abstraction should be "test run request" and +"test runner adapter", not "ERT file list". + +***** In-process ERT causes state contamination +=cj/test-load-all= and focused runs load test files into the current Emacs +session. This is fast and ergonomic, but it can leak: +- global variables +- advice +- loaded features +- overridden functions +- ERT test definitions +- load-path mutations + +The runner should support two ERT execution modes: +- =interactive= / in-process for fast local TDD +- =isolated= / batch Emacs for reliable verification + +The isolated path should be preferred for "before commit", CI parity, and +agent-driven verification. + +***** Test discovery is regex-based and fragile +=cj/test--extract-test-names= scans files with a regex for =ert-deftest=. +That misses or mishandles: +- macro-generated tests +- commented forms in unusual shapes +- multiline or reader-conditional forms +- non-ERT Elisp tests such as Buttercup +- stale ERT tests already loaded in the session + +Better approach: +- for ERT in isolated mode, let ERT discover tests after loading files +- for source navigation, use syntax-aware forms where possible +- store discovered tests as structured records with file, line, name, framework, + tags, and runner + +***** Path containment has at least one suspicious edge +=cj/test--do-focus-add-file= checks: + +#+begin_src elisp +(string-prefix-p (file-truename testdir) (file-truename filepath)) +#+end_src + +That should use =cj/test--file-in-directory-p= or ensure the directory has a +trailing slash. Otherwise sibling paths with a shared prefix are a recurring +class of bug. + +***** Runner commands are shell strings too early +=cj/--f6-test-runner-cmd-for= returns shell command strings. That makes it +harder to: +- inspect command parts +- safely quote arguments +- offer command editing +- run via =make-process= / =compilation-start= without shell ambiguity +- attach metadata +- rerun exact invocations +- convert commands into UI labels + +Prefer a structured command object: + +#+begin_src elisp +(:program "pytest" + :args ("tests/test_foo.py" "-q") + :default-directory "/project/" + :env (("PYTHONPATH" . "...")) + :runner pytest + :scope file) +#+end_src + +Render to a shell string only at the final compilation boundary. + +***** F6 and =C-; t= workflows duplicate the same domain +F6 already handles "all tests" and "current file's tests" for multiple +languages. =C-; t= handles ERT-only focus and run state. These should converge +on one runner service: +- F6: quick entry point +- =C-; t=: full runner menu +- both call the same scope/adapter engine + +***** Test directory discovery is too narrow +Current discovery prefers =test/= then =tests/=, with a global fallback. Real +projects often need: +- Python: =tests/=, package-local =test_*.py=, =pytest.ini=, =pyproject.toml= +- JS/TS: =package.json= scripts, =vitest.config.*=, =jest.config.*=, + =*.test.ts=, =*.spec.ts= +- Go: package directories, =go.mod= +- Rust: =Cargo.toml=, integration tests under =tests/= +- Elisp packages: =Makefile=, =Eask=, =ert-runner=, Buttercup, =tests/= + +Discovery should be adapter-specific and project-config-aware. + +***** No structured result model +=cj/test-last-results= exists but is not meaningfully populated. A powerful +runner needs a normalized result model: +- run id +- started/finished timestamps +- status: passed/failed/errored/cancelled/skipped/xfail/xpass +- command +- runner adapter +- scope +- exit code +- duration +- failed test records +- file/line locations +- raw output buffer +- coverage artifact paths + +This enables last-failed, failures-first, summaries, dashboards, and AI-assisted +failure explanation. + +***** No failure parser / navigation layer +Compilation buffers are useful, but the runner should parse common failure +formats and provide: +- next/previous failure +- jump to source line +- failure summary buffer +- copy failure context +- rerun failed test at point +- annotate failing tests in source buffers + +Adapters can provide regexes/parsers for ERT, pytest, Jest/Vitest, Go, Rust, +and shell. + +***** Missing watch/rerun modes +Modern test runners optimize the feedback loop: +- pytest supports selecting tests, markers, last-failed, failures-first, + stepwise, fixtures, xfail/skip, plugins, and cache state. +- Jest/Vitest support watch workflows, changed-file selection, coverage, + snapshots, and rich interactive filtering. Vitest also defaults to watch in + development and run mode in CI. +- Go and Rust runners commonly support package-level runs, regex selection, + race/coverage flags, and cached test behavior. + +The Emacs runner should expose the subset that maps well to editor workflows: +- current test +- current file +- related test file +- focused set +- last failed +- failed first +- changed since git base +- watch current scope +- full project +- coverage for current scope + +**** Proposed Architecture +***** Core Types +Use plain plists initially; promote to =cl-defstruct= only if helpful. + +#+begin_src elisp +;; Test runner adapter +(:id pytest + :name "pytest" + :languages (python) + :detect cj/test-pytest-detect + :discover cj/test-pytest-discover + :build-command cj/test-pytest-build-command + :parse-results cj/test-pytest-parse-results + :capabilities (:current-test :file :project :last-failed :coverage :watch)) + +;; Test run request +(:project-root "/repo/" + :language python + :framework pytest + :scope file + :file "/repo/tests/test_api.py" + :test-name "test_create_user" + :extra-args ("-q") + :profile default) + +;; Test run result +(:run-id "..." + :status failed + :exit-code 1 + :duration 2.14 + :failures (...) + :output-buffer "*test pytest*" + :artifacts (...)) +#+end_src + +***** Adapter Registry +Create a registry like: + +#+begin_src elisp +(defvar cj/test-runner-adapters nil) +(cj/test-register-adapter 'pytest ...) +(cj/test-register-adapter 'ert ...) +(cj/test-register-adapter 'vitest ...) +#+end_src + +Runner selection should consider: +- buffer file extension +- project files +- explicit user override +- available executables +- package manager scripts +- existing Makefile targets + +***** Scope Model +Make scopes explicit and shared across languages: +- =test-at-point= +- =current-file= +- =related-file= +- =focused-files= +- =last-failed= +- =changed= +- =package/module= +- =project= +- =coverage= +- =watch= + +Each adapter can say which scopes it supports. Unsupported scopes should produce +clear user-errors with suggestions. + +***** Command Builder Pipeline +1. Detect project. +2. Detect language/framework candidates. +3. Resolve user-requested scope. +4. Build structured command object. +5. Optionally let user edit command. +6. Run via =compilation-start= or =make-process=. +7. Parse output/result artifacts. +8. Store normalized result. +9. Update UI/modeline/messages/failure buffer. + +***** Keep Makefile Support But Do Not Require It +For this Emacs config, =make test-file= and =make test-name= are useful and +should remain the default Elisp isolated path. But adapter detection should +support: +- direct =emacs --batch= ERT invocation +- =make test= +- =make test-file= +- =make test-name= +- Eask +- Buttercup + +**** Elisp-Specific Improvements +***** Add isolated ERT runs +Support batch commands for: +- all project tests +- one test file +- one test name +- focused files +- last failed, once result parsing exists + +Use the same Makefile targets in this repo, but design the adapter so other +Elisp projects can run without this Makefile. + +***** Support Buttercup/Eask Later +Buttercup uses BDD-style =describe= / =it= suites and is common in Elisp +package testing. Eask is often used to run package tests. Add adapter slots +for these instead of hard-coding ERT forever. + +***** Avoid unnecessary global ERT deletion +=cj/ert-clear-tests= is a pragmatic fix for project contamination, but the +stronger long-term answer is isolated runs plus project-scoped discovery. Keep +the cleanup command, but do not make correctness depend on deleting global ERT +state. + +**** Python / pytest Ideas +- Detect pytest by =pyproject.toml=, =pytest.ini=, =tox.ini=, =setup.cfg=, or + presence of =tests/=. +- Build commands for: + - project: =pytest= + - file: =pytest path/to/test_file.py= + - test at point: =pytest path/to/test_file.py::test_name= + - class method: =pytest path::TestClass::test_method= + - marker: =pytest -m marker= + - last failed: =pytest --lf= + - failed first: =pytest --ff= + - stop after first: =pytest -x= + - coverage: =pytest --cov=...= +- Parse output for failing node ids and =file:line= references. +- Read pytest cache for last-failed where useful. +- Offer marker completion by parsing =pytest --markers= or config files. +- Surface xfail/skip separately from hard failures. + +**** TypeScript / JavaScript Ideas +***** Detection +Detect runner by project files and scripts: +- =vitest.config.ts/js/mts/mjs= +- =jest.config.ts/js/mjs/cjs= +- =package.json= scripts: =test=, =test:watch=, =vitest=, =jest= +- lockfile/package manager: =pnpm-lock.yaml=, =yarn.lock=, =package-lock.json=, + =bun.lockb= + +Prefer project scripts over raw =npx= when present: +- =pnpm test -- path= +- =npm test -- path= +- =yarn test path= +- =bun test path= + +***** Scopes +- current file: =vitest run path= or =jest path= +- test at point: use nearest =it= / =test= / =describe= string and pass =-t= +- watch current file +- changed tests where runner supports it +- coverage current file/project +- update snapshots + +***** Result Parsing +Parse: +- failing test names +- file paths and line numbers +- snapshot failures +- coverage summary + +Treat snapshot updates as an explicit command, not an automatic side effect. + +**** Go Ideas +- Detect =go.mod=. +- Current file/source: run package =go test ./pkg=. +- Test at point: nearest =func TestXxx= and run =go test ./pkg -run '^TestXxx$'=. +- Bench at point: nearest =BenchmarkXxx= and run =go test -bench '^BenchmarkXxx$'=. +- Add toggles for =-race=, =-cover=, =-count=1=, =-v=. +- Parse =file.go:line:= output and package failure summaries. + +**** Rust Ideas +- Detect =Cargo.toml=. +- Use =cargo test= by default, optionally =cargo nextest run= when available. +- Current test at point: nearest =#[test]= function. +- Current file/module where possible. +- Integration test file: =cargo test --test name=. +- Support =-- --nocapture= toggle. +- Parse compiler/test failures and =file:line= links. + +**** Shell / Generic Ideas +- Adapter for Makefile targets: + - detect =make test=, =make check=, =make coverage= + - expose project-level commands even when language-specific detection fails +- Adapter for arbitrary project command configured in dir-locals or a project + config plist. +- Let users register custom command templates per project: + +#+begin_src elisp +((:name "unit" + :command ("npm" "run" "test:unit" "--" "{file}")) + (:name "integration" + :command ("pytest" "tests/integration" "-q"))) +#+end_src + +**** UI Ideas +***** Transient Menu +Replace or complement the raw keymap with a =transient= menu: +- scope: current test/file/focused/last failed/project +- runner: auto/ert/pytest/vitest/jest/go/cargo/make +- toggles: watch, coverage, debug, fail-fast, verbose, update snapshots +- actions: run, rerun, edit command, show failures, open report + +***** Result Buffer +Create a normalized =*Test Results*= buffer: +- latest status per project +- command and duration +- pass/fail/skip counts +- failure list with clickable =file:line= +- actions to rerun failed/current/all +- links to coverage artifacts + +***** Modeline / Headerline Signal +Show the last run status for the current project: +- green passed +- red failed +- yellow running +- gray no run + +Keep it quiet and optional. + +***** History +Store recent run requests per project: +- rerun last +- rerun last failed +- choose previous command +- compare duration/status against previous run + +**** Configuration Ideas +- =cj/test-runner-default-scope= +- =cj/test-runner-prefer-isolated-elisp= +- =cj/test-runner-project-overrides= +- =cj/test-runner-known-adapters= +- =cj/test-runner-enable-watch= +- =cj/test-runner-result-retention= +- per-project override through =.dir-locals.el= + +Example: + +#+begin_src elisp +((nil . ((cj/test-runner-project-overrides + . (:adapter pytest + :default-args ("-q") + :coverage-args ("--cov=src")))))) +#+end_src + +**** Safety And Robustness +- Use structured commands until the final boundary. +- Quote only at render time. +- Avoid shell when =make-process= / =process-file= is sufficient. +- Keep command preview/editing available for surprising cases. +- Detect missing executables before running. +- Add timeouts/cancel commands for long-running or hung tests. +- Do not silently fall back from a missing runner to a different runner unless + the fallback is visible in the command preview. +- Avoid mutating global =load-path= permanently. +- Keep remote/TRAMP behavior explicit; do not accidentally run local commands + for remote projects. + +**** Coverage Integration +Tie this into the existing coverage work: +- run coverage for current file/scope +- open latest coverage report +- summarize uncovered lines for current file +- support Elisp SimpleCov/Undercover, pytest-cov, Vitest coverage, Go cover, + and Rust coverage later +- store coverage artifact paths in the normalized run result + +**** AI-Assisted Debugging Ideas +- Summarize failing tests from the parsed failure records and raw output. +- Include command, changed files, failure snippets, and relevant source/test + locations. +- Redact env vars, tokens, Authorization headers, and secrets before sending to + =gptel=. +- Add commands: + - =cj/test-runner-explain-failure= + - =cj/test-runner-suggest-related-tests= + - =cj/test-runner-summarize-coverage-gap= + +**** Migration Plan +***** Phase 1: Internal cleanup +- Fix the task typo and rename current ERT-specific functions or wrap them under + an ERT adapter. +- Move F6 language detection/command construction from =dev-fkeys.el= into + =test-runner.el= or a new =test-runner-core.el=. +- Replace shell-string command builders with structured command plists. +- Fix path containment in =cj/test--do-focus-add-file=. +- Make =cj/test-last-results= real for ERT runs. + +***** Phase 2: ERT adapter +- Implement adapter registry. +- Add ERT adapter with in-process and isolated modes. +- Preserve all current keybindings by routing them through the adapter. +- Add failure/result normalization for ERT. +- Add "rerun last" and "rerun failed" for ERT. + +***** Phase 3: Python and JS/TS adapters +- Add pytest adapter. +- Add Vitest/Jest adapter with package-manager/script detection. +- Support current file and test-at-point for both. +- Add parser/navigation for common failures. + +***** Phase 4: UI and watch modes +- Add transient menu. +- Add result buffer. +- Add cancellation and rerun history. +- Add watch commands where supported. + +***** Phase 5: Coverage and AI +- Connect coverage commands to adapter capabilities. +- Add failure summarization with redaction. +- Add coverage-gap summarization. + +**** Acceptance Criteria For First Fix-Up Pass +- Existing ERT workflow still works. +- F6 and =C-; t= use the same underlying runner API. +- Current-file test command generation is covered for Elisp, Python, Go, + TypeScript, and JavaScript. +- At least one isolated ERT command path exists. +- Path containment checks are robust against sibling-prefix paths and symlinks. +- Runner requests and results are represented as data, not only messages. +- Missing runner/tool errors are clear and actionable. +- Tests cover adapter detection, command building, scope resolution, result + storage, and key interactive paths. + +** TODO [#B] Keymap consolidation — resolve decisions, run Phase 1-2 :feature:refactor:solo: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-13 +:END: +Spec: [[id:540bf06b-16b8-46c6-b459-c40d1b9c795d][keybinding-console-safety-spec-doing.org]]. Phase 0 (revert 4a1ecf64) is done and pushed. Decisions D1-D5 are open TODOs in the spec; D2/D4/D5 gate the primary work (Phase 1 prune via Appendix D, Phase 2 consolidate + retire the translation block), while D1/D3 (the console-safe prefix) gate only the optional Phase 3 and can stay open indefinitely. Resolve D2/D4/D5, then run Phase 1-2. Appendix D is the keybinding pruning checklist. Add a =#+TODO: TODO | DONE SUPERSEDED CANCELLED= header line to the spec if adopting those decision keywords (rulesets convention update, 2026-06-12). + +** TODO [#B] ledger-config is orphaned — ledger-mode never configured :bug:quick: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-21 +:END: +Nothing requires =modules/ledger-config.el= (verified by grep), so .dat/.ledger/.journal open without ledger-mode, reports, or flycheck-ledger. The module looks finished, not staged (unlike duet-config, which documents its pre-alpha orphaning). Decide: wire into init.el (+ =cj/executable-find-or-warn= for the ledger binary) or delete. From the 2026-06 config audit. + +** 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: +Three projects shipped themeable faces and asked theme-studio to render accurate previews. Data lives in the PROCESSED handoff files. +*** TODO pearl — 6 faces + overlay-driven appearance +Six faces in the =pearl= customize group plus overlay-driven appearance a raw buffer read won't show. =inbox/PROCESSED-2026-06-23-2239-from-pearl-theme-studio-pearl-spec.org= + cover + =sample-pearl-buffer.org=. +*** TODO emacs-wttrin — 4 new faces +Was hardcoded "gray60"; now four customizable faces (branch =feature/themeable-faces=). =inbox/PROCESSED-2026-06-23-2253-from-emacs-wttrin-wttrin-faces-handoff.org= + rendered sample. +*** TODO chime — 4 themeable modeline faces +Four modeline faces shipped (081d76e). =inbox/PROCESSED-2026-06-23-2326-from-chime-chime-added-four-themeable-modeline.org=. +** TODO [#C] VAMP — extract music-config into a standalone player :feature:refactor: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-21 +:END: +Build VAMP ("VAMP Audio Music Player"), a standalone, publishable Emacs music player at =~/code/vamp= — derived from a maintained subset of EMMS, depending on the EMMS package not at all, with MPV and mpd behind a generalized adapter API. =.emacs.d= keeps thin glue (=vamp-config.el=: keybindings, paths, dashboard); archsetup owns OS wiring (Super+/ launcher, m3u MIME). Models the =linear-config= → =pearl= migration. + +Brainstorm complete 2026-06-22 — validated design at [[file:docs/design/vamp-music-player.org][docs/design/vamp-music-player.org]]. It builds on the prior EMMS-removal work ([[file:docs/specs/music-config-without-emms-spec.org][spec]] + [[file:docs/design/music-config-without-emms-review.org][2026-05-15 review]]), confirming its B1/B2/B4/S3 decisions and pivoting four things (publishable-now, two adapters + generalized API, VAMP name, desktop integration). + +Next: (1) revise the spec to the new direction; (2) spike the risky assumptions (mpd dumb-single-file-player contract; m3u =.desktop= on Hyprland); (3) =/start-work= against the revised spec — pure-helper extraction (review Migration Plan step 1) is the safe first phase. Priority [#C] is a placeholder pending Craig's call. + +** TODO [#C] ai-term: step between running ai-terms even when detached :feature:solo:next: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-22 +:END: +The step-to-next-agent family (s-F9 and friends) should cycle to a running ai-term even when that ai-term is currently detached, instead of skipping it. Today the step only lands on attached/visible ai-terms, so a detached-but-running agent gets passed over and there's no keyboard path back to it — re-attach/display it on landing. From the roam inbox. + +** TODO [#C] ai-term: multi-backend (Claude / Codex / local ollama) :feature: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-22 +:END: +Allow creating an ai-term backed by any of Claude, Codex, or a local LLM via ollama, with the backend chosen seamlessly at the start of the session. ai-term currently assumes Claude; generalize the launch path so the agent backend is a selectable parameter and switching between them at session start is frictionless. Routed here from the rulesets roam-inbox item "multiple agent source improvements" (its bullet 3 asked to send emacs this note); the item's other bullets — naming the agent so non-Claude agents aren't called "Claude", and tightening workflow wording for Codex's more literal reading — stay with rulesets. + +** TODO [#C] Compare terminal themeability: EAT vs vterm vs ghostel :feature:solo:next: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-22 +: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. + +** VERIFY [#C] Remove unused system-power keybindings :refactor:quick:next: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-20 +:END: +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. + +#+begin_src cj: comment + remove them all. +#+end_src + ** PROJECT [#C] Music Open Work Parent grouping the open music / EMMS issues; close each child independently. *** VERIFY [#C] music: extract faces for music config :refactor:quick:solo:next: @@ -2972,51 +3564,6 @@ Restart the daemon, open a GUI frame, trigger an encrypted decrypt, confirm =pin *** TODO [#C] Archive the original L3813 task After this work lands, mark the original "Finish terminal GPG pinentry configuration" task DONE with a =CLOSED:= stamp and a one-line note pointing at this parent task. -** TODO [#A] Unified popup and messenger UX — placement, dismissal, one library :feature: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -Merged 2026-06-20 from the config-wide popup-policy task and the messenger-unification -task — they're the same policy at two scopes (the messenger windows are the first -concrete application of the general popup rules). Two parts: - -(A) Config-wide popup policy. All transient popups follow one set of principles. -Placement: when the Emacs frame is wider than tall, the popup rises from the right; -when square or taller, from the bottom — settle the aspect-ratio threshold and the -pop-out percentage. Dismissal: C-c C-c when there's an accept action, C-c C-k when -there's a cancel, otherwise =q= closes the window. Generalizes ai-term adaptive -placement (the aspect-ratio docking) and the messenger window/key rules below into -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. - -** TODO [#B] Auto-dim: org headings, links, and tags do not dim in unfocused windows :bug: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -auto-dim-other-buffers-affected-faces (auto-dim-config.el) remaps font-lock and a few org faces to the flat dim face, but not org-level-1..8, org-link, or org-tag, so headings, links (seen in daily-prep.org), and tags like :solo: stay lit when the window loses focus. Decide the dim approach: a flat-dim remap like font-lock (quick) versus dedicated -dim variants surfaced through org-faces / theme-studio (richer, matches the keyword work; Craig flagged org-tags may want the org-faces treatment). Consolidates three roam-inbox captures. -** TODO [#B] "? = curated help menu" convention across modes :feature: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-20 -:END: -From the calibredb keybindings work 2026-06-06. The pattern that worked: in a modal/major-mode buffer (calibredb), bind =?= to a curated transient of the frequent workflows, and move the package's own full dispatch to =H=. It fixes the "I can't discover the keys" problem that which-key can't help with (which-key only pops up after a prefix, not for top-level single keys in a mode-map). - -Task: survey the modes/modules Craig works in and identify where a =?= -> curated-help-menu (transient) makes sense. Candidates: any major-mode buffer with single-key bindings and no good discovery affordance -- calibredb (done), nov, dirvish, mu4e, ghostel/term, signel, pearl/linear, ELFeed, etc. For each, note whether =?= is free or already a help dispatch, and whether a curated menu (vs the package's own) adds value. Establish it as a convention (and maybe a small helper/macro to define a curated =?= menu consistently). - -** TODO [#B] Dupre diff-changed / diff-refine-changed legibility :bug: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-21 -:END: -Surfaced 2026-06-07 from a pearl session designing its modified-ticket indicator (pearl marks a changed field by inheriting =diff-changed=). dupre's =diff-refine-changed= is bright gold (#ffd700) under near-white text (#f0fef0) -- WCAG contrast ~1.35, unreadable as a plain background. It only looks fine inside diff-mode because diff-mode overlays its own dark foreground. =diff-changed= (#875f00 amber) is ~5.49, readable but off the modus model. Every modus variant keeps both faces legible (contrast 9-16) by pairing a dark low-saturation background with a hue-matched foreground. - -Ask: -1. Rework dupre's =diff-changed= and =diff-refine-changed= on modus lines: dark low-saturation background, legible foreground (plain default fg for simplicity, or hue-tinted per modus -- decide), and keep refine slightly stronger than changed (refine is the word-level emphasis inside a changed region; modus keeps them distinct). -2. While there, audit dupre's broader diff/palette faces against modus conventions (background/foreground tinting, contrast targets) and flag where it diverges. - -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]]. ** TODO [#C] Migrate tests off mocking primitives (native-comp robustness) :test:refactor: :PROPERTIES: :LAST_REVIEWED: 2026-06-21 @@ -3029,522 +3576,6 @@ This task is the durable fix the ecosystem and =elisp-testing.md= point to: rest Full mechanism, the three failure modes, the research (Emacs bug#51140, bug#61880, buttercup #230, Debian #1021842, the emacs-29 redefine-primitive warning, the manual on =native-comp-enable-subr-trampolines=), and the decision: [[file:docs/native-comp-subr-mocking.org][docs/native-comp-subr-mocking.org]]. -** TODO [#B] Fix up test runner :feature:refactor: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-21 -:END: -*** 2026-05-16 Sat @ 11:15:51 -0500 Ideas -**** Current State -=modules/test-runner.el= is a solid first pass for an Emacs-config-specific ERT -workflow: -- project-scoped focus lists -- run all vs focused mode -- run ERT test at point -- load all test files -- clear ERT tests from other project roots -- keybindings under =C-; t= - -The universal test-running direction is currently split across modules: -- =test-runner.el= owns ERT focus/state/UI. -- =dev-fkeys.el= owns F6 language detection and command generation for Elisp, - Python, Go, and partial TypeScript. - -That split is the biggest architectural pressure point. The test runner should -eventually own runner discovery, scopes, command construction, result handling, -and UI. F6 should become a thin entry point into the runner. - -**** Critical Design Issues -***** Too ERT-specific at the core -The current state model is named generically, but most operations assume: -- test files live in =test/= or =tests/= -- files match =test-*.el= -- tests are ERT forms -- individual tests can be selected by ERT selector regex -- loading tests into the current Emacs process is acceptable - -This makes the module hard to extend cleanly to pytest, Jest, Vitest, Go, Rust, -or shell test runners. The common abstraction should be "test run request" and -"test runner adapter", not "ERT file list". - -***** In-process ERT causes state contamination -=cj/test-load-all= and focused runs load test files into the current Emacs -session. This is fast and ergonomic, but it can leak: -- global variables -- advice -- loaded features -- overridden functions -- ERT test definitions -- load-path mutations - -The runner should support two ERT execution modes: -- =interactive= / in-process for fast local TDD -- =isolated= / batch Emacs for reliable verification - -The isolated path should be preferred for "before commit", CI parity, and -agent-driven verification. - -***** Test discovery is regex-based and fragile -=cj/test--extract-test-names= scans files with a regex for =ert-deftest=. -That misses or mishandles: -- macro-generated tests -- commented forms in unusual shapes -- multiline or reader-conditional forms -- non-ERT Elisp tests such as Buttercup -- stale ERT tests already loaded in the session - -Better approach: -- for ERT in isolated mode, let ERT discover tests after loading files -- for source navigation, use syntax-aware forms where possible -- store discovered tests as structured records with file, line, name, framework, - tags, and runner - -***** Path containment has at least one suspicious edge -=cj/test--do-focus-add-file= checks: - -#+begin_src elisp -(string-prefix-p (file-truename testdir) (file-truename filepath)) -#+end_src - -That should use =cj/test--file-in-directory-p= or ensure the directory has a -trailing slash. Otherwise sibling paths with a shared prefix are a recurring -class of bug. - -***** Runner commands are shell strings too early -=cj/--f6-test-runner-cmd-for= returns shell command strings. That makes it -harder to: -- inspect command parts -- safely quote arguments -- offer command editing -- run via =make-process= / =compilation-start= without shell ambiguity -- attach metadata -- rerun exact invocations -- convert commands into UI labels - -Prefer a structured command object: - -#+begin_src elisp -(:program "pytest" - :args ("tests/test_foo.py" "-q") - :default-directory "/project/" - :env (("PYTHONPATH" . "...")) - :runner pytest - :scope file) -#+end_src - -Render to a shell string only at the final compilation boundary. - -***** F6 and =C-; t= workflows duplicate the same domain -F6 already handles "all tests" and "current file's tests" for multiple -languages. =C-; t= handles ERT-only focus and run state. These should converge -on one runner service: -- F6: quick entry point -- =C-; t=: full runner menu -- both call the same scope/adapter engine - -***** Test directory discovery is too narrow -Current discovery prefers =test/= then =tests/=, with a global fallback. Real -projects often need: -- Python: =tests/=, package-local =test_*.py=, =pytest.ini=, =pyproject.toml= -- JS/TS: =package.json= scripts, =vitest.config.*=, =jest.config.*=, - =*.test.ts=, =*.spec.ts= -- Go: package directories, =go.mod= -- Rust: =Cargo.toml=, integration tests under =tests/= -- Elisp packages: =Makefile=, =Eask=, =ert-runner=, Buttercup, =tests/= - -Discovery should be adapter-specific and project-config-aware. - -***** No structured result model -=cj/test-last-results= exists but is not meaningfully populated. A powerful -runner needs a normalized result model: -- run id -- started/finished timestamps -- status: passed/failed/errored/cancelled/skipped/xfail/xpass -- command -- runner adapter -- scope -- exit code -- duration -- failed test records -- file/line locations -- raw output buffer -- coverage artifact paths - -This enables last-failed, failures-first, summaries, dashboards, and AI-assisted -failure explanation. - -***** No failure parser / navigation layer -Compilation buffers are useful, but the runner should parse common failure -formats and provide: -- next/previous failure -- jump to source line -- failure summary buffer -- copy failure context -- rerun failed test at point -- annotate failing tests in source buffers - -Adapters can provide regexes/parsers for ERT, pytest, Jest/Vitest, Go, Rust, -and shell. - -***** Missing watch/rerun modes -Modern test runners optimize the feedback loop: -- pytest supports selecting tests, markers, last-failed, failures-first, - stepwise, fixtures, xfail/skip, plugins, and cache state. -- Jest/Vitest support watch workflows, changed-file selection, coverage, - snapshots, and rich interactive filtering. Vitest also defaults to watch in - development and run mode in CI. -- Go and Rust runners commonly support package-level runs, regex selection, - race/coverage flags, and cached test behavior. - -The Emacs runner should expose the subset that maps well to editor workflows: -- current test -- current file -- related test file -- focused set -- last failed -- failed first -- changed since git base -- watch current scope -- full project -- coverage for current scope - -**** Proposed Architecture -***** Core Types -Use plain plists initially; promote to =cl-defstruct= only if helpful. - -#+begin_src elisp -;; Test runner adapter -(:id pytest - :name "pytest" - :languages (python) - :detect cj/test-pytest-detect - :discover cj/test-pytest-discover - :build-command cj/test-pytest-build-command - :parse-results cj/test-pytest-parse-results - :capabilities (:current-test :file :project :last-failed :coverage :watch)) - -;; Test run request -(:project-root "/repo/" - :language python - :framework pytest - :scope file - :file "/repo/tests/test_api.py" - :test-name "test_create_user" - :extra-args ("-q") - :profile default) - -;; Test run result -(:run-id "..." - :status failed - :exit-code 1 - :duration 2.14 - :failures (...) - :output-buffer "*test pytest*" - :artifacts (...)) -#+end_src - -***** Adapter Registry -Create a registry like: - -#+begin_src elisp -(defvar cj/test-runner-adapters nil) -(cj/test-register-adapter 'pytest ...) -(cj/test-register-adapter 'ert ...) -(cj/test-register-adapter 'vitest ...) -#+end_src - -Runner selection should consider: -- buffer file extension -- project files -- explicit user override -- available executables -- package manager scripts -- existing Makefile targets - -***** Scope Model -Make scopes explicit and shared across languages: -- =test-at-point= -- =current-file= -- =related-file= -- =focused-files= -- =last-failed= -- =changed= -- =package/module= -- =project= -- =coverage= -- =watch= - -Each adapter can say which scopes it supports. Unsupported scopes should produce -clear user-errors with suggestions. - -***** Command Builder Pipeline -1. Detect project. -2. Detect language/framework candidates. -3. Resolve user-requested scope. -4. Build structured command object. -5. Optionally let user edit command. -6. Run via =compilation-start= or =make-process=. -7. Parse output/result artifacts. -8. Store normalized result. -9. Update UI/modeline/messages/failure buffer. - -***** Keep Makefile Support But Do Not Require It -For this Emacs config, =make test-file= and =make test-name= are useful and -should remain the default Elisp isolated path. But adapter detection should -support: -- direct =emacs --batch= ERT invocation -- =make test= -- =make test-file= -- =make test-name= -- Eask -- Buttercup - -**** Elisp-Specific Improvements -***** Add isolated ERT runs -Support batch commands for: -- all project tests -- one test file -- one test name -- focused files -- last failed, once result parsing exists - -Use the same Makefile targets in this repo, but design the adapter so other -Elisp projects can run without this Makefile. - -***** Support Buttercup/Eask Later -Buttercup uses BDD-style =describe= / =it= suites and is common in Elisp -package testing. Eask is often used to run package tests. Add adapter slots -for these instead of hard-coding ERT forever. - -***** Avoid unnecessary global ERT deletion -=cj/ert-clear-tests= is a pragmatic fix for project contamination, but the -stronger long-term answer is isolated runs plus project-scoped discovery. Keep -the cleanup command, but do not make correctness depend on deleting global ERT -state. - -**** Python / pytest Ideas -- Detect pytest by =pyproject.toml=, =pytest.ini=, =tox.ini=, =setup.cfg=, or - presence of =tests/=. -- Build commands for: - - project: =pytest= - - file: =pytest path/to/test_file.py= - - test at point: =pytest path/to/test_file.py::test_name= - - class method: =pytest path::TestClass::test_method= - - marker: =pytest -m marker= - - last failed: =pytest --lf= - - failed first: =pytest --ff= - - stop after first: =pytest -x= - - coverage: =pytest --cov=...= -- Parse output for failing node ids and =file:line= references. -- Read pytest cache for last-failed where useful. -- Offer marker completion by parsing =pytest --markers= or config files. -- Surface xfail/skip separately from hard failures. - -**** TypeScript / JavaScript Ideas -***** Detection -Detect runner by project files and scripts: -- =vitest.config.ts/js/mts/mjs= -- =jest.config.ts/js/mjs/cjs= -- =package.json= scripts: =test=, =test:watch=, =vitest=, =jest= -- lockfile/package manager: =pnpm-lock.yaml=, =yarn.lock=, =package-lock.json=, - =bun.lockb= - -Prefer project scripts over raw =npx= when present: -- =pnpm test -- path= -- =npm test -- path= -- =yarn test path= -- =bun test path= - -***** Scopes -- current file: =vitest run path= or =jest path= -- test at point: use nearest =it= / =test= / =describe= string and pass =-t= -- watch current file -- changed tests where runner supports it -- coverage current file/project -- update snapshots - -***** Result Parsing -Parse: -- failing test names -- file paths and line numbers -- snapshot failures -- coverage summary - -Treat snapshot updates as an explicit command, not an automatic side effect. - -**** Go Ideas -- Detect =go.mod=. -- Current file/source: run package =go test ./pkg=. -- Test at point: nearest =func TestXxx= and run =go test ./pkg -run '^TestXxx$'=. -- Bench at point: nearest =BenchmarkXxx= and run =go test -bench '^BenchmarkXxx$'=. -- Add toggles for =-race=, =-cover=, =-count=1=, =-v=. -- Parse =file.go:line:= output and package failure summaries. - -**** Rust Ideas -- Detect =Cargo.toml=. -- Use =cargo test= by default, optionally =cargo nextest run= when available. -- Current test at point: nearest =#[test]= function. -- Current file/module where possible. -- Integration test file: =cargo test --test name=. -- Support =-- --nocapture= toggle. -- Parse compiler/test failures and =file:line= links. - -**** Shell / Generic Ideas -- Adapter for Makefile targets: - - detect =make test=, =make check=, =make coverage= - - expose project-level commands even when language-specific detection fails -- Adapter for arbitrary project command configured in dir-locals or a project - config plist. -- Let users register custom command templates per project: - -#+begin_src elisp -((:name "unit" - :command ("npm" "run" "test:unit" "--" "{file}")) - (:name "integration" - :command ("pytest" "tests/integration" "-q"))) -#+end_src - -**** UI Ideas -***** Transient Menu -Replace or complement the raw keymap with a =transient= menu: -- scope: current test/file/focused/last failed/project -- runner: auto/ert/pytest/vitest/jest/go/cargo/make -- toggles: watch, coverage, debug, fail-fast, verbose, update snapshots -- actions: run, rerun, edit command, show failures, open report - -***** Result Buffer -Create a normalized =*Test Results*= buffer: -- latest status per project -- command and duration -- pass/fail/skip counts -- failure list with clickable =file:line= -- actions to rerun failed/current/all -- links to coverage artifacts - -***** Modeline / Headerline Signal -Show the last run status for the current project: -- green passed -- red failed -- yellow running -- gray no run - -Keep it quiet and optional. - -***** History -Store recent run requests per project: -- rerun last -- rerun last failed -- choose previous command -- compare duration/status against previous run - -**** Configuration Ideas -- =cj/test-runner-default-scope= -- =cj/test-runner-prefer-isolated-elisp= -- =cj/test-runner-project-overrides= -- =cj/test-runner-known-adapters= -- =cj/test-runner-enable-watch= -- =cj/test-runner-result-retention= -- per-project override through =.dir-locals.el= - -Example: - -#+begin_src elisp -((nil . ((cj/test-runner-project-overrides - . (:adapter pytest - :default-args ("-q") - :coverage-args ("--cov=src")))))) -#+end_src - -**** Safety And Robustness -- Use structured commands until the final boundary. -- Quote only at render time. -- Avoid shell when =make-process= / =process-file= is sufficient. -- Keep command preview/editing available for surprising cases. -- Detect missing executables before running. -- Add timeouts/cancel commands for long-running or hung tests. -- Do not silently fall back from a missing runner to a different runner unless - the fallback is visible in the command preview. -- Avoid mutating global =load-path= permanently. -- Keep remote/TRAMP behavior explicit; do not accidentally run local commands - for remote projects. - -**** Coverage Integration -Tie this into the existing coverage work: -- run coverage for current file/scope -- open latest coverage report -- summarize uncovered lines for current file -- support Elisp SimpleCov/Undercover, pytest-cov, Vitest coverage, Go cover, - and Rust coverage later -- store coverage artifact paths in the normalized run result - -**** AI-Assisted Debugging Ideas -- Summarize failing tests from the parsed failure records and raw output. -- Include command, changed files, failure snippets, and relevant source/test - locations. -- Redact env vars, tokens, Authorization headers, and secrets before sending to - =gptel=. -- Add commands: - - =cj/test-runner-explain-failure= - - =cj/test-runner-suggest-related-tests= - - =cj/test-runner-summarize-coverage-gap= - -**** Migration Plan -***** Phase 1: Internal cleanup -- Fix the task typo and rename current ERT-specific functions or wrap them under - an ERT adapter. -- Move F6 language detection/command construction from =dev-fkeys.el= into - =test-runner.el= or a new =test-runner-core.el=. -- Replace shell-string command builders with structured command plists. -- Fix path containment in =cj/test--do-focus-add-file=. -- Make =cj/test-last-results= real for ERT runs. - -***** Phase 2: ERT adapter -- Implement adapter registry. -- Add ERT adapter with in-process and isolated modes. -- Preserve all current keybindings by routing them through the adapter. -- Add failure/result normalization for ERT. -- Add "rerun last" and "rerun failed" for ERT. - -***** Phase 3: Python and JS/TS adapters -- Add pytest adapter. -- Add Vitest/Jest adapter with package-manager/script detection. -- Support current file and test-at-point for both. -- Add parser/navigation for common failures. - -***** Phase 4: UI and watch modes -- Add transient menu. -- Add result buffer. -- Add cancellation and rerun history. -- Add watch commands where supported. - -***** Phase 5: Coverage and AI -- Connect coverage commands to adapter capabilities. -- Add failure summarization with redaction. -- Add coverage-gap summarization. - -**** Acceptance Criteria For First Fix-Up Pass -- Existing ERT workflow still works. -- F6 and =C-; t= use the same underlying runner API. -- Current-file test command generation is covered for Elisp, Python, Go, - TypeScript, and JavaScript. -- At least one isolated ERT command path exists. -- Path containment checks are robust against sibling-prefix paths and symlinks. -- Runner requests and results are represented as data, not only messages. -- Missing runner/tool errors are clear and actionable. -- Tests cover adapter detection, command building, scope resolution, result - storage, and key interactive paths. - -** TODO [#B] Keymap consolidation — resolve decisions, run Phase 1-2 :feature:refactor:solo: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-13 -:END: -Spec: [[id:540bf06b-16b8-46c6-b459-c40d1b9c795d][keybinding-console-safety-spec-doing.org]]. Phase 0 (revert 4a1ecf64) is done and pushed. Decisions D1-D5 are open TODOs in the spec; D2/D4/D5 gate the primary work (Phase 1 prune via Appendix D, Phase 2 consolidate + retire the translation block), while D1/D3 (the console-safe prefix) gate only the optional Phase 3 and can stay open indefinitely. Resolve D2/D4/D5, then run Phase 1-2. Appendix D is the keybinding pruning checklist. Add a =#+TODO: TODO | DONE SUPERSEDED CANCELLED= header line to the spec if adopting those decision keywords (rulesets convention update, 2026-06-12). - -** TODO [#B] ledger-config is orphaned — ledger-mode never configured :bug:quick: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-21 -:END: -Nothing requires =modules/ledger-config.el= (verified by grep), so .dat/.ledger/.journal open without ledger-mode, reports, or flycheck-ledger. The module looks finished, not staged (unlike duet-config, which documents its pre-alpha orphaning). Decide: wire into init.el (+ =cj/executable-find-or-warn= for the ledger binary) or delete. From the 2026-06 config audit. - ** TODO [#C] buffer-differs save prompt: 4-way yes/no/diff/cancel :feature:next: :PROPERTIES: :LAST_REVIEWED: 2026-06-21 @@ -3709,6 +3740,12 @@ 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=. +** 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. +** 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. ** TODO [#D] Dashboard over-scroll: pin last line to window bottom :bug: :PROPERTIES: :LAST_REVIEWED: 2026-05-22 |
