* Emacs Config Open Work ** TODO [#A] Add Transcription Org-capture Workflow :PROPERTIES: :CATEGORY: emacs :END: *** Goal Integrate audio-to-text transcription into Org-capture so meeting recordings can be turned into timestamped notes automatically. *** Steps **** 1. Create CLI for transcription - Option A — OpenAI API (Whisper): #+begin_src bash # ~/bin/oai-transcribe #!/usr/bin/env bash set -euo pipefail AUDIO="$1"; LANG="${2:-en}" : "${OPENAI_API_KEY:?Set OPENAI_API_KEY in your environment}" curl -s -X POST "https://api.openai.com/v1/audio/transcriptions" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -F "model=whisper-1" \ -F "response_format=text" \ -F "language=${LANG}" \ -F "file=@${AUDIO}" #+end_src - Option B — Local Whisper: #+begin_src bash pip install openai-whisper # once # ~/bin/local-whisper #!/usr/bin/env bash set -euo pipefail AUDIO="$1"; MODEL="${2:-medium}"; LANG="${3:-en}" whisper "$AUDIO" --model "$MODEL" --language "$LANG" \ --task transcribe --output_format txt --verbose False >/dev/null cat "${AUDIO%.*}.txt" #+end_src **** 2. Emacs Lisp configuration Add to init.el: #+begin_src emacs-lisp (defcustom cj/transcribe-program (or (executable-find "oai-transcribe") (executable-find "local-whisper")) "Program used to transcribe audio to plain text.") (defcustom cj/meetings-org-dir "~/org/meetings/" "") (defun cj/transcribe-audio (file) (with-temp-buffer (call-process shell-file-name nil t nil shell-command-switch (format "%s %s" (shell-quote-argument cj/transcribe-program) (shell-quote-argument (expand-file-name file)))) (buffer-string))) (defun cj/org-capture-meeting-template () (let* ((audio (read-file-name "Audio: ")) (participants (read-string "Participants: " "Mark; Craig")) (location (read-string "Location: " "French Truck Coffee")) (topic (read-string "Topic: " "Contract discussion")) (transcript (cj/transcribe-audio audio))) (format "* Meeting: %s\n:PROPERTIES:\n:DATE: %s\n:LOCATION: %s\n:PARTICIPANTS: %s\n:AUDIO: %s\n:END:\n\n** Summary\n- \n\n** Transcript\n#+begin_quote\n%s\n#+end_quote\n" topic (format-time-string "%Y-%m-%d %H:%M") location participants (abbreviate-file-name audio) transcript))) (with-eval-after-load 'org (add-to-list 'org-capture-templates `("m" "Meeting (transcribe audio)" plain (file ,(expand-file-name (format-time-string "%Y-meetings.org") cj/meetings-org-dir)) "%(cj/org-capture-meeting-template)" :empty-lines 1 :unnarrowed t))) #+end_src **** 3. Usage 1. Record meeting as .m4a or .wav 2. Run `M-x org-capture m` → select the audio file 3. Org inserts transcript and metadata under `~/org/meetings/YYYY-meetings.org` **** 4. Optional quick recap template #+begin_src emacs-lisp (with-eval-after-load 'org (add-to-list 'org-capture-templates '("r" "Post-meeting recap" entry (file+headline (lambda () (expand-file-name (format-time-string "%Y-meetings.org") cj/meetings-org-dir)) "Recaps") "* %U %^{Title|Meeting w/ Mark (Carrollton)}\n:LOCATION: %^{Location|French Truck (Dryades)}\n** Facts\n- %?\n** Next Steps\n- "))) #+end_src **** 5. Notes - Keep filenames descriptive: `2025-11-03_mark-frenchtruck.m4a` - For privacy: use local-whisper variant to keep audio offline. ** TODO [#A] Finish Testing New Org-webclipper ** TODO [#A] Document Goals, Specs, and Architecture Docs for this Config :maintenance: *** Emacs Config V2MOM Misc Requirements - Keybindings easy to change - Keybindings have good mnemonics **** Vision Your personal ideal Emacs configuration allows you to work at the speed of your own thought. It's stable and never needs edge-case bug fixing. The functionality covers your workflow such that you can work all day in emacs for any of the following task areas: - programming in any language, including emacs-lisp, common-lisp, scheme, golang, and c - reading ebooks, including marginalia handling - listening to music, including playlist management - listening to podcasts, including saving episodes - task organizing and planning - calendaring, including syncing with google calendar - alerting and notifications on tasks and calendar events - timer/stopwatch functionality, including pomodoro presets - audio and desktop recording - memorization drills - email, including email-to-task integration - messaging, i.e., IRC - file-management, including extensive integration,, and DWM scratchpad integration outside Emacs - shell commands, leveraging vterm and eshell with custom methods, and DWM scratchpad integration outside Emacs Anytime you make a change in the config, you have unit tests to tell you quickly if the change created unexpected issues. You have a test-runner that integrates with **** Values - Intuitive - Fast - Simple **** Methods **** Metrics *** Architecture Description for modules, utils, libs, and tests *** UX and UI Conventions **** Patterns I'm seeing in the config: ***** Pop Up Accessories Simplify Ideally, I'll have just one way accessories appear, unless there's a good reason. So what's my criteria for success: - allows C-x 1 to make it the only window. - can tab between this and the - can resize - can toggle orientation. I'm going to ****** Bottom Up Pattern Loads from the bottom and is dynamically adjusted. If I hit q, the window disappears. Org-Agenda ****** Side Out Pattern These pop up in a side window. Typically, I I'd like them to have - Elfeed - EMMS - AI Chat (can't choose q to quit) *** UI-Patterns Windows that hold should behave in the same way *** File Naming Conventions |---------+-----------------------------------------+---------+---------| | type | description | pattern | example | |---------+-----------------------------------------+---------+---------| | | | | | |---------+-----------------------------------------+---------+---------| | -config | -config config settings | | | |---------+-----------------------------------------+---------+---------| | -libs | general purpose - extends functionality | | | |---------+-----------------------------------------+---------+---------| | -utils | helper utilities for personal | | | | | config or project.Not general purpose. | | | |---------+-----------------------------------------+---------+---------| | test- | test suite named after method | | | ** TODO [#A] Org Reveal.js Presentations From Local Org :enhancement: *** References https://revealjs.com/ https://github.com/hakimel/reveal.js *** Steps from Zamansky's Videos https://www.youtube.com/watch?v=psDpCpcIVYs&t=10s **** Install Org Reveal ** TODO [#B] Implement additional custom operations After analyzing all custom-* modules, the following line/paragraph operations are missing and would be useful additions to custom-line-paragraph.el: *** Most Useful Operations (High Priority) - [ ] Copy current line - Duplicate line to kill ring without cutting (like yy in Vim) - [ ] Sort lines - Sort lines alphabetically, numerically, or by custom key - [ ] Insert blank line above - Add empty line above current line without moving cursor - [ ] Insert blank line below - Add empty line below current line without moving cursor *** Additional Operations (Medium Priority) - [ ] Reverse lines - Reverse the order of lines in region or buffer - [ ] Shuffle lines - Randomly reorder lines in region or buffer - [ ] Uniquify lines - Remove duplicate lines while preserving order (different from existing remove-duplicates which uses regex) - [ ] Filter lines matching regex - Keep only lines matching a pattern (inverse of remove-lines-containing) - [ ] Number/enumerate lines - Add line numbers or enumeration to lines in region - [ ] Join with separator - Join lines with custom separator (e.g., comma, semicolon) *** Lower Priority Operations - [ ] Kill whole line - Delete entire line including newline (like dd in Vim) - [ ] Wrap/unwrap at column - Hard wrap at fill-column or unwrap wrapped lines - [ ] Indent/dedent region - Increase or decrease indentation for region Note: Some operations like fill-paragraph, transpose-lines, and basic indentation already exist in Emacs core. This list focuses on commonly-needed operations not yet implemented in custom modules. ** TODO [#B] investigate unassociated files - [ ] /home/cjennings/.emacs.d/assets/test/test-misspellings.org - [ ] /home/cjennings/.emacs.d/assets/test/test.org - [ ] /home/cjennings/.emacs.d/custom/c-boxes.el - [ ] /home/cjennings/.emacs.d/custom/edit-indirect.el - [ ] /home/cjennings/.emacs.d/custom/eplot.el - [ ] /home/cjennings/.emacs.d/custom/gptel-prompts.el - [ ] /home/cjennings/.emacs.d/custom/org-checklist.el - [ ] /home/cjennings/.emacs.d/custom/pdf-continuous-scroll-mode.el - [ ] /home/cjennings/.emacs.d/custom/pdf-continuous-scroll-mode-latest.el - [ ] /home/cjennings/.emacs.d/custom/profile-dotemacs.el - [ ] /home/cjennings/.emacs.d/custom/titlecase.el - [ ] /home/cjennings/.emacs.d/custom/utilities/vcf-conversion-helpers.el - [ ] /home/cjennings/.emacs.d/gptel-tools/update_text_file.el - [ ] /home/cjennings/.emacs.d/modules/ai-conversations.el - [ ] /home/cjennings/.emacs.d/modules/erc-config.el - [ ] /home/cjennings/.emacs.d/modules/jumper.el - [ ] /home/cjennings/.emacs.d/modules/ledger-config.el - [ ] /home/cjennings/.emacs.d/modules/lipsum-generator.el - [ ] /home/cjennings/.emacs.d/modules/lorem-generator.el - [ ] /home/cjennings/.emacs.d/modules/mu4e-org-contacts-integration.el - [ ] /home/cjennings/.emacs.d/modules/mu4e-org-contacts-setup.el - [ ] /home/cjennings/.emacs.d/modules/org-noter-config.el - [ ] /home/cjennings/.emacs.d/modules/prog-lsp.el - [ ] /home/cjennings/.emacs.d/modules/wip.el - [ ] /home/cjennings/.emacs.d/themes/dupre-theme.el - [ ] /home/cjennings/.emacs.d/todo.org ** TODO [#B] Replace customization constructions in modules :bug: Files using defcustom or other customization-related constructions: - media-utils.el - external-open.el - dashboard-config.el - lipsum-generator.el - ui-config.el - ai-conversations.el - video-audio-recording.el - jumper.el - local-repository.el - lorem-generator.el - eww-config.el ** TODO [#B] Test Failures Fixed *** TODO [#A] Add All ERT Tests Into Separate Directory :bug: **** List of ERT tests that need consolidation: Looks like there are only two files that have tests left: modules/undead-buffers.el 73 (ert-deftest undead-buffers/kill-or-bury-when-not-in-list-kills () 85 (ert-deftest undead-buffers/kill-or-bury-when-in-list-buries () 107 (ert-deftest undead-buffers/kill-or-bury-adds-to-list-with-prefix () 119 (ert-deftest undead-buffers/kill-buffer-and-window-removes-window () 133 (ert-deftest undead-buffers/kill-other-window-deletes-that-window () 150 (ert-deftest undead-buffers/kill-all-other-buffers-and-windows-keeps-only-current () themes/dupre-theme.el 137 (ert-deftest dupre-theme-default-face () 146 (ert-deftest dupre-theme-comment-face-italic () **** 2025-09-13 Sat @ 12:18:26 -0500 Gave this task to Codex Move all ERT tests out of individual files in the modules directories, and move them all to files in the test directories. The files should be named after the functions they test. Move existing ERT tests in the test directories to these files as well, removing those original test files. Provide another elisp file that will read all ERT files, and offer to run all the ERT tests in that file, or all ERT tests from all the files. **** Goals Tests should be added to a separate directory rather than the end of each elisp file. Gather them together, move them there, and make it easy to run tests per module and workflow. *** TODO [#B] Fix all failing test files ~/.emacs.d/tests/*.disabled :maintenance: *** TODO [#C] Write missing tests for each module :bug: ** TODO [#B] AI-Config Fixes and Enhancements *** DOING [#A] Finish Creating and Refactoring GPTel Tools and Tests :maintenance: **** TODO Refactor setup/teardown in all library test files Use the methods in test-tool-library-general.el - [ ] tests/test-tool-library-fs--mode-to-permissions.el - [ ] tests/test-tool-library-fs-directory-entries.el - [ ] tests/test-tool-library-fs-filter-by-extension.el - [ ] tests/test-tool-library-fs-format-file-info.el - [ ] tests/test-tool-library-fs-get-file-info.el - [ ] tests/test-tool-library-fs-list-directory-recursive-extra.el - [ ] tests/test-tool-library-fs-list-directory-recursive.el - [ ] tests/test-tool-library-fs-validate-path.el ***** 2025-09-28 Sun @ 01:33:56 -0500 Issue, Goals, and Discussion ****** cjennings When I ran the snippet you provided, I received a backtrace indicating there was no directory of that name. So most likely the test teardown function removed it. ```backtrace Debugger entered--Lisp error: (error "Path does not exist: /tmp/fs-lib-testHdl7KY") error("Path does not exist: %s" "/tmp/fs-lib-testHdl7KY") (if (file-exists-p expanded) nil (error "Path does not exist: %s" expanded)) (let* ((expanded (expand-file-name path)) (home (expand-file-name "~")) (tmp-dir (expand-file-name temporary-file-directory))) (if (or (string-prefix-p home expanded) (string-prefix-p tmp-dir expanded)) nil (error "Path is not inside home or temp directory: %s" expanded)) (if (file-exists-p expanded) nil (error "Path does not exist: %s" expanded)) (if (file-readable-p expanded) nil (error "No read permission for path: %s" expanded)) expanded) cj/fs-validate-path("/tmp/fs-lib-testHdl7KY") (let* ((expanded (cj/fs-validate-path path)) (entries (directory-files expanded t nil t)) (filtered-entries (cl-remove-if #'(lambda (entry) (or (member ... ...) (and ... ...))) entries))) (if filter-predicate (seq-filter filter-predicate filtered-entries) filtered-entries)) cj/fs-directory-entries("/tmp/fs-lib-testHdl7KY" t) (let ((entries (cj/fs-directory-entries "/tmp/fs-lib-testHdl7KY" t))) (cl-remove-if-not #'(lambda (e) (string-prefix-p "." (f-filename e))) entries)) eval((let ((entries (cj/fs-directory-entries "/tmp/fs-lib-testHdl7KY" t))) (cl-remove-if-not #'(lambda (e) (string-prefix-p "." (f-filename e))) entries)) nil) elisp--eval-last-sexp(nil) #f(compiled-function () #)() eval-last-sexp(nil) funcall-interactively(eval-last-sexp nil) command-execute(eval-last-sexp) ``` Weirdly, when I ran the tests in test-tool-library-fs-directory-entries.el a second time, they all passed. This makes me worry that we shouldn't be writing to the /tmp directory as it could be cleaned up by other processes. Do you have any hypotheses for this intermittent failure? ****** AI: gpt-4.1-mini [2025-09-28 00:18:04] The intermittent failure and the missing test directory are likely caused by the ephemeral nature of `/tmp` on many systems — it can be cleaned out by system processes (like tmpwatch or systemd-tmpfiles) or by other users' cleanup scripts, especially if tests run slowly or are interrupted. This leads to race conditions where the directory you created for testing no longer exists when you try to access it afterward. Here are some hypotheses and recommendations: 1. **Ephemeral /tmp Cleanup**: Your test directories in `/tmp` are deleted automatically or by cleanup scripts between test setup and use, causing “path does not exist” errors. 2. **Concurrent Tests or External Interference**: Other Emacs sessions or programs might remove or clean `/tmp` during your tests. 3. **Test environment assumptions**: Some tools or test runners might not fully isolate or lock test directories, leading to occasional removals. --- ### Recommendations to improve test stability: - **Use a dedicated subdirectory inside your user’s home directory**, e.g., `~/.emacs.d/gptel-test` or `~/.emacs.d/test-temp`, instead of `/tmp`. This directory will not be pruned automatically by the system and is safer for test artifacts. - Modify all test setup functions to create temporary directories inside this persistent location, for example: ```elisp (defun my/fs-make-temp-dir () "Create a user-local temporary directory for tests." (make-temp-file (expand-file-name "~/.emacs.d/gptel-test/") t)) ``` - Adjust your test setup to use this function instead of `make-temp-file` defaulting to `/tmp`. - Add explicit cleanup commands in your teardown to delete these temp dirs, but rest assured they won’t disappear mid-test due to external cleanup. - Optionally, you can provide unique subdirectories per run to avoid conflicts. --- ### Additional debugging tips: - Add logging in setups and teardowns to confirm directories exist immediately after creation and before tests. - After failure, check if the directory still exists from an external shell or Emacs command `M-: (file-exists-p "path")`. - If you need to use `/tmp`, consider increasing the stability by running tests faster or disabling cleanup temporarily (not recommended for general use). --- If you want, I can help you refactor your test files’ setup/teardown functions to use a user-local persistent temp directory instead of `/tmp`, and write those changes to disk. Would you like me to proceed with that? ****** cjennings Yes. What you're saying makes sense. Here's what I'd like to do, based on what you've said. 1. Create a test-tool-library-general.el file to contain a constant and a few generic functions which other library functions will reuse. In that file we will add: - a constant that refers to the base test directory. This is the directory in which all test assets will be created. - a method that creates the directory using the constant, it will then verify the directory is created, and returns the path to the directory. - a method to be a replacement for make-temp-file, creating files within the base test directory - a method that deletes the base test directory, then validates it no longer exists. This will be the bulk of the teardown methods in other files. 2. Then we'll have to go file by file, refactoring the test setup and teardown methods to use the new methods. 3. As we finish refactoring a test file, we'll run it to ensure it passes. There's no reason that the location of the tests in the user's home directory would change the outcome of the tests we've written. As long as our code is correct, we can expect our tests to pass. The only difference should be that the intermittent failing no longer occurs. 4. Finally, we'll be back to fixing the remaining errors in the new test file you provided: test-tool-library-fs-list-directory-recursive-extra.el Any additional thought or suggestions? Is there anything else that should go into test-library-general.el that may be common to other tests? Any other best practice ideas? ****** AI: gpt-4.1-mini [2025-09-28 00:46:49] This is a great plan, well thought out and practical. ### Few additional thoughts and suggestions: 1. **Additional common utilities in test-tool-library-general.el** Beyond the test base directory constant and basic temp-directory management, consider adding: - A helper to create files with given content inside the base test directory (to simplify setup code). - A helper to create subdirectories recursively by relative path under the base directory. - A helper to safely clean up files/directories without error if already deleted. - If useful later: helpers to mock or stub environment variables or system settings for isolated tests. - Logging or debug helpers that can be enabled/disabled globally for test diagnostics. 2. **Idempotent setup and teardown** Make your setup and teardown helpers idempotent and robust against partial failures or leftover files/directories from crashed previous test runs. 3. **Isolation between tests** Ensure each test that needs a temp directory obtains its own unique subdirectory under the base test directory, for full test isolation and to support parallel test runs. 4. **Configurable base test directory** Consider making the base test directory configurable via a defcustom or environment variable so it can be adjusted without editing code. 5. **Document the helpers** Add proper docstrings and commentary to the new test library for clarity and maintainability. --- ### Implementation plan summary for test-tool-library-general.el - Define a constant: `cj/test-base-dir` (e.g., `~/.emacs.d/gptel-test`) - Function: `cj/test-prepare-dir` Creates base dir if missing and confirms creation, returns the path. - Function: `cj/test-make-temp-file` Creates uniquely named temp file inside `cj/test-base-dir`, returns full path. - Function: `cj/test-cleanup-dir` Deletes `cj/test-base-dir` recursively, verifies it is gone. --- ### Post general library creation - Then systematically refactor all existing test files’ setup and teardown functions (starting with those most affected by `/tmp`) to use these helpers for temp directory creation and cleanup. - Run tests after each refactor to confirm continued success. - Finally, return to addressing remaining test failures (e.g., the `test-tool-library-fs-list-directory-recursive-extra.el` file) with reduced risk of intermittent failures. --- If you confirm this, I will write the full `test-tool-library-general.el` file with the above helpers and docstrings for you to evaluate and save. Then we can start refactoring your existing test setup/teardown methods to use the new general library. Does this sound good? Anything else you'd want included in that general test library? ****** cjennings These helper methods make sense to me: ``` quote - A helper to create files with given content inside the base test directory (to simplify setup code). - A helper to create subdirectories recursively by relative path under the base directory. ``` I have questions and recommendations about the last three helper methods: ``` quote - A helper to safely clean up files/directories without error if already deleted. ``` I don't understand this, but might be missing something. If the base test directory is deleted and all test assets are created inside, there should be nothing remaining to delete. ```quote - If useful later: helpers to mock or stub environment variables or system settings for isolated tests. ``` This makes sense, but I suggest we write that only when we first need it. ```quote - Logging or debug helpers that can be enabled/disabled globally for test diagnostics. ``` Again, this is a good idea, and we'll probably do it. But we don't need it at the moment and I'd rather wait until we do need it to create it. And I'd like to make these method naming changes: - Function: `cj/create-test-base-dir` Creates base dir if missing and confirms creation, returns the path. - Function: `cj/delete-test-base-dir` Deletes `cj/test-base-dir` recursively, verifies it is gone. These names more directly describe what they do and how they relate to the constant `cj/test-base-dir` Unless you think it's a bad idea to have the base directory be a hidden directory, let's have cj/test-base-dir point to "~/.temp-gptel-tests/". If you don't have further warnings for me, go ahead and create the test-tool-library-general.el file as described above. **** 2025-09-30 Tue @ 17:26:00 -500 Renamed test-tool-library-general.el CLOSED: [2025-09-30 Tue 17:26] Why does this just have to do with test-tools? It's actually a generic test library. **** 2025-09-28 Sun @ 01:46:40 -0500 Status - create_text_file: not enough tests - read_text_file: not enough tests - move_to_trash: not enough tests - update_text_file: unreliable; not enough tests - list_directory_files: unreliabile; not enough tests - fetch_page: not enough tests - read_buffer: not enough tests **** 2025-09-28 Sun @ 01:41:36 -0500 Overall Goal A complete set of tests allowing an LLM to - perform basic CRUD operations and file listings - fetch web pages given a URL - read live Emacs buffers (for info in buffers unassociated with files) Once this set of abilities is reliable, LLMs will be able to assist writing ERT tests, writing custom Elisp functions, and modifying my Emacs config. *** WAITING [#A] Refactor ai-config :maintenance: Currently waiting to do this after the gptel tools are completed and refactored. Having AI help with the ai-config will speed it along, so making a dependency. **** 2025-09-26 Fri @ 21:4-:13 -0500 Goal and Plan Outline *Goal* Refactor to improve modularity, readability, and testability while keeping the current behavior and keybindings. --- ***** Refactoring Strategy ****** 1. Separate Backend and API key Management - Put authentication helper functions and backend creation/initialization in a dedicated file, e.g., =ai-backends.el=. - This file would export functions like =cj/anthropic-api-key=, =cj/openai-api-key=, and =cj/ensure-gptel-backends=. - This modularization supports easier testing of API key retrieval and backend initialization logic. Note: Additional Work Post Refactor - Add Gemini Backend - Add Presets (see https://github.com/karthink/gptel?tab=readme-ov-file#option-presets) ****** 2. Conversation and Context Management - Extract code related to adding files/buffers as context, clearing context, and conversation persistence (saving/loading) into =ai-conversations.el=. - Include commands like =cj/gptel-add-file=, =cj/gptel-add-buffer-file=, context clearing, and conversation loading or saving. Additional Work: - Create cj/gptel-new_conversation - Loading conversation also saves context, tools, etc. - Saving conversations also saves context, tools, etc. ****** 3. GPTel User Commands and Backend Switching - Isolate user-level commands to change backend, model, clear buffers, and toggle GPTel window into =ai-commands.el=. - This will allow unit testing of interactive functions separately and simpler maintenance. Additional Work - Consider removing one of the changing backends/models - two ways of doing the same thing. ****** 4. GPTel Configuration and Org-mode Enhancement - Have a file, e.g., =ai-gptel-config.el=, that contains all the =use-package= declarations, hooks, and GPTel-specific customizations like org-mode prompt prefixes and post-response functions. - This remains mostly configuration but separated from command implementations. ****** 5. Keybindings and UI - Place the custom AI keymap and global keybindings into =ai-keymap.el=. - This file imports the commands from =ai-commands.el= and binds them. ****** 6. Prompts Management - The GPTel prompts management can be extracted into a separate file. Additional Work - Make Prompts Management more stable --- ***** Ensuring Testability - Each module should export pure or mostly pure functions when possible. - Functions with side effects like modifying buffers or external state still should be isolated. - Provide interactive commands that call core helper functions internally. - Use ERT unit tests for key helper functions such as context addition/removal, backend initialization, model switching logic. - Use mock authentication backends or mock auth-source for API key tests. --- ***** Development and Testing Plan - First, refactor the code into the modules described, structuring with clean =provide= and =require=. - Next, write ERT tests especially for: - API key fetching logic (including caching behavior) - Backend initialization and backend switching commands - Context addition/removal commands (mocking file system/buffer state) - Test that the keybindings are preserved and commands are correctly callable. - Then manually test integrated GPTel functionality through the side chat buffer and context commands. - Keep the public user interface stable while refactoring. --- **** 2025-09-26 Fri @ 21:44:20 -0500 Current Plan for Step One Step 1: Extract API Key Helpers and Backend Initialization ***** Goal Pull out the API key fetching, caching, and GPTel backend creation logic from ai-config.el into a new separate file, for example =ai-backends.el=. ***** Responsibilities for =ai-backends.el= - Provide functions to fetch Anthropic and OpenAI API keys with caching (=cj/anthropic-api-key=, =cj/openai-api-key=). - Provide a function to lazily initialize the Anthropic and ChatGPT GPTel backends (=cj/ensure-gptel-backends=). - Maintain the =gptel-claude-backend= and =gptel-chatgpt-backend= variables scoped to this module. - Provide a common entrypoint to initialize and get available backends. ***** Integration - Replace the relevant parts in =ai-config.el= with calls to the new module. - Adjust the code that calls =cj/ensure-gptel-backends= and sets the default =gptel-backend= to defer fully to the new module. - Ensure that =cj/toggle-gptel= and other commands rely on this properly. ***** Testing - Break out key functionality into testable, side-effect isolated helper functions. - Write ERT tests for: - API key retrieval with and without cached value - Backend creation and caching behavior - Backend availability and detection function correctness - Possibly mock auth-source for API key tests. ***** Manual Verification - After extraction and integration, reload Emacs and verify: - GPTel backends initialize properly on toggling AI buffer. - No regressions in model switching or backend switching commands. - Cached API keys are respected. --- ** TODO [#B] Decent HTML Exports From Org Mode With Inline CSS :enhancement: *** fniessen/org-html-themes: Org mode files transformed into stunning HTML documents [[https://github.com/fniessen/org-html-themes?tab=readme-ov-file#using-a-theme][fniessen/org-html-themes: Transform your Org mode files into stunning HTML documents in minutes with our Org mode HTML theme. Elevate your productivity and impress your readers! #orgmode #html #theme #productivity #design]] Captured On: [2025-08-18 Mon 14:36] ** TODO [#B] Dirvish/File Manager Fixes/Enhancements :maintenance: *** DOING [#B] Test all dwim-shell-commands :enhancement: This way you can also have test assets **** Test Status for Commands - [X] convert-audio-to-mp3 () - [X] convert-audio-to-opus () - [ ] view-image-exif-metadata () - [ ] flip-image-horizontally () - [ ] flip-image-vertically () - [ ] convert-image-to () - [ ] convert-svg-to-png () - [ ] join-images-into-pdf () - [ ] extract-pdf-page-number () - [ ] ocr-text-from-image-using-tesseract () - [ ] convert-video-to-webp () - [ ] convert-video-to-high-compatibility-mp4 () - [ ] convert-video-to-hevc-mkv () - [ ] extract-archive-smartly () - [ ] zip-file-or-directory () - [ ] tar-gzip-file-or-directory () - [ ] epub-to-org () - [ ] document-to-pdf () - [ ] pdf-to-txt () - [ ] resize-image-by-factor () - [ ] resize-image-in-pixels () - [ ] pdf-password-protect () - [ ] pdf-password-unprotect () - [ ] video-trim () - [ ] drop-audio-from-video () - [ ] open-externally () - [ ] git-clone-clipboard-url () - [X] open-file-manager () - [ ] count-words-lines () - [ ] checksum () - [ ] backup-with-timestamp () - [ ] optimize-image-for-web () - [ ] csv-to-json () - [ ] json-to-yaml () - [ ] extract-urls-from-file () - [ ] extract-emails-from-file () - [ ] create-gif-from-video () - [ ] concatenate-videos () - [ ] create-video-thumbnail () - [ ] merge-pdfs () - [ ] split-pdf-by-pages () - [ ] compress-pdf () - [ ] ascii-art () - [ ] text-to-speech () - [ ] remove-empty-directories () - [ ] create-thumbnail-from-image () - [ ] extract-audio-from-video () - [ ] normalize-audio-volume () - [ ] remove-zip-encryption () - [ ] create-encrypted-zip () - [ ] list-archive-contents () - [ ] count-words-lines-in-text-file () - [ ] make-executable () - [ ] secure-delete () - [ ] sanitize-filename () - [ ] number-files-sequentially () - [ ] git-history () - [ ] encrypt-with-gpg () - [ ] decrypt-with-gpg () - [ ] kill-gpg-agent () **** Reference https://github.com/xenodium/dwim-shell-command ** DOING [#B] Eshell Fixes/Enhancements :enhancement: *** TODO [#C] Add mu4e add file as attachment :enhancement: *** AI Suggested **** Improved Directory Navigation with =eshell-z= #+begin_src emacs-lisp (use-package eshell-z :after eshell :config (add-hook 'eshell-mode-hook (lambda () (require 'eshell-z)))) #+end_src **** Directory Tracking and Shell Command Execution Improvements #+begin_src emacs-lisp ;; Allow Eshell to track directory changes in external shells (use-package eshell-prompt-extras :after eshell :config (setq eshell-highlight-prompt nil eshell-prompt-function 'epe-theme-lambda)) ;; Utility to run shell commands from anywhere (defun cj/eshell-command-on-region-file (command) "Run shell COMMAND on the contents of region or current line. If region is active, use that, otherwise use the current line." (interactive (list (read-shell-command "Shell command on region: "))) (let* ((text (if (use-region-p) (buffer-substring-no-properties (region-beginning) (region-end)) (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (temp-file (make-temp-file "eshell-cmd-")) (output-buffer (generate-new-buffer "*Shell Command Output*"))) (with-temp-file temp-file (insert text)) (with-current-buffer output-buffer (insert (shell-command-to-string (format "%s %s" command temp-file))) (pop-to-buffer output-buffer)) (delete-file temp-file))) (global-set-key (kbd "C-c !") 'cj/eshell-command-on-region-file) #+end_src **** Enhanced Visual Output and ANSI Colors #+begin_src emacs-lisp ;; Better handling of visual commands (defun cj/eshell-exec-visual-and-kill () "Execute the command via `eshell-exec-visual' and then kill the eshell buffer." (interactive) (let ((command (buffer-substring-no-properties eshell-last-output-end (point-max)))) (kill-buffer) (eshell-exec-visual command))) ;; Add this to your eshell aliases (add-hook 'eshell-mode-hook (lambda () (eshell/alias "vk" 'cj/eshell-exec-visual-and-kill))) #+end_src **** Buffer Management and Layout Improvements #+begin_src emacs-lisp ;; Auto rename eshell buffers based on current directory (defun cj/eshell-rename-buffer-as-dir () "Rename the eshell buffer to include the current directory." (let ((dir (abbreviate-file-name (eshell/pwd)))) (rename-buffer (format "*eshell: %s*" dir) t))) (add-hook 'eshell-directory-change-hook 'cj/eshell-rename-buffer-as-dir) ;; Split eshell in a sensible way based on window size (defun cj/eshell-pop-window () "Open eshell in a sensible way depending on window dimensions." (interactive) (let ((width-percentage 0.3) (height-percentage 0.3) (current-window-width (window-width)) (current-window-height (window-height))) (if (> current-window-width (* 2 current-window-height)) ;; For wide windows, split side by side (let ((width (floor (* current-window-width width-percentage)))) (split-window-horizontally (- current-window-width width)) (other-window 1) (eshell)) ;; For tall windows, split top/bottom (let ((height (floor (* current-window-height height-percentage)))) (split-window-vertically (- current-window-height height)) (other-window 1) (eshell))))) (global-set-key (kbd "C-c e") 'cj/eshell-pop-window) #+end_src **** Useful Utility Functions #+begin_src emacs-lisp ;; Enhanced eshell clear that preserves history (defun cj/eshell-clear-buffer () "Clear the eshell buffer, preserving the prompt and history." (interactive) (let ((inhibit-read-only t)) (erase-buffer) (eshell-send-input))) (add-hook 'eshell-mode-hook (lambda () (local-set-key (kbd "C-l") 'cj/eshell-clear-buffer))) ;; Function to insert the output of elisp expressions into eshell (defun cj/eshell-insert-elisp-output (elisp) "Evaluate ELISP expression and insert its value at point in eshell." (interactive "sEval Elisp: ") (let ((result (eval (read elisp)))) (insert (if (stringp result) result (format "%S" result))))) (add-hook 'eshell-mode-hook (lambda () (local-set-key (kbd "C-c C-e") 'cj/eshell-insert-elisp-output))) ;; Quick file manager in eshell (defun eshell/fman (file) "Open FILE in a file manager." (cond ((eq system-type 'darwin) (shell-command (format "open %s" (expand-file-name default-directory)))) ((eq system-type 'gnu/linux) (shell-command (format "xdg-open %s" (expand-file-name default-directory)))))) ;; Enhanced cd command that respects projects (defun eshell/pcd () "Change directory to the project root." (let ((dir (cond ((fboundp 'projectile-project-root) (projectile-project-root)) ((fboundp 'project-root) (project-root (project-current))) (t (error "No project system available"))))) (if dir (eshell/cd dir) (error "Not in a project")))) #+end_src *** Steal Some of These Emacs Shell Ideas [[https://github.com/howardabrams/hamacs/blob/main/ha-eshell.org#ebb-and-flow-output-to-emacs-buffers][hamacs/ha-eshell.org at main · howardabrams/hamacs]] Captured On: [2025-09-16 Tue 04:33] ** DOING [#B] Migrate Selection Framework :enhancement: *** References https://github.com/minad/consult/wiki/Auxiliary-packages https://www.reddit.com/r/emacs/comments/1aguy8g/how_to_get_consultripgrep_to_remember_previous/ *** Auxiliary Packages to Explore https://gitlab.com/OlMon/consult-flyspell https://github.com/ghosty141/consult-git-log-grep https://github.com/minad/affe *** Manual Testing Here's a comprehensive test checklist we should verify after migration: **** Core Functionality Tests - [ ] =M-x= (execute-extended-command) with rich annotations - [ ] =C-x C-f= (find-file) with file preview - [ ] =C-x b= (switch-to-buffer) with buffer annotations - [ ] =C-s= (isearch) replacement with consult-line - [ ] =C-h f= (helpful-callable) with completing-read - [ ] =C-h v= (helpful-variable) with completing-read **** Project/File Navigation Tests - [ ] =C-c p f= (projectile-find-file) - [ ] =C-c p p= (projectile-switch-project) - [ ] =C-c p G= (projectile-ripgrep) - [ ] Recent files access - [ ] Bookmarks **** Window/Buffer Management Tests - [ ] =M-V= (cj/split-and-follow-right) - buffer selection in new window - [ ] =M-H= (cj/split-and-follow-below) - buffer selection in new window - [ ] Kill-ring browsing - [ ] Imenu navigation **** Integration Tests - [ ] =C-c s i= (ivy-yasnippet → consult-yasnippet) - [ ] Git timemachine revision selection - [ ] Helpful integration (counsel-describe-/ functions) - [ ] Projectile integration **** Search/Replace Tests - [ ] In-buffer search - [ ] Project-wide search - [ ] Search with preview - [ ] Occur-like functionality *** Mu4e - Save All Attachments Testing It's pretty easy if you use vertico + embark. After running mu4e-view-save-attachments, - invoke embark-act-all in the completion menu - followed by RET (mu4e-view-save-attachments) to save all attachments, - or TAB (vertico-insert) followed by (comma) next to each file you want to save, - then RET (vertico-exit), to save selected attachments. ** TODO [#B] Mail-Config Fixes/Enhancements :bug: *** VERIFY [#A] Change mu4e view actions **** Goal: Add actions you use. the defaults are currently #+begin_src emacs-lisp (("capture message" . mu4e-action-capture-message) ("view in browser" . mu4e-action-view-in-browser) ("browse online archive" . mu4e-action-browse-list-archive) ("xview in xwidget" . mu4e-action-view-in-xwidget) ("show this thread" . mu4e-action-show-thread) ("org-contact-add" . mu4e-action-add-org-contact)) ;; Original value was (("capture message" . mu4e-action-capture-message) ("view in browser" . mu4e-action-view-in-browser) ("browse online archive" . mu4e-action-browse-list-archive) ("xview in xwidget" . mu4e-action-view-in-xwidget) ("show this thread" . mu4e-action-show-thread)) #+end_src The only ones I use are: #+begin_src emacs-lisp ("view in browser" . mu4e-action-view-in-browser) ("capture message" . mu4e-action-capture-message) ("org-contact-add" . mu4e-action-add-org-contact) #+end_src and I would like to add one for save-attachment(s) ** TODO [#B] Org Agenda Work :enhancement: *** TODO [#B] In progress tasks (i.e., DOING) in separate section *** TODO [#C] Add ability to start/stop Gcal sync's :enhancement: Enable/Disable Idle Timer to do this - Set the timer (code at bottom of file) - Kill the timer (ensure this is unit-testable) ** DOING [#B] Org-Contact Fixes/Enhancements :bug: *** TODO [#B] Add ERT tests for cj/get-all-contact-emails *** TODO [#B] Consider turning defun cj/get-all-contact-emails into key based finding Rather than just finding :EMAIL: matches, why not pass in key and have it find :PHONE: or :ADDRESS: also? *** TODO [#B] Finding Contact should only match Org Header text *** TODO [#B] Viewing Contacts should just bring up main contacts org file, not table *** Manual Tests - [X] Complete addresses with org-contact in to, cc, bcc - [ ] Complete addresses with new org-contact in to, cc, bcc - [ ] Complete with forwards/replies in to, cc, bcc - [ ] Add contact from mail (with new native mu4e function) - [ ] Create contact via org-contacts capture template "C" - [ ] Insert a contact in any buffer via keymap C-c C i - [ ] Add a new contact from anywhere via keymap C-c C n - [ ] Look up a contact from anywhere via keymap C-c C f - [ ] View all contacts from anywhere via keymap C-c C v - [X] Verify birthdays showing on agenda - [ ] Verify birthdays showing on agenda after adding new contact - [ ] Link a contact to an org-roam node (cj/org-contacts-link-to-roam) *** TODO [#C] Finish Org-Contacts/Mail Modules :maintenance: ** DOING [#B] Org Roam Fixes/Enhancements :enhancement: *** TODO [#B] New Org-Roam type: Book Notes Part of the integration with org-noter ** TODO [#B] Describe and Implement Personal Workflow Project Actions What other actions do I do in a project? *** TODO [#B] Rename and save any file to project/assets directory use case: - I receive a load of files in google-drive/dropbox share or email attachments. - They all belong to a project - Projects are structured with an assets or data folder (decide which one first) - I select the file in dirvish and hit a key, - It brings up completing read for the projects - After choosing one, it moves selected files into the proper assets/data folder *** TODO [#B] Override archiving a task to a project archive use case: - C-x a archives to a central archive.org file. - If I'm in a projectile/project.el project it overrides the destination to an resolved.org file - if the resolved.org file doesn't exist, it creates it. - If not in a projectile/project.el project, it falls back to the archive.org file. ** TODO [#B] M-y and show-kill-ring don't load first launch ** TODO [#B] create unfill paragraph (paragraph to single line) #+BEGIN_QUOTE I like to have a keybind to do the opposite of fill-paragraph. It’s taken from Stefan Monnier. (defun unfill-paragraph (&optional region) "Takes a multi-line paragraph and turns it into a single line" (interactive (progn (barf-if-buffer-read-only) '(t))) (let ((fill-column (point-max)) (emacs-lisp-docstring-fill-column t)) (fill-paragraph nil region))) (define-key global-map "\M-Q" 'unfill-paragraph) #+END_QUOTE [[https://gitlab.com/jdm204/dotfiles/-/blob/master/config.org][config.org · master · jdm204 / dotfiles · GitLab]] Captured On: [2025-10-15 Wed 18:35] ** DOING [#B] Jumper Package :enhancement: *** Specification **** variable:custom: jumper-max-locations maximum number of locations to store: default 10 **** variable:internal: jumper--registers vector of used registers: (make-register jumper-max-locations nil) **** variable:internal: jumper--last-location-register register used to store the last location: ?z **** method:internal: jumper--point-matches-register #+begin_src emacs-lisp ;; First store a position in register 'a' (point-to-register ?a) ;; Later, check if current position matches what's in register 'a' (if (cj/point-matches-register ?a) (message "Current position matches register 'a'") (message "Current position differs from register 'a'")) (defun cj/point-matches-register (register) "Return t if current position matches position stored in REGISTER. REGISTER is a character representing the register to check against. Returns nil if the register doesn't exist or doesn't contain position information." (let ((reg-val (get-register register))) (when (and reg-val (markerp reg-val) (marker-buffer reg-val) (eq (current-buffer) (marker-buffer reg-val)) (= (point) (marker-position reg-val))) t))) #+end_src **** method: **** method: save-last-position saves location to register z: (point-to-register ?z) **** method: save-to-next-register gets next free register in the sequence calls (save-last-position) saves using (point-to-register REGISTER &optional ARG) *** 2025-09-02 Tue @ 14:06:03 -0500 Functionality Description Tentative Package Name: Jumper I typically use registers to jump between places, but I often forget the keybindings. Also, I sometimes overwrite registers by hitting the wrong key. Many Emacs users don't even know about registers. I've seen posts from software developers asking how to conveniently store and jump to specific locations in a large code base. To solve this I'd like to write a little elisp package that leverages Emacs vanilla register functionality to make jumping between locations using registers simple and transparent. The user won't have to think about registers or what character they've stored their location in at all. All keybindings will be based on keys from home row of the keyboard. Preliminaries: We will map the functionality to the keybinding prefix C-; j, but the prefix should be easy to configure. Let's imagine a set of characters in a sequence. They could be any characters, but for the sake of simplicity we'll use the numbers from 0 to 9, and a one more character used for the "last location" -- the character "z". What follows is a description of the workflow: **** Adding Locations keybinding: space (store destination) If the user is visiting a buffer and presses space, - the current location is stored in the next free register. if this is the first time they are storing a location, the first free register is 0. user feedback: a message is displayed in the echo area saying "location stored." If the user stays on the same location and presses prefix again, they should receive a message in the echo area saying the location is already stored, and no changes to the register is made. If the user moves to a new location and presses prefix again, the next free register is used to store the location, in this case it would be 1. They can then iterate and store additional locations up to the last character in the sequence up to the limit of 10 registers (0 through 9). If they try to store a register after all 10 registers are filled, they will receive a message ("sorry - all jump locations are filled!"). **** Jump-To A Location keybinding: j (jump to destination) NOTE: Whenever the user presses j, that location is automatically stored in the "last location" register z. ***** When only one location is stored, and the user presses j: If there is only one location stored, the user IS at the location, and they press j, they see an echo area message "you're already at the stored location." If there is only one location stored in the sequence, and the user is NOT at that location, their location is stored in register z, then the user is immediately to their destination location via (jump-to-register). user feedback: a message is displayed in the echo area saying "jumped to location." If the user presses J again, they are automatically taken back to the location in register z. and the same user feedback message is displayed. In other words, after the user stores one location and moves elsewhere, pressing j will jump back and forth between the two places. ***** When multiple locations are stored, and the user presses j: A completing read is displayed with all the locations between 0 and 9 along with register z (their last location) at the bottom. Each line contains the letter as well as the content that the register would normally display. In other words, it could just be the register list is displayed, but narrowed down to 0 - 9 and z. When the user chooses a location 0 -9, z from completing read: - The current location is stored in register z, replacing their last location - They are taken to the location via (jump-to-register). user feedback: a message is displayed in the echo area saying "jumped to location." **** Removing Locations keybinding: d A completing read is displayed with the first item (where the cursor is on) being "Cancel". The rest of the registers are displayed in descending order, i.e., from 9 to 0. Note: the z register is not displayed. Selecting a register from the list: - removes that item from the list, and removes the location stored in the register. - if the item is NOT the top one in the register, it reorders the rest of the sequence in other words, if 0 - 9 are all stored, and: the user removes item 7: - location 8 is restored in 7 - location 9 is stored in 8 the user removes item 0 - location 1 is stored in 0 - location 2 is stored in 1 - location 3 is stored in 2 - location 4 is stored in 3 - location 5 is stored in 4 - location 6 is stored in 5 - location 7 is stored in 6 - location 8 is stored in 7 - location 9 is stored in 8 user feedback: a message is displayed in the echo area saying "location removed." **** Open Questions - Are there buffers which the user should not be able to store in a register? - How can we prevent the user from creating issues by adding to the registers 0 through 9 by M-x point-to-register or it's keybinding? Is there a way to block or reserve those registers? Or is it better to just choose a sequence that is the least likely to be used by a human user? If so, what would that sequence be? - Is 10 registers enough for a normal developer? - I should probably - Can I use a vector/list internally and map to registers behind the scenes? Depending on context, can I add the - Function name at point - File name + line number - First few words of the line Do we think the reordering behavior when deleting locations might confuse users? How to simplify? What happens if a buffer is deleted that doesn't have a file associated with it? If we're using registers underneath, how do registers handle this? ** TODO [#B] Get Tufte.css working and as a separate entry Below is one way to get Org-mode’s HTML exporter to play nicely with Tufte-CSS. The basic recipe is: 1. Inject Tufte’s stylesheet into every HTML export 2. Teach Org to emit the little “margin-toggle” + “sidenote” markup that tufte.css expects for footnotes 3. (Optionally) wrap images in
so you get tufte-style captions out of the box Along the way you’ll see where Org’s default HTML‐classes line up with tufte.css and where you have to override them. — 1 Inject tufte.css into your exports Put tufte.css somewhere your exported HTML can see it (for example ~/.emacs.d/assets/tufte.css or a URL on your webserver). Then in your init.el: (with-eval-after-load 'ox-html ;; 1a) tell Org to link in your tufte.css (setq org-html-head-extra "") ;; 1b) enable HTML5 “fancy” output (so you get
around images) (setq org-html-html5-fancy t org-html-inline-images t)) — 2 Turn Org footnotes into Tufte sidenotes By default Org emits [1] …and then a big =
= at the bottom. Tufte-CSS wants each footnote inline, wrapped in …your note… We can override two Org variables: (with-eval-after-load 'ox-html ;; format of each inline footnote reference (setq org-html-footnote-format (concat "" "" "%2$s")) ;; drop Org’s default footnote list at the end (setq org-html-footnote-separator "")) Once you do that, exporting an Org file with footnotes will generate the markup tufte.css needs to float them in the margin. — 3 (Optionally) get
+
around images If you set =org-html-html5-fancy= to t (see step 1) Org will automatically emit:
Your caption
and tufte.css already has rules for =
= etc. — 4 Common pitfalls • Make sure your href in =org-html-head-extra= actually points to the css that the browser can load (absolute vs. relative). • If you still see a “Footnotes” section at the bottom, double-check that =org-html-footnote-separator= is set to the empty string and that your init-file got re-evaluated. • On Windows or if you’re testing locally, run e.g. =python3 -m http.server= inside your export folder so your browser can fetch the CSS. — 5 Unit test for your footnote hack Drop this in =~/.emacs.d/tests/test-org-tufte.el= and run =M-x ert RET t RET=: (require 'ert) ;; load your config; adjust the path if necessary (load-file "~/.emacs.d/init.el") (ert-deftest org-tufte-footnote-format-test () "Ensure each footnote reference becomes a margin-toggle + sidenote." (let/ ((id "fn:42") (content "My note.") (html (format org-html-footnote-format id content))) (should (string-match-p "class=\"margin-toggle\"" html)) (should (string-match-p "My note\\." html)) ;; it must not accidentally reintroduce Org’s bottom-of-page footnote div (should-not (string-match-p "div id=\"footnotes\"" html)))) Once that test passes, you know your footnotes are being rewritten into Tufte-style side notes. From there, you can sprinkle in additional filters (e.g. wrap =
= in a =.sidenote= class, override list/p table styles, etc.) or just let the rest of tufte.css style Org’s default tags (h1, p, ul, table, code, etc.). Enjoy your beautifully-typeset Org → HTML exports in true Tufte style! ** TODO [#B] Get Dupre Theme Working :bug: *** AI Suggestioms Here are some ideas for making dupre-theme a bit more “complete” and future-proof as an Emacs theme. You don’t have to do all of them, of course, but most “modern” themes ship a fair number of these extra faces and integrations. **** TODO [#A] Fill out the “standard” Emacs faces • error / warning / success • link / link-visited / shadow • highlight / secondary-selection • tooltip / tooltip-selection / tooltip-tip • underline / strike-through • match / search / lazy-highlight (you already have some isearch) • info-title-1…info-title-5, info-header-node, help-*-face **** TODO [#A] Cosmetic / code-style notes • Factor out a helper macro to reduce repetition: #+begin_src elisp (defmacro dupre-face (face &rest specs) `(,face ((t ,specs)))) #+end_src • Use =custom-theme-set-faces= only once; wrap your =when (>= emacs-major-version…)= inside it rather than nesting top-level. • Move your ERT tests into a separate file like =dupre-theme-test.el= so that the main theme file stays lean. **** TODO [#B] Make sure you define a “minimum package” set of faces so that folks don’t see glaring unstyled text in any part of Emacs, even if they never install extra packages. **** TODO [#B] Improve terminal support: • Detect true-color (=(display-graphic-p)=) vs 256-color vs 16-color and fall-back gracefully. • Provide “256-color” approximations (you already do this), but you might want a simpler palette for legacy terminals. **** TODO [#B] Other Packages The rest is just more of the same—pick the most common modes/packages you and your users rely on and give them a quick coat of paint. Once you’ve rounded out those faces you’ll have hit what most people expect from a “complete” modern Emacs theme. Based on a quick skim of your modules/ directory (and the packages they pull in via use-package), here’s a non-exhaustive list of the major packages you’re using—along with the faces you should think about adding to dupre-theme so that nothing ends up looking “unstyled.” 1. completion/mini-frameworks • vertico (vertico-current, vertico-group-title, vertico-separator, vertico-doc) • orderless (orderless-match-face-0…3) • consult (consult-preview-line, consult-narrow, consult-region, consult-file) • marginalia (marginalia-annotation, marginalia-document, marginalia-cache) • embark (embark-general, embark-heading, embark-target, embark-helpful) • corfu + cape (corfu-default, corfu-current, corfu-bar, cape-file, cape-keyword etc.) • company (company-tooltip, company-tooltip-selection, company-scrollbar­-thumb) 2. ivy/counsel/swiper (if you ever load them side-by-side) • ivy-current-match, ivy-minibuffer-match-face-1…4 • counsel-outline, swiper-match-face-1… 3. syntax checkers & LSP • flycheck (flycheck-error, flycheck-warning, flycheck-info) • eglot or lsp-mode (lsp-face-highlight-textual, lsp-face-highlight-read, lsp-face-semantic-type-…) • tree-sitter (tree-sitter-hl-face:function, :variable, :property, …) 4. git & diffs • magit (magit-section, magit-section-title, magit-hunk-heading, magit-branch, magit-log) • diff-hl / git-gutter (diff-hl-face-insert, diff-hl-face-delete, git-gutter:added, git-gutter:deleted) • diff-mode (diff-added, diff-removed, diff-context, diff-file-header) 5. project management & navigation • projectile (projectile-project-name, projectile-project-root) • treemacs (treemacs-root-face, treemacs-directory-face, treemacs-file-face) 6. Org-mode • org-document-title, org-level-1…org-level-8 • org-checkbox, org-todo, org-done, org-warning, org-date, org-code, org-block • plus any “pretty” add-ons you use (org-bullets, org-modern, etc.) 7. Hydra / which-key / key-help • which-key (which-key-key-face, which-key-group-description-face, which-key-local-map-description-face) • hydra (hydra-face-red, hydra-face-blue, hydra-face-teal, hydra-face-amaranth) 8. visual helpers • rainbow-delimiters-depth-1…9 • highlight-indent-guides-odd/even • highlight-parentheses-/ • beacon (beacon), whitespace (whitespace-space, whitespace-tab, whitespace-newline) • display-line-numbers (line-number, line-number-current-line—you already have these) 9. UI polishers • all-the-icons (all-the-icons-blue, all-the-icons-red, …) on modeline or treemacs • doom-modeline (doom-modeline-bar, doom-modeline-buffer-path) • fancy-splash/dashboard-faces if you use a startup screen 10. terminals & shells • vterm (vterm-copy-region-highlight) • ansi-colors (ansi-color-face-bold, ansi-color-face-italic, ansi-color-[0…7]) To figure out exactly which ones you have in your modules, you can do for example: #+begin_src elisp ;; from your ~/.emacs.d (let (pkgs) (dolist (f (directory-files "~/.emacs.d/modules" t "\\.el$")) (with-temp-buffer (insert-file-contents f) (goto-char (point-min)) (while (re-search-forward "use-package +\\([^ ]+\\)" nil t) (push (intern (match-string 1)) pkgs)))) (delete-dups pkgs)) #+end_src Then for each of those packages, grep in their repo for =defface= or check their manual to see which faces they define, and add reasonable dupre-colours for them. Once you’ve covered that list, you’ll have a “complete” theme that won’t leave any of your modules half-unstyled. **** TODO [#C] Support the built-in “tab-bar” and “tab-group” faces (Emacs 27+) • tab-bar / tab-bar-tab / tab-bar-tab-inactive • tab-bar-close / tab-bar-separator • tab-group-title / tab-group-separator **** TODO [#C] Add faces for =dired=, =diff=, =flycheck=/=flymake=, =magit= (or git-gutter), =ediff=, =erc= etc. For example: • diff-added, diff-removed, diff-context, diff-file-header • flycheck-error, flycheck-warning, flycheck-info • magit-section, magit-log, magit-hunk-heading, magit-branch, etc. **** TODO [#C] Org-mode • org-level-1…org-level-8, org-document-title • org-checkbox, org-code, org-date, org-todo, org-done, org-warning • org-block, org-block-begin-line, org-block-end-line **** TODO [#C] Completion frameworks & minibuffer enhancements • ivy-current-match / ivy-minibuffer-match-face-1…4 • swiper-line-face • vertico-current / marginalia-… • corfu-default / corfu-bar / cape / orderless-match-face-… **** TODO [#C] LSP / tree-sitter / syntax-aware faces • lsp-face-highlight-textual, lsp-face-highlight-read, etc. • tree-sitter-hl-face:variable, :function, :property, etc. **** TODO [#C] Which-Key, Hydras, rainbow-delimiters, etc. • which-key-key-face, which-key-local-map-description-face • hydra-face-red / teal / blue • rainbow-delimiters-depth-1…9 **** TODO [#C] Theme Metadata & Packaging • Add =;; Package-Requires: ((emacs "26.1"))= if you need a minimum. • Consider adding a README with screenshots. • A simple Makefile or script to refresh “theme metadata” (=M-x theme-inspect=, MELPA recipe). **** TODO [#C] Tests • Extend your ERT suite to cover more faces (org, diff, flycheck…). • You might write a small helper that loops over a list of faces and asserts they’re defined without errors. **** Example Sample Faces Below is a small excerpt showing how you might start adding some of the “missing” standard faces and a couple org faces: #+begin_src elisp (custom-theme-set-faces 'dupre ;; … your existing faces … ;; standard global faces `(error ((t (:foreground ,dupre-intense-red :weight bold)))) `(warning ((t (:foreground ,dupre-yellow+1 :weight bold)))) `(success ((t (:foreground ,dupre-green+1 :weight bold)))) `(link ((t (:foreground ,dupre-blue+1 :underline t)))) `(shadow ((t (:foreground ,dupre-gray)))) `(highlight ((t (:background ,dupre-bg+1)))) `(tooltip ((t (:foreground ,dupre-fg :background ,dupre-bg+2)))) `(tooltip-selection ((t (:background ,dupre-bg+0)))) ;; Org-mode sample `(org-document-title ((t (:foreground ,dupre-yellow+2 :weight bold :height 1.2)))) `(org-level-1 ((t (:foreground ,dupre-blue+2 :weight bold)))) `(org-level-2 ((t (:foreground ,dupre-green+1)))) `(org-checkbox ((t (:foreground ,dupre-yellow :weight bold)))) `(org-todo ((t (:foreground ,dupre-red :weight bold)))) `(org-done ((t (:foreground ,dupre-green+2 :weight bold)))) ) #+end_src ** TODO [#B] Emacs Add Difftastic Functionality :enhancement: Linux Binary Install https://github.com/Wilfred/difftastic https://www.wilfred.me.uk/blog/2022/09/06/difftastic-the-fantastic-diff/ https://difftastic.wilfred.me.uk/installation.html (basically $ sudo pacman -S difftastic) Emacs Package https://github.com/pkryger/difftastic.el #+begin_src emacs-lisp (use-package difftastic :defer t :vc (:url "https://github.com/pkryger/difftastic.el.git" :rev :newest)) #+end_src ** TODO [#B] Emacs error if no file in buffer-and-file operations :bug: ** TODO [#B] Add Hugo Blogging Functionality :enhancement: ** TODO [#C] Miscellaneous Maintenance *** TODO [#C] Move persistence and history files into subdir :enhancement: *** DOING [#B] Implement Performance Recommendations :bug: **** Lazy-load configuration modules :complex4:impact5: Loading every module with `require` forces all code and packages to initialize during startup. Switching to `use-package` with `:defer` or `:commands` loads modules only when their functionality is invoked, greatly reducing startup time. #+begin_src emacs-lisp (use-package dirvish-config :load-path "modules" :commands (dirvish dirvish-side)) #+end_src **** DONE Defer Dashboard initialization :complex2:impact4: CLOSED: [2025-09-08 Mon 14:09] The dashboard package is loaded eagerly via `:demand`, adding a noticeable delay. Load it after startup and open it on the first idle event instead. #+begin_src emacs-lisp (use-package dashboard :defer t :hook (emacs-startup . dashboard-open)) #+end_src **** CANCELLED Replace synchronous network ping with non-blocking check :complex3:impact2: CLOSED: [2025-09-08 Mon 16:07] - State "CANCELLED" from [2025-09-08 Mon 16:07] `internet-up-p` spawns a blocking `ping` process at startup. Using `make-network-process` avoids shelling out and lets the check run asynchronously. #+begin_src emacs-lisp (defun internet-up-p (&optional host) "Non-blocking network availability check." (make-network-process :name "net-check" :host (or host "www.google.com") :service 80 :sentinel (lambda (proc _) (setq cj/network-available (eq (process-status proc) 'open)) (delete-process proc)))) #+end_src This actually added 15 seconds to the launch time. **** Postpone package refreshing to idle time :complex1:impact3: Refreshing ELPA archives during startup adds I/O overhead. Defer this check to an idle timer so it runs after Emacs is ready. #+begin_src emacs-lisp (add-hook 'emacs-startup-hook (lambda () (run-with-idle-timer 60 nil #'package-refresh-contents))) #+end_src **** Enable package quickstart caching :complex1:impact2: Precomputing autoloads with package quickstart reduces the cost of loading package code. #+begin_src emacs-lisp (setq package-quickstart t) (package-quickstart-refresh) #+end_src **** Byte-compile configuration files :complex1:impact2: Byte-compiled Emacs Lisp loads faster than source. Recompile the configuration directory when changes are made. #+begin_src emacs-lisp (byte-recompile-directory user-emacs-directory 0) #+end_src **** Manage garbage collection with GCMH :complex1:impact2: After startup, `gcmh` dynamically adjusts GC thresholds to minimize pauses without manual tuning. #+begin_src emacs-lisp (use-package gcmh :hook (after-init . gcmh-mode) :config (setq gcmh-idle-delay 5 gcmh-high-cons-threshold (* 16 1024 1024))) #+end_src **** DONE Load Dirvish on demand :complex2:impact3: CLOSED: [2025-09-13 Sat 20:56] `dirvish-config` requires Dirvish during initialization, negating deferral. Let `use-package` autoload the commands and enable overrides when Dired loads. #+begin_src emacs-lisp (use-package dirvish :commands (dirvish dirvish-side) :hook (dired-mode . dirvish-override-dired-mode)) #+end_src **** DONE Start Org-roam lazily :complex3:impact3: CLOSED: [2025-09-08 Mon 16:10] Org-roam and its database sync run at startup. Load Org-roam only when Org is active, and start autosync after initialization. #+begin_src emacs-lisp (use-package org-roam :after org :commands (org-roam-node-find org-roam-node-insert) :hook (after-init . org-roam-db-autosync-mode)) #+end_src *** DOING [#C] Fix all docstring and elisp linting issues :bug: - [X] user-constants - [X] host-environment - [ ] config-utilities - [ ] system-defaults - [ ] keybindings - [ ] auth-config - [ ] custom-functions - [ ] chrono-tools - [ ] file-config - [ ] keyboard-macros - [ ] system-utils - [ ] text-config - [ ] undead-buffers - [ ] ui-config - [ ] ui-theme - [ ] ui-navigation - [ ] font-config - [ ] diff-config - [ ] eshell-vterm-config - [ ] flyspell-and-abbrev - [ ] help-utils - [ ] help-config - [ ] latex-config - [ ] modeline-config - [ ] pdf-config - [ ] selection-framework - [ ] tramp-config - [ ] show-kill-ring - [ ] calibredb-epub-config - [ ] dashboard-config - [ ] dirvish-config - [ ] dwim-shell-config - [ ] elfeed-config - [ ] erc-config - [ ] eww-config - [ ] httpd-config - [ ] mail-config - [ ] markdown-config - [ ] weather-config - [ ] prog-general - [ ] vc-config - [ ] flycheck-config - [ ] prog-lsp - [ ] prog-training - [ ] prog-c - [ ] prog-go - [ ] prog-lisp - [ ] prog-shell - [ ] prog-python - [ ] prog-webdev - [ ] prog-yaml - [ ] org-config - [ ] org-agenda-config - [ ] org-babel-config - [ ] org-capture-config - [ ] org-refile-config - [ ] org-drill-config - [ ] org-export-config - [ ] org-roam-config - [ ] org-contacts-config - [ ] ai-config - [ ] reconcile-open-repos - [ ] video-audio-recording - [ ] local-repository - [ ] eradio-config - [ ] games-config - [ ] wrap-up ** TODO [#C] Git Timemachine Litters Empty Buffers :bug: Don't choose a revision and you'll see a blank buffer that needs to be killed ** TODO [#C] capture windows should pop up from bottom and 1/3 at most ** TODO [#C] install visible mark so you know where the mark is #+BEGIN_QUOTE Showing the location of the mark, probably helps with learning to use mark better. (use-package visible-mark :init (global-visible-mark-mode) :custom (visible-mark-faces '(visible-mark-face1 visible-mark-face2)) (visible-mark-forward-faces '(visible-mark-face1 visible-mark-face2)) (visible-mark-max 2)) #+END_QUOTE [[https://gitlab.com/jdm204/dotfiles/-/blob/master/config.org][config.org · master · jdm204 / dotfiles · GitLab]] Captured On: [2025-10-15 Wed 18:33] ** TODO [#C] Edit Indirect (Custom Code) Bugs and Improvements *** Bugs (by Severity) *** High Severity *Race Condition in =edit-indirect--commit=* The commit function modifies the parent buffer while iterating through hooks that may also modify the buffer. The use of markers helps, but there's still a potential race condition if hooks modify the region being replaced. The =save-match-data= approach with markers could fail if the hooks significantly alter the buffer structure between marker creation and replacement. *Potential Data Loss in =edit-indirect--clean-up=* The function calls =delete-overlay= before setting =edit-indirect--overlay= to nil. If =delete-overlay= signals an error (e.g., if the overlay was already deleted), the overlay reference won't be cleared, potentially causing infinite loops in =edit-indirect--abort-on-kill-buffer=. *** Medium Severity *Missing Buffer Validation in =edit-indirect--commit=* The function doesn't check if the parent buffer still exists before attempting to modify it. If the parent buffer was killed while the indirect buffer was being edited, this will cause an error. *Hook Execution Order Issue* The =edit-indirect--rebind-save-hooks= function is added to =after-change-major-mode-hook= globally but never removed. This means it will be called for every buffer that changes major mode, not just edit-indirect buffers, causing unnecessary overhead. *** Low Severity *Inefficient Overlay Search in =edit-indirect--search-for-edit-indirect=* The function uses =overlays-in= which returns all overlays, then filters them. For buffers with many overlays, this is inefficient. *Missing Error Handling in =edit-indirect--display-buffer=* The function doesn't handle cases where =display-buffer= returns nil (which can happen if all windows are dedicated or display is inhibited). *** Recommended Improvements *** Easy Implementation *Add Buffer Existence Check* Add a check in =edit-indirect--commit= to ensure the parent buffer still exists: #+begin_src emacs-lisp (unless (buffer-live-p (overlay-buffer edit-indirect--overlay)) (error "Parent buffer no longer exists")) #+end_src *Improve Hook Management* Remove the global hook when not needed: #+begin_src emacs-lisp (defun edit-indirect--rebind-save-hooks () (when (edit-indirect-buffer-indirect-p) (setq buffer-offer-save t) (add-hook 'write-contents-functions #'edit-indirect--commit-on-save nil t) ;; Remove global hook after use (remove-hook 'after-change-major-mode-hook #'edit-indirect--rebind-save-hooks))) #+end_src *Add Safety to Clean-up* Wrap cleanup operations in condition-case: #+begin_src emacs-lisp (defun edit-indirect--clean-up () (condition-case nil (delete-overlay edit-indirect--overlay) (error nil)) (setq edit-indirect--overlay nil) ...) #+end_src *Add Display Buffer Error Handling* Check display-buffer return value: #+begin_src emacs-lisp (defun edit-indirect--display-buffer (buffer) (with-current-buffer buffer (setq-local edit-indirect--should-quit-window t)) (let ((window (display-buffer buffer))) (if window (select-window window) (switch-to-buffer buffer))) nil) #+end_src *** Medium Implementation *Optimize Overlay Search* Use a more efficient search strategy: #+begin_src emacs-lisp (defun edit-indirect--search-for-edit-indirect (beg end) (cl-loop for overlay in (overlays-in beg end) when (overlay-get overlay 'edit-indirect-buffer) return overlay)) #+end_src *Add Undo Boundary Management* Add undo boundaries to make commits atomic: #+begin_src emacs-lisp (defun edit-indirect--commit () (undo-boundary) ;; ... rest of commit logic ... (undo-boundary)) #+end_src *Improve Buffer Naming* Make buffer names more informative: #+begin_src emacs-lisp (defun edit-indirect--create-indirect-buffer (beg end overlay) (let* ((mode-name (symbol-name major-mode)) (buffer-name (format "*edit-indirect %s [%s]*" (buffer-name) (replace-regexp-in-string "-mode$" "" mode-name)))) ...)) #+end_src *** Hard Implementation *Add Support for Nested Indirect Edits* Allow editing a region within an already indirect buffer by tracking parent chain. *Implement Diff Preview* Add a command to show differences between the indirect buffer and parent region before committing: #+begin_src emacs-lisp (defun edit-indirect-diff-preview () "Show differences between indirect buffer and parent region." (interactive) ;; Implementation using diff-mode ) #+end_src *Add Persistent Session Support* Save and restore indirect editing sessions across Emacs restarts using desktop.el integration. *Implement Conflict Resolution* Handle cases where the parent buffer was modified while editing in the indirect buffer, similar to version control merge conflicts. *Add Support for Multiple Regions* Allow editing multiple non-contiguous regions in a single indirect buffer, useful for refactoring similar code blocks simultaneously. ** TODO [#C] Review Titlecase Functionality added in custom. Came from: https://codeberg.org/acdw/titlecase.el Originally seen at https://emacselements.com/true-titlecase-in-emacs.html ** TODO [#C] Revisit and Refactor Localrepo Functionality ** TODO [#C] Fix Exporting Documents to HTML. This appears to work with emacs -q. What's up with your config? ** TODO [#C] Rename 'sync-dir' Variable in Init File to 'org-dir' [[https://github.com/ryuslash/mode-icons][ryuslash/mode-icons: Show icons instead of mode names]] Captured On: [2025-06-07 Sat 13:29] ** TODO [#C] Emacs Change Appropriate Use-package Installs to :vc especially where you're cloning them to provide fixes ** TODO [#C] Emacs Config: Narrowing Org doesn't allow refresh by type After generating an org agenda list, I can narrow the type to all DONE or IN-PROGRESS. However when I use my custom functions to just get the agenda for the current buffer, I can't. The scope has now widened and the refresh is for all buffers. ** TODO [#C] Add a Restrict to Subtree Org Agenda Command In Org-mode the “agenda” machinery by default doesn’t pay any attention to your buffer­narrow — it always scans whole files in your =org-agenda-files=. However, you can teach Org-agenda to only look between two points (the start and end of your current subtree) by using the built-in “restrict” hooks: 1. =org‐agenda‐restrict= – turn on restriction 2. =org‐agenda‐restrict‐begin= – a marker or position where scanning begins 3. =org‐agenda‐restrict‐end= – where it ends 4. (optionally) set =org-agenda-files= to just your current file Here is a drop-in =org-agenda-custom-commands= entry that will give you a “Subtree TODOs” view limited to the subtree you’re on. Put this in your Emacs init: #+begin_src emacs-lisp ;; ------------------------------------------------------------------- ;; 1) Define a custom agenda command "C" (hit C-c a C) ;; that shows only TODO entries in the current subtree. ;; ------------------------------------------------------------------- (with-eval-after-load 'org (add-to-list 'org-agenda-custom-commands '("C" ; the key you press after C-c a "Subtree TODOs" ; a descriptive name ((todo "" ; match any TODO keyword ;; === per-block settings === ;; only look in this file (org-agenda-files (list buffer-file-name)) ;; enable the restriction engine (org-agenda-restrict t) ;; start at the top of the current heading (org-agenda-restrict-begin (progn (org-back-to-heading t) (point))) ;; end at the end of this subtree (org-agenda-restrict-end (progn (org-end-of-subtree t) (point)))) ;; you can add more blocks here if you like )))) #+end_src How this works, step by step: • We bind a new custom command under the key “C” (so you’ll type =C-c a C=). • In that command we use the =todo= matcher =""= to catch every headline whose state is one of your TODO keywords. • We dynamically set – =org-agenda-files= to just the current buffer’s file, – =org-agenda-restrict= to non-nil so Org will honor the begin/end markers, – =org-agenda-restrict-begin= to the position of the current heading, – =org-agenda-restrict-end= to the end of the subtree. When you invoke it (=C-c a C=), Org will only scan headlines in that slice of the file and will build you a mini-agenda of TODO items from exactly that subtree. Quick alternative: if you don’t need the full agenda machinery (dates, deadlines, etc.) but just want a fast outline of your TODOs under the current heading, you can also use the sparse-tree command: • =C-c / t= ⇒ shows only the TODO headings in the narrowed or whole buffer as an indented outline. — Tips & Pitfalls — • Make sure your file is saved and in =org-agenda-files= (the snippet above forces it). • Remember to call =org-narrow-to-subtree= (or let the command compute its own begin/end via =org-back-to-heading=/=org-end-of-subtree=). • If you rename or move your file, Emacs must still see =buffer-file-name= valid. • You can duplicate the above snippet for other TODO states, agenda views, or matchers (deadlines, tags, etc.) With this in place you effectively get an agenda tailored to exactly the bit of tree you’re working on. ** TODO [#C] Find Another Keymapping for Emojify ** TODO [#C] Emacs: Add Reverso Functionality :enhancement: https://sqrtminusone.xyz/packages/reverso/ check his config for a usage example Reverso reverso.el is a package of mine that provides Emacs interface for https://reverso.net. #+begin_src emacs-lisp (use-package reverso :straight (:host github :repo "SqrtMinusOne/reverso.el") :init (my-leader-def "ar" #'reverso) :commands (reverso) :config (setq reverso-languages '(russian english german spanish french portuguese)) (reverso-history-mode)) #+end_src ** TODO [#C] Install Magit TODOs #+BEGIN_QUOTE It’s nice for magit to display in-tree TODOs in the status buffer: (use-package magit-todos :init (magit-todos-mode) :custom (magit-todos-exclude-globs '(".git/" "*.gz" "*.html" "*.fastq" "*.fq")) :after (magit)) #+END_QUOTE [[https://gitlab.com/jdm204/dotfiles/-/blob/master/config.org][config.org · master · jdm204 / dotfiles · GitLab]] Captured On: [2025-10-15 Wed 18:40] ** TODO [#C] Move from company to corfu #+BEGIN_QUOTE Completion A completion framework—I used to use company but corfu seems to integrate with stock Emacs better. (use-package corfu :custom (corfu-cycle t) :init (global-corfu-mode) :config (with-eval-after-load 'savehist (corfu-history-mode 1) (add-to-list 'savehist-additional-variables 'corfu-history))) Use more completion at point functions from cape. dabbrev completion means that you can complete from words already present in the buffer, which helps for documents with long words that get used a lot. (use-package cape :init (dolist (fn (list #'cape-file (cape-capf-super #'cape-dabbrev #'cape-dict #'org-block-capf #'cape-keyword))) (add-to-list 'completion-at-point-functions fn)) :config (let ((wordlist (getenv "WORDLIST"))) (setq cape-dict-file wordlist ispell-alternate-dictionary wordlist))) #+END_QUOTE [[https://gitlab.com/jdm204/dotfiles/-/blob/master/config.org][config.org · master · jdm204 / dotfiles · GitLab]] Captured On: [2025-10-15 Wed 18:37] ** TODO [#C] Config recentf to exclude mail #+BEGIN_QUOTE recentf provides a persistent list of recently-opened files. (use-package recentf :ensure nil :init (recentf-mode) :config (setq recentf-exclude '("Maildir"))) #+END_QUOTE [[https://gitlab.com/jdm204/dotfiles/-/blob/master/config.org][config.org · master · jdm204 / dotfiles · GitLab]] Captured On: [2025-10-15 Wed 18:36] ** TODO [#C] consider installing minimap #+BEGIN_QUOTE A mini-map (miniaturised window showing more of the buffer than the main window) is nice for longer documents: (use-package minimap :custom (minimap-major-modes '(org-mode prog-mode)) (minimap-window-location 'right) :bind ("C-+" . minimap-mode)) #+END_QUOTE [[https://gitlab.com/jdm204/dotfiles/-/blob/master/config.org][config.org · master · jdm204 / dotfiles · GitLab]] Captured On: [2025-10-15 Wed 18:31] ** TODO [#C] check into org-download for drag/drop images into org-buffers #+BEGIN_QUOTE Drag and drop images into Org buffers to insert links: (use-package org-download :custom (org-download-image-dir "./img")) #+END_QUOTE [[https://gitlab.com/jdm204/dotfiles/-/blob/master/config.org][config.org · master · jdm204 / dotfiles · GitLab]] Captured On: [2025-10-15 Wed 18:30] ** TODO [#D] BrainCurses Emacs Mastermind Game [[https://sourceforge.net/projects/braincurses/][BrainCurses download | SourceForge.net]] Captured On: [2025-09-26 Fri 00:38] ** TODO [#D] Emacs: org project should be yellow or green like todo ** TODO [#D] Emacs Signal Client *** TODO investigate: net/signel.org · master · undefined · GitLab [[https://gitlab.com/jaor/elibs/-/blob/master/net/signel.org][net/signel.org · master · undefined · GitLab]] Captured On: [2025-05-29 Thu 04:24] *** TODO investigate: signel, a barebones signal chat on top of signal-cli [[https://jao.io/blog/signel.html][signel, a barebones signal chat on top of signal-cli]] Captured On: [2025-05-29 Thu 04:23] ** TODO [#D] Add test document to test graphviz-dot-mode ** TODO [#D] Consider Replicating Some of This Auctex and PDF Tools Code [[https://www.reddit.com/r/emacs/comments/cd6fe2/how_to_make_emacs_a_latex_ide/][How to make Emacs a Latex IDE? : r/emacs]] Captured On: [2025-08-14 Thu 03:43] ** TODO [#D] Install ZOxide Integration into Emacs [[https://gitlab.com/Vonfry/zoxide.el][Vonfry / zoxide.el · GitLab]] Captured On: [2025-06-07 Sat 17:11] ** TODO [#D] manateelazycat/awesome-tray: Hide mode-line, display necessary information at right of minibuffer. [[https://github.com/manateelazycat/awesome-tray][manateelazycat/awesome-tray: Hide mode-line, display necessary information at right of minibuffer.]] Captured On: [2025-06-07 Sat 13:29] ** TODO [#D] ryuslash/mode-icons: Show icons instead of mode names