diff options
| author | Craig Jennings <c@cjennings.net> | 2025-11-08 16:11:58 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-11-08 16:11:58 -0600 |
| commit | 8176eff73b826f7fec9d7f458f7d2f36f4d12e58 (patch) | |
| tree | 3e73394b689f0e32dce6930431d9060d946b0b79 | |
| parent | d093a4a96c653d3f9adcbba17b4094d6d9a5a85a (diff) | |
feat: Fix modeline lag and add org multi-level sort with comprehensive tests
Performance improvement and new feature with full test coverage.
## Changes
### 1. Fix modeline line/column position lag (#A priority)
- Replace expensive line-number-at-pos with cached %l/%c format specifiers
- Enable line-number-mode explicitly for caching
- Result: Instant modeline updates, zero performance overhead
- Files: modules/modeline-config.el:81-83, modules/ui-config.el:53
### 2. Implement multi-level org sorting
- New function: cj/org-sort-by-todo-and-priority
- Sorts by TODO status (TODO before DONE) AND priority (A→B→C→D)
- Uses stable sorting: priority first, then TODO state
- Gracefully handles empty sections (no error)
- Bound to C-; o o (ordering → org sort)
- Files: modules/org-config.el:278-299, modules/custom-ordering.el:253,267
### 3. Comprehensive ERT test suite (12/12 passing)
- Normal cases: Mixed TODO/DONE, multiple of same type, same priority
- Boundary cases: Empty sections, single entries, no priorities
- Error cases: Non-org-mode buffer
- Test file: tests/test-org-sort-by-todo-and-priority.el
### 4. Testing improvements discovered
- Disable org-mode hooks to avoid package dependencies in batch mode
- org-sort-entries must be called from parent heading
- Preserve priority cookie in org-get-heading (t t nil t)
- Add condition-case to handle "Nothing to sort" gracefully
### 5. Minor cleanup
- Comment out chime-debug setting (org-agenda-config.el:267)
- Mark modeline lag task as DONE in todo.org
## Technical Details
Modeline optimization:
- line-number-at-pos is O(n) where n = current line
- %l and %c are O(1) lookups from cached values
Org sorting algorithm uses stable sort:
1. Sort by priority (A, B, C, D, unprioritized)
2. Sort by TODO status (preserves priority order within groups)
Result: TODO [#A], TODO [#B], DONE [#A], DONE [#B], etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
| -rw-r--r-- | assets/abbrev_defs | 7 | ||||
| -rw-r--r-- | modules/auth-config.el | 6 | ||||
| -rw-r--r-- | modules/custom-ordering.el | 6 | ||||
| -rw-r--r-- | modules/modeline-config.el | 5 | ||||
| -rw-r--r-- | modules/org-agenda-config.el | 2 | ||||
| -rw-r--r-- | modules/org-config.el | 24 | ||||
| -rw-r--r-- | modules/org-gcal-config.el | 2 | ||||
| -rw-r--r-- | modules/ui-config.el | 3 | ||||
| -rw-r--r-- | modules/weather-config.el | 4 | ||||
| -rw-r--r-- | tests/test-org-sort-by-todo-and-priority.el | 283 | ||||
| -rw-r--r-- | todo.org | 344 |
11 files changed, 499 insertions, 187 deletions
diff --git a/assets/abbrev_defs b/assets/abbrev_defs index cd9c6818..8fc58efd 100644 --- a/assets/abbrev_defs +++ b/assets/abbrev_defs @@ -132,7 +132,7 @@ ("customizaton" "customization" nil :count 0) ("dacquiri" "daiquiri" nil :count 0) ("daneel" "Danneel" nil :count 0) - ("danneel" "Danneel" nil :count 25) + ("danneel" "Danneel" nil :count 26) ("daquiri" "daiquiri" nil :count 0) ("decieve" "deceive" nil :count 0) ("decisons" "decisions" nil :count 0) @@ -294,7 +294,7 @@ ("oppositiion" "opposition" nil :count 0) ("opppsite" "opposite" nil :count 0) ("orignal" "original" nil :count 0) - ("ot" "to" nil :count 42) + ("ot" "to" nil :count 43) ("otehr" "other" nil :count 3) ("otes" "notes" nil :count 0) ("outgoign" "outgoing" nil :count 0) @@ -393,7 +393,7 @@ ("takss" "tasks" nil :count 3) ("talekd" "talked" nil :count 0) ("talkign" "talking" nil :count 6) - ("teh" "the" nil :count 156) + ("teh" "the" nil :count 159) ("tehir" "their" nil :count 5) ("tehre" "there" nil :count 3) ("testimentary" "testamentary" nil :count 1) @@ -425,6 +425,7 @@ ("vehical" "vehicle" nil :count 0) ("visious" "vicious" nil :count 0) ("waht" "what" nil :count 4) + ("walkthrough" "walk" nil :count 0) ("warant" "warrant" nil :count 0) ("welfair" "welfare" nil :count 0) ("welomce" "welcome" nil :count 0) diff --git a/modules/auth-config.el b/modules/auth-config.el index 2b52087e..c3000f7f 100644 --- a/modules/auth-config.el +++ b/modules/auth-config.el @@ -40,7 +40,11 @@ :config (epa-file-enable) ;; (setq epa-pinentry-mode 'loopback) ;; emacs request passwords in minibuffer - (setq epg-gpg-program "gpg2")) ;; force use gpg2 (not gpg v.1) + (setq epg-gpg-program "gpg2") ;; force use gpg2 (not gpg v.1) + + ;; Update gpg-agent with current DISPLAY environment + ;; This ensures pinentry can open GUI windows when Emacs starts + (call-process "gpg-connect-agent" nil nil nil "updatestartuptty" "/bye")) ;; ---------------------------------- Plstore ---------------------------------- ;; Encrypted storage used by oauth2-auto for Google Calendar tokens. diff --git a/modules/custom-ordering.el b/modules/custom-ordering.el index 7d906e75..f6972910 100644 --- a/modules/custom-ordering.el +++ b/modules/custom-ordering.el @@ -249,7 +249,8 @@ Returns the transformed string without modifying the buffer." "r" #'cj/reverse-lines "n" #'cj/number-lines "A" #'cj/alphabetize-region - "L" #'cj/comma-separated-text-to-lines) + "L" #'cj/comma-separated-text-to-lines + "o" #'cj/org-sort-by-todo-and-priority) (keymap-set cj/custom-keymap "o" cj/ordering-map) (with-eval-after-load 'which-key @@ -262,7 +263,8 @@ Returns the transformed string without modifying the buffer." "C-; o r" "reverse lines" "C-; o n" "number lines" "C-; o A" "alphabetize" - "C-; o L" "comma to lines")) + "C-; o L" "comma to lines" + "C-; o o" "org: sort by TODO+priority")) (provide 'custom-ordering) ;;; custom-ordering.el ends here. diff --git a/modules/modeline-config.el b/modules/modeline-config.el index b1403539..a1c85caa 100644 --- a/modules/modeline-config.el +++ b/modules/modeline-config.el @@ -78,8 +78,9 @@ Green = writeable, Red = read-only, Gold = overwrite. Truncates in narrow windows. Click to switch buffers.") (defvar-local cj/modeline-position - '(:eval (format "L:%d C:%d" (line-number-at-pos) (current-column))) - "Line and column position as L:line C:col.") + '("L:" (:eval (format-mode-line "%l")) " C:" (:eval (format-mode-line "%c"))) + "Line and column position as L:line C:col. +Uses built-in cached values for performance.") (defvar cj/modeline-vc-faces '((added . vc-locally-added-state) diff --git a/modules/org-agenda-config.el b/modules/org-agenda-config.el index 61e542f6..70ca9d4a 100644 --- a/modules/org-agenda-config.el +++ b/modules/org-agenda-config.el @@ -264,7 +264,7 @@ This allows a line to show in an agenda without being scheduled or a deadline." :load-path "~/code/chime.el" :init ;; Debug mode (keep set to nil, but available for troubleshooting) - (setq chime-debug nil) + ;; (setq chime-debug nil) :bind ("C-c A" . chime-check) :config diff --git a/modules/org-config.el b/modules/org-config.el index 75d4c7db..5cae1d0e 100644 --- a/modules/org-config.el +++ b/modules/org-config.el @@ -225,6 +225,7 @@ (use-package org-appear :hook (org-mode . org-appear-mode) + :disabled t :custom (org-appear-autoemphasis t) ;; Show * / _ when cursor is on them (org-appear-autolinks t) ;; Also works for links @@ -274,6 +275,29 @@ the current buffer's cache. Useful when encountering parsing errors like (message "Cleared org-element cache for current buffer")) (user-error "Current buffer is not in org-mode")))) +;; ----------------------- Org Multi-Level Sorting ----------------------------- + +(defun cj/org-sort-by-todo-and-priority () + "Sort org entries by TODO status (TODO before DONE) and priority (A to D). +Sorts the current level's entries. Within each TODO state group, entries are +sorted by priority. Uses stable sorting: sort by priority first, then by TODO +status to preserve priority ordering within TODO groups." + (interactive) + (unless (derived-mode-p 'org-mode) + (user-error "Current buffer is not in org-mode")) + (save-excursion + ;; First sort by priority (A, B, C, D, then no priority) + ;; Ignore "Nothing to sort" errors for empty sections + (condition-case nil + (org-sort-entries nil ?p) + (user-error nil)) + ;; Then sort by TODO status (TODO before DONE) + ;; This preserves the priority ordering within each TODO group + (condition-case nil + (org-sort-entries nil ?o) + (user-error nil))) + (message "Sorted entries by TODO status and priority")) + ;; which-key labels for org-table-map (with-eval-after-load 'which-key (which-key-add-key-based-replacements diff --git a/modules/org-gcal-config.el b/modules/org-gcal-config.el index 97e8446a..4eca5e7e 100644 --- a/modules/org-gcal-config.el +++ b/modules/org-gcal-config.el @@ -190,7 +190,7 @@ Useful after changing `cj/org-gcal-sync-interval-minutes'." ;; Start automatic sync timer based on user configuration ;; Set cj/org-gcal-sync-interval-minutes to nil to disable -(cj/org-gcal-start-auto-sync) +;; (cj/org-gcal-start-auto-sync) ;; Google Calendar keymap and keybindings (defvar-keymap cj/gcal-map diff --git a/modules/ui-config.el b/modules/ui-config.el index 837d2169..3922ce2a 100644 --- a/modules/ui-config.el +++ b/modules/ui-config.el @@ -50,7 +50,8 @@ (setq use-file-dialog nil) ;; no file dialog (setq use-dialog-box nil) ;; no dialog boxes either -(column-number-mode 1) ;; show column number in the modeline +(line-number-mode 1) ;; show line number in the modeline (cached) +(column-number-mode 1) ;; show column number in the modeline (cached) (setq switch-to-buffer-obey-display-actions t) ;; manual buffer switching obeys display action rules ;; -------------------------------- Transparency ------------------------------- diff --git a/modules/weather-config.el b/modules/weather-config.el index 3a30aa17..82589af0 100644 --- a/modules/weather-config.el +++ b/modules/weather-config.el @@ -14,7 +14,8 @@ (add-to-list 'load-path "/home/cjennings/code/wttrin") ;; Set debug flag BEFORE loading wttrin (checked at load time) -(setq wttrin-debug nil) +;; Change this to t to enable debug logging +(setq wttrin-debug t) (use-package wttrin ;; Uncomment the next line to use vc-install instead of local directory: @@ -27,6 +28,7 @@ :bind ("M-W" . wttrin) :custom + ;; wttrin-debug must be set BEFORE loading (see line 17 above) (wttrin-unit-system "u") (wttrin-mode-line-favorite-location "New Orleans, LA") (wttrin-mode-line-refresh-interval 900) ; 15 minutes diff --git a/tests/test-org-sort-by-todo-and-priority.el b/tests/test-org-sort-by-todo-and-priority.el new file mode 100644 index 00000000..873f37c2 --- /dev/null +++ b/tests/test-org-sort-by-todo-and-priority.el @@ -0,0 +1,283 @@ +;;; test-org-sort-by-todo-and-priority.el --- Tests for cj/org-sort-by-todo-and-priority -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Unit tests for cj/org-sort-by-todo-and-priority function. +;; Tests multi-level sorting: TODO status (TODO before DONE) and priority (A before B before C). +;; +;; Testing approach: +;; - Use real org-mode buffers (don't mock org-sort-entries) +;; - Trust org-mode framework works correctly +;; - Test OUR integration logic: calling org-sort-entries twice in correct order +;; - Verify final sort order matches expected TODO/priority combination +;; +;; The function uses stable sorting: +;; 1. First sort by priority (A, B, C, D, none) +;; 2. Then sort by TODO status (TODO before DONE) +;; Result: Priority order preserved within each TODO state group + +;;; Code: + +(require 'ert) +(require 'org) +(require 'org-config) ; Defines cj/org-sort-by-todo-and-priority + +;;; Test Helpers + +(defun test-org-sort-by-todo-and-priority--create-buffer (content) + "Create a temporary org-mode buffer with CONTENT. +Returns the buffer object. +Disables org-mode hooks to avoid missing package dependencies in batch mode." + (let ((buf (generate-new-buffer "*test-org-sort*"))) + (with-current-buffer buf + ;; Disable hooks to prevent org-superstar and other package loads + (let ((org-mode-hook nil)) + (org-mode)) + (insert content) + (goto-char (point-min))) + buf)) + +(defun test-org-sort-by-todo-and-priority--get-entry-order (buffer) + "Extract ordered list of TODO states and priorities from BUFFER. +Returns list of strings like \"TODO [#A]\" or \"DONE\" for each heading." + (with-current-buffer buffer + (goto-char (point-min)) + (let (entries) + (org-map-entries + (lambda () + (let* ((todo-state (org-get-todo-state)) + ;; Get heading: no-tags, no-todo, KEEP priority, no-comment + (heading (org-get-heading t t nil t)) + ;; Extract priority cookie from heading text + (priority (when (string-match "\\[#\\([A-Z]\\)\\]" heading) + (match-string 1 heading)))) + (push (if priority + (format "%s [#%s]" (or todo-state "") priority) + (or todo-state "")) + entries))) + nil 'tree) + (nreverse entries)))) + +(defun test-org-sort-by-todo-and-priority--sort-children (buffer) + "Position cursor on parent heading in BUFFER and sort its children. +Moves to first * heading (Parent) and calls sort function to sort children." + (with-current-buffer buffer + (goto-char (point-min)) + (when (re-search-forward "^\\* " nil t) + (beginning-of-line) + (cj/org-sort-by-todo-and-priority)))) + +;;; Normal Cases + +(ert-deftest test-org-sort-by-todo-and-priority-normal-mixed-todo-done-sorts-correctly () + "Test mixed TODO and DONE entries with various priorities sort correctly. + +Input: TODO [#A], DONE [#B], TODO [#C], DONE [#A] +Expected: TODO [#A], TODO [#C], DONE [#A], DONE [#B]" + (let* ((content "* Parent +** TODO [#A] First task +** DONE [#B] Second task +** TODO [#C] Third task +** DONE [#A] Fourth task +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "TODO [#A]" "TODO [#C]" "DONE [#A]" "DONE [#B]"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-normal-multiple-todos-sorts-by-priority () + "Test multiple TODO entries sort by priority A before B before C. + +Input: TODO [#C], TODO [#A], TODO [#B] +Expected: TODO [#A], TODO [#B], TODO [#C]" + (let* ((content "* Parent +** TODO [#C] Task C +** TODO [#A] Task A +** TODO [#B] Task B +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "TODO [#A]" "TODO [#B]" "TODO [#C]"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-normal-multiple-dones-sorts-by-priority () + "Test multiple DONE entries sort by priority A before B before C. + +Input: DONE [#C], DONE [#A], DONE [#B] +Expected: DONE [#A], DONE [#B], DONE [#C]" + (let* ((content "* Parent +** DONE [#C] Done C +** DONE [#A] Done A +** DONE [#B] Done B +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "DONE [#A]" "DONE [#B]" "DONE [#C]"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-normal-same-priority-todo-before-done () + "Test entries with same priority sort TODO before DONE. + +Input: DONE [#A], TODO [#A] +Expected: TODO [#A], DONE [#A]" + (let* ((content "* Parent +** DONE [#A] Done task +** TODO [#A] Todo task +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "TODO [#A]" "DONE [#A]"))))) + (kill-buffer buf)))) + +;;; Boundary Cases + +(ert-deftest test-org-sort-by-todo-and-priority-boundary-empty-section-no-error () + "Test sorting empty section does not signal error. + +Input: Heading with no children +Expected: No error, no change" + (let* ((content "* Parent\n") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (with-current-buffer buf + (goto-char (point-min)) + (should-not (condition-case err + (progn + (cj/org-sort-by-todo-and-priority) + nil) + (error err)))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-boundary-single-todo-no-change () + "Test sorting single TODO entry does not change order. + +Input: Single TODO [#A] +Expected: Same order (no change)" + (let* ((content "* Parent +** TODO [#A] Only task +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "TODO [#A]"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-boundary-single-done-no-change () + "Test sorting single DONE entry does not change order. + +Input: Single DONE [#B] +Expected: Same order (no change)" + (let* ((content "* Parent +** DONE [#B] Only task +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "DONE [#B]"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-boundary-all-todos-sorts-by-priority () + "Test all TODO entries sort by priority only. + +Input: TODO [#C], TODO [#A], TODO [#B] +Expected: TODO [#A], TODO [#B], TODO [#C]" + (let* ((content "* Parent +** TODO [#C] Task C +** TODO [#A] Task A +** TODO [#B] Task B +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "TODO [#A]" "TODO [#B]" "TODO [#C]"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-boundary-all-dones-sorts-by-priority () + "Test all DONE entries sort by priority only. + +Input: DONE [#B], DONE [#D], DONE [#A] +Expected: DONE [#A], DONE [#B], DONE [#D]" + (let* ((content "* Parent +** DONE [#B] Done B +** DONE [#D] Done D +** DONE [#A] Done A +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "DONE [#A]" "DONE [#B]" "DONE [#D]"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-boundary-no-priorities-sorts-by-todo () + "Test entries without priorities sort by TODO status only. + +Input: TODO (no priority), DONE (no priority), TODO (no priority) +Expected: TODO, TODO, DONE" + (let* ((content "* Parent +** TODO Task 1 +** DONE Task 2 +** TODO Task 3 +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "TODO" "TODO" "DONE"))))) + (kill-buffer buf)))) + +(ert-deftest test-org-sort-by-todo-and-priority-boundary-unprioritized-after-prioritized () + "Test unprioritized entries appear after prioritized within TODO/DONE groups. + +Input: TODO (no priority), TODO [#A], DONE [#B], DONE (no priority) +Expected: TODO [#A], TODO (no priority), DONE [#B], DONE (no priority)" + (let* ((content "* Parent +** TODO Task no priority +** TODO [#A] Task A +** DONE [#B] Done B +** DONE Done no priority +") + (buf (test-org-sort-by-todo-and-priority--create-buffer content))) + (unwind-protect + (progn + (test-org-sort-by-todo-and-priority--sort-children buf) + (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf))) + (should (equal order '("" "TODO [#A]" "TODO" "DONE [#B]" "DONE"))))) + (kill-buffer buf)))) + +;;; Error Cases + +(ert-deftest test-org-sort-by-todo-and-priority-error-non-org-buffer-signals-error () + "Test calling in non-org-mode buffer signals user-error. + +Input: fundamental-mode buffer +Expected: user-error" + (let ((buf (generate-new-buffer "*test-non-org*"))) + (unwind-protect + (with-current-buffer buf + (fundamental-mode) + (should-error (cj/org-sort-by-todo-and-priority) :type 'user-error)) + (kill-buffer buf)))) + +(provide 'test-org-sort-by-todo-and-priority) +;;; test-org-sort-by-todo-and-priority.el ends here @@ -17,7 +17,89 @@ If the answer is "no" to all five → DON'T ADD IT. V2MOM is located at: [[file:docs/emacs-config-v2mom.org][emacs-config-v2mom.org]] Research/ideas that don't serve vision: [[file:docs/someday-maybe.org][someday-maybe.org]] -* Method 1: Make Using Emacs Frictionless [8/18] +* Method 1: Make Using Emacs Frictionless [10/18] + +** TODO [#A] Fix Google Calendar password prompts every 15 minutes + +IRRITANT: gcal-sync triggers password prompts approximately every 15 minutes, +interrupting workflow and breaking focus. This defeats the purpose of having +passphrase caching configured. + +**Current Setup:** +- GPG agent configured with 400-day cache (gpg-agent.conf): + - default-cache-ttl 34560000 + - max-cache-ttl 34560000 + - allow-loopback-pinentry enabled +- Plstore caching enabled (auth-config.el:54): + - plstore-cache-passphrase-for-symmetric-encryption t + - plstore-encrypt-to nil (symmetric encryption) +- Auth-source cache: 24 hours (auth-config.el:31) +- Auto-sync interval: 30 minutes (org-gcal-config.el:50) + +**Problem:** +Despite proper GPG agent caching, oauth2-auto.plist prompts for passphrase +every ~15 minutes during gcal-sync operations. This suggests: +1. plstore may not be using GPG agent cache properly for symmetric encryption +2. oauth2-auto token refresh might be bypassing cache +3. EPinentry mode may need explicit configuration (currently commented out) + +**Goal:** +Passphrase should be entered ONCE per Emacs session, then cached until Emacs +closes. No interruptions during normal work. + +**Investigation Paths:** +1. Check if oauth2-auto respects plstore passphrase caching +2. Investigate plstore symmetric encryption cache behavior with GPG agent +3. Test enabling epa-pinentry-mode 'loopback (auth-config.el:42) +4. Check oauth2-auto token refresh cycle vs password prompt timing +5. Consider oauth2-auto configuration options for token persistence +6. Review org-gcal or oauth2-auto issues for similar problems + +**Files:** +- modules/auth-config.el (plstore and GPG configuration) +- modules/org-gcal-config.el (org-gcal and oauth2-auto setup) +- ~/.gnupg/gpg-agent.conf (GPG agent cache settings) +- oauth2-auto.plist (encrypted OAuth tokens - prompts every access?) + +**Related:** +This violates the "Frictionless" value - interruptions every 15 minutes during +calendar sync breaks concentration and workflow momentum. + +** TODO [#B] Fix org-noter (reading/annotation workflow currently "so painful") + +High priority - daily pain point. + +** TODO [#B] Fix mail attachment workflow (currently awkward) + +Daily workflow improvement. + +** TODO [#B] Toggle org-appear on/off + +When org links have long paths and point is on them, they expand and make text difficult to read +(especially in org-tables). Need ability to toggle org-appear: on for editing links, off for reading. +Moved from inbox 2025-11-07. + +** TODO [#B] Optimize org-agenda performance using built-in profiler + +THE BOTTLENECK. Currently 30+ seconds, target < 5 seconds. +Use M-x profiler-start before Method 3 debug-profiling.el is built. + +** TODO [#B] Optimize org-capture target building performance + +15-20 seconds every time capturing a task (12+ times/day). +Major daily bottleneck - minutes lost waiting, plus context switching cost. + +** TODO Frequently used org-mode keybindings under C-; o + +Add quick access keybindings for common org commands (org-table, org-reveal, etc.) under C-; o. +Makes org-mode operations more frictionless. +Moved from inbox 2025-11-07. + +** TODO [#D] Fix EMMS keybinding inconsistency with other buffers + +EMMS keybindings conflict with standard buffer keybindings, causing mistypes. +Results in accidental destructive actions (clearing buffers), requires undo + context switch. +Violates Intuitive value - muscle memory should help, not hurt. ** DONE [#A] Remove network check from startup (saves 1+ seconds) CLOSED: [2025-10-31 Fri] @@ -58,85 +140,6 @@ CLOSED: [2025-10-31 Fri] Bound to C-; b D. Weekly need satisfied. -** DONE [#B] Fix go-ts-mode-map keybinding error (void-variable) -CLOSED: [2025-11-03 Sun] - -Error: "Debugger entered--Lisp error: (void-variable go-ts-mode-map)" -Location: modules/prog-go.el - trying to bind keys before mode loads. - -✅ Already fixed in commit 196b289 (Nov 2, 2025) -- Moved keybinding from `:bind (:map go-ts-mode-map ...)` to hook function -- Keybinding now set in `cj/go-mode-keybindings` called via `:hook` -- Function executes after mode loads, ensuring keymap exists -- Today's cleanup: Removed unused forward declarations (lines 34-35) - -Fix was: Wrap keybinding in hook function instead of :bind clause. -Result: No more void-variable error, keybinding works correctly. - -** TODO [#B] Fix org-noter (reading/annotation workflow currently "so painful") - -High priority - daily pain point. - -** DONE [#B] Fix video/audio recording module (use constantly, just broke) -CLOSED: [2025-11-03 Sun] - -Main issue: No way to select audio devices when multiple are available. -Plugging in external audio interface broke recording - only captured input, not output. - -✅ COMPLETED - Module now has robust device detection and selection. - -*** DONE [#A] Add diagnostic command cj/recording-list-devices -CLOSED: [2025-11-03 Sun] - -✅ Created `cj/recording-list-devices` command (C-; r d) -Shows ALL available PulseAudio/PipeWire sources with drivers and states. -Displays current configuration. -Helps debug why auto-detection fails. - -*** DONE [#A] Add device selection UI -CLOSED: [2025-11-03 Sun] - -✅ Created TWO selection workflows: -1. `cj/recording-select-devices` (C-; r s) - Full manual control - - Select mic and system audio separately - - Interactive completion with device states -2. `cj/recording-quick-setup-for-calls` (C-; r c) - Quick call setup - - Smart device pairing (groups mic + monitor by hardware) - - One selection for both mic and system audio - - Handles Bluetooth MAC normalization - -Devices cached in variables for future recordings. -Can switch devices without restarting Emacs. - -*** DONE [#B] Improve error messages -CLOSED: [2025-11-03 Sun] - -✅ Error messages now include: -- Guidance to run `cj/recording-select-devices` -- Clear indication when auto-detection fails -- User-friendly prompts for manual selection - -*** DONE [#B] Make device detection more flexible -CLOSED: [2025-11-03 Sun] - -✅ Implemented multi-level fallback system: -1. Auto-detect using pactl output parsing -2. Prompt user to select manually if auto-detect fails -3. Error with helpful guidance if user declines - -✅ Smart device grouping in `cj/recording-group-devices-by-hardware`: -- Handles USB, PCI (built-in), and Bluetooth devices -- Normalizes Bluetooth MAC addresses (colons ↔ underscores) -- Assigns friendly names (e.g., "Built-in Laptop Audio", "Bluetooth Headset") -- Filters incomplete devices (must have both mic and monitor) - -✅ Supports both PulseAudio and PipeWire (both use pactl). - -*** TODO [#B] Validate recording startup -Check process status after starting. -Parse ffmpeg output for errors. -Show actual ffmpeg command for debugging. - ** DONE [#A] Add comprehensive test coverage for video-audio-recording module CLOSED: [2025-11-03 Sun] @@ -190,99 +193,101 @@ Auto-compress after recording. Move to cloud sync directory. Generate transcript (once transcription workflow exists). -** TODO [#B] Fix mail attachment workflow (currently awkward) - -Daily workflow improvement. - -** DONE cj/flyspell-then-abbrev loses keybinding in scratch org-mode buffer +** DONE [#A] Delay in modeline lines and columns update CLOSED: [2025-11-08 Fri] -✅ Fixed keybinding issue in org-mode buffers. +✅ Fixed modeline position lag by replacing expensive function calls with cached values. **Problem:** -1. Autoload cookies were just comments and never executed -2. Org-mode was overriding C-' with org-cycle-agenda-files +- Line/column numbers lagged behind cursor movement +- Used `line-number-at-pos` which counts from buffer start on every update +- Performance degraded in large files **Solution:** -- Set keybindings directly when module loads (lines 239-240) -- Explicitly override org-mode's C-' after org loads (lines 244-245) -- Both C-' and C-c f now work correctly in all buffers including org-mode +- Replaced with built-in format specifiers `%l` and `%c` (modeline-config.el:81) +- These use cached values maintained by line-number-mode and column-number-mode +- Explicitly enabled line-number-mode in ui-config.el:53 +- Zero performance overhead - cached values update instantly -File modified: modules/flyspell-and-abbrev.el:235-251 +**Result:** +- Modeline position now updates instantly with cursor movement +- No lag, even in large files +- Maintains same "L:line C:col" format -** TODO [#A] Delay in modeline lines and columns update +** DONE [#B] Fix go-ts-mode-map keybinding error (void-variable) +CLOSED: [2025-11-03 Sun] -Performance issue where line/column numbers in modeline lag behind cursor movement. -Breaks flow and makes navigation feel sluggish. -Moved from inbox 2025-11-07. +Error: "Debugger entered--Lisp error: (void-variable go-ts-mode-map)" +Location: modules/prog-go.el - trying to bind keys before mode loads. -** TODO Frequently used org-mode keybindings under C-; o +✅ Already fixed in commit 196b289 (Nov 2, 2025) +- Moved keybinding from `:bind (:map go-ts-mode-map ...)` to hook function +- Keybinding now set in `cj/go-mode-keybindings` called via `:hook` +- Function executes after mode loads, ensuring keymap exists +- Today's cleanup: Removed unused forward declarations (lines 34-35) -Add quick access keybindings for common org commands (org-table, org-reveal, etc.) under C-; o. -Makes org-mode operations more frictionless. -Moved from inbox 2025-11-07. +Fix was: Wrap keybinding in hook function instead of :bind clause. +Result: No more void-variable error, keybinding works correctly. -** TODO [#B] Toggle org-appear on/off +** DONE [#B] Fix video/audio recording module (use constantly, just broke) +CLOSED: [2025-11-03 Sun] -When org links have long paths and point is on them, they expand and make text difficult to read -(especially in org-tables). Need ability to toggle org-appear: on for editing links, off for reading. -Moved from inbox 2025-11-07. +Main issue: No way to select audio devices when multiple are available. +Plugging in external audio interface broke recording - only captured input, not output. -** TODO [#A] Fix Google Calendar password prompts every 15 minutes +✅ COMPLETED - Module now has robust device detection and selection. -IRRITANT: gcal-sync triggers password prompts approximately every 15 minutes, -interrupting workflow and breaking focus. This defeats the purpose of having -passphrase caching configured. +*** DONE [#A] Add diagnostic command cj/recording-list-devices +CLOSED: [2025-11-03 Sun] -**Current Setup:** -- GPG agent configured with 400-day cache (gpg-agent.conf): - - default-cache-ttl 34560000 - - max-cache-ttl 34560000 - - allow-loopback-pinentry enabled -- Plstore caching enabled (auth-config.el:54): - - plstore-cache-passphrase-for-symmetric-encryption t - - plstore-encrypt-to nil (symmetric encryption) -- Auth-source cache: 24 hours (auth-config.el:31) -- Auto-sync interval: 30 minutes (org-gcal-config.el:50) +✅ Created `cj/recording-list-devices` command (C-; r d) +Shows ALL available PulseAudio/PipeWire sources with drivers and states. +Displays current configuration. +Helps debug why auto-detection fails. -**Problem:** -Despite proper GPG agent caching, oauth2-auto.plist prompts for passphrase -every ~15 minutes during gcal-sync operations. This suggests: -1. plstore may not be using GPG agent cache properly for symmetric encryption -2. oauth2-auto token refresh might be bypassing cache -3. EPinentry mode may need explicit configuration (currently commented out) +*** DONE [#A] Add device selection UI +CLOSED: [2025-11-03 Sun] -**Goal:** -Passphrase should be entered ONCE per Emacs session, then cached until Emacs -closes. No interruptions during normal work. +✅ Created TWO selection workflows: +1. `cj/recording-select-devices` (C-; r s) - Full manual control + - Select mic and system audio separately + - Interactive completion with device states +2. `cj/recording-quick-setup-for-calls` (C-; r c) - Quick call setup + - Smart device pairing (groups mic + monitor by hardware) + - One selection for both mic and system audio + - Handles Bluetooth MAC normalization -**Investigation Paths:** -1. Check if oauth2-auto respects plstore passphrase caching -2. Investigate plstore symmetric encryption cache behavior with GPG agent -3. Test enabling epa-pinentry-mode 'loopback (auth-config.el:42) -4. Check oauth2-auto token refresh cycle vs password prompt timing -5. Consider oauth2-auto configuration options for token persistence -6. Review org-gcal or oauth2-auto issues for similar problems +Devices cached in variables for future recordings. +Can switch devices without restarting Emacs. -**Files:** -- modules/auth-config.el (plstore and GPG configuration) -- modules/org-gcal-config.el (org-gcal and oauth2-auto setup) -- ~/.gnupg/gpg-agent.conf (GPG agent cache settings) -- oauth2-auto.plist (encrypted OAuth tokens - prompts every access?) +*** DONE [#B] Improve error messages +CLOSED: [2025-11-03 Sun] -**Related:** -This violates the "Frictionless" value - interruptions every 15 minutes during -calendar sync breaks concentration and workflow momentum. +✅ Error messages now include: +- Guidance to run `cj/recording-select-devices` +- Clear indication when auto-detection fails +- User-friendly prompts for manual selection -** TODO [#B] Optimize org-agenda performance using built-in profiler +*** DONE [#B] Make device detection more flexible +CLOSED: [2025-11-03 Sun] -THE BOTTLENECK. Currently 30+ seconds, target < 5 seconds. -Use M-x profiler-start before Method 3 debug-profiling.el is built. +✅ Implemented multi-level fallback system: +1. Auto-detect using pactl output parsing +2. Prompt user to select manually if auto-detect fails +3. Error with helpful guidance if user declines -** TODO [#B] Optimize org-capture target building performance +✅ Smart device grouping in `cj/recording-group-devices-by-hardware`: +- Handles USB, PCI (built-in), and Bluetooth devices +- Normalizes Bluetooth MAC addresses (colons ↔ underscores) +- Assigns friendly names (e.g., "Built-in Laptop Audio", "Bluetooth Headset") +- Filters incomplete devices (must have both mic and monitor) -15-20 seconds every time capturing a task (12+ times/day). -Major daily bottleneck - minutes lost waiting, plus context switching cost. +✅ Supports both PulseAudio and PipeWire (both use pactl). + +*** TODO [#B] Validate recording startup +Check process status after starting. +Parse ffmpeg output for errors. +Show actual ffmpeg command for debugging. ** DONE [#C] Fix grammar checker performance (currently disabled) CLOSED: [2025-11-04 Mon] @@ -305,11 +310,21 @@ LanguageTool catches: Workflow: Open org/text/markdown file → press C-; ? → see errors in *Flycheck errors* buffer -** TODO [#D] Fix EMMS keybinding inconsistency with other buffers +** DONE cj/flyspell-then-abbrev loses keybinding in scratch org-mode buffer +CLOSED: [2025-11-08 Fri] -EMMS keybindings conflict with standard buffer keybindings, causing mistypes. -Results in accidental destructive actions (clearing buffers), requires undo + context switch. -Violates Intuitive value - muscle memory should help, not hurt. +✅ Fixed keybinding issue in org-mode buffers. + +**Problem:** +1. Autoload cookies were just comments and never executed +2. Org-mode was overriding C-' with org-cycle-agenda-files + +**Solution:** +- Set keybindings directly when module loads (lines 239-240) +- Explicitly override org-mode's C-' after org loads (lines 244-245) +- Both C-' and C-c f now work correctly in all buffers including org-mode + +File modified: modules/flyspell-and-abbrev.el:235-251 * Method 2: Stop Problems Before They Appear [3/5] @@ -375,7 +390,7 @@ CLOSED: [2025-11-03 Sun] Already using prescient with vertico. Extend to Corfu after migration. -* Method 3: Make *Fixing* Emacs Frictionless [1/4] +* Method 3: Make *Fixing* Emacs Frictionless [1/5] ** TODO [#B] Build debug-profiling.el module @@ -484,25 +499,4 @@ Review this inbox, cancel stale items, keep < 20 active. Track in calendar. Can't research next thing until current thing is implemented. * Emacs Config Inbox -** DONE mu4e issue No sent emails since August? -CLOSED: [2025-11-08 Fri] - -✅ Fixed cmail (c@cjennings.net) sent folder not syncing since May. - -**Root Cause:** -- mbsync was using `Patterns *` which processes folders alphabetically -- Errors in "Drafts" and "All Mail" folders caused mbsync to abort early -- Sent folder (alphabetically after Drafts) was never reached - -**The Fix:** -- Changed ~/.mbsyncrc to use explicit channels like Gmail configuration -- Now syncs 6 folders independently: INBOX, Sent, Trash, Archive, Starred, Spam -- Omitted problematic Drafts and "All Mail" folders -- Errors in one folder no longer prevent other folders from syncing - -**Result:** -- Synced 136 missing sent messages (May through November 7) -- Gmail account was unaffected (always worked correctly) -- Automatic syncs now include Sent folder - -File modified: ~/.mbsyncrc:80-143 +* Emacs Config Resolved |
