From 5ee760fbe82382cabaf1437050d1d8eec8e61253 Mon Sep 17 00:00:00 2001 From: Thierry Pouplier Date: Thu, 30 Apr 2026 10:25:25 -0400 Subject: [PATCH] Doom emacs config progress dump --- doom/.config/doom/README.org | 439 ++++++++++++++++++++++++----------- doom/.config/doom/config.el | 407 +++++++++++++++++++++++--------- 2 files changed, 602 insertions(+), 244 deletions(-) diff --git a/doom/.config/doom/README.org b/doom/.config/doom/README.org index d601248..584784a 100644 --- a/doom/.config/doom/README.org +++ b/doom/.config/doom/README.org @@ -156,7 +156,6 @@ My current workflow consist in having the 3-5 files I work on open in vertical s #+end_src * Org Mode - If you use ~org~ and don't want your org files in the default location below, change ~org-directory~. It must be set before org loads! @@ -164,12 +163,11 @@ change ~org-directory~. It must be set before org loads! #+begin_src emacs-lisp (setq - org-directory "~/ExoKortex/" - +org-capture-todo-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" - +org-capture-notes-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" - +org-capture-journal-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" - +org-capture-emails-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-todo-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" + +org-capture-notes-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" + +org-capture-journal-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" + +org-capture-emails-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" ;; +org-capture-central-project-todo-file '"refile.org" ;; +org-capture-project-todo-file '"refile.org" org-agenda-files (directory-files-recursively "~/ExoKortex/" "\\.org$") @@ -366,15 +364,34 @@ change ~org-directory~. It must be set before org loads! ("p" . "Personal commands") ("wg" "GTD view" ( - (agenda "" ((org-agenda-span 'week))) - (todo "STRT" ((org-agenda-overriding-header gtd/started-head))) - (todo "NEXT" ((org-agenda-overriding-header gtd/next-action-head))) - (todo "WAIT" ((org-agenda-overriding-header gtd/waiting-head))) + (agenda "" ((org-agenda-span 'week) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "STRT" ((org-agenda-overriding-header gtd/started-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "NEXT" ((org-agenda-overriding-header gtd/next-action-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "WAIT" ((org-agenda-overriding-header gtd/waiting-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) ;; (todo "DONE" ((org-agenda-overriding-header gtd/complete-head))) (stuck "" ((org-stuck-projects '("TODO=\"PROJ\"" ("NEXT" "STRT") nil "")) - (org-agenda-overriding-header gtd/project-head))) - (todo "IDEA" ((org-agenda-overriding-header gtd/someday-head))) + (org-agenda-overriding-header gtd/project-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "IDEA" ((org-agenda-overriding-header gtd/someday-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) ) + ((org-agenda-tag-filter-preset '("+work"))) ) ("pg" "GTD view" @@ -476,10 +493,10 @@ change ~org-directory~. It must be set before org loads! ))) ((org-agenda-tag-filter-preset '("+BA_ON_SITE"))) + ) ) ) ) -) #+end_src ** Rust @@ -492,20 +509,64 @@ change ~org-directory~. It must be set before org loads! #+end_src ** Latex - #+begin_src emacs-lisp (after! org - (setq + (setq org-format-latex-options (plist-put org-format-latex-options :scale 2.0)) - ;; LaTeX math preview - org-format-latex-options '(:foreground default :background default :scale 2 :html-foreground "Black" - :html-background "Transparent" :html-scale 1.0 - :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")) - ) + ;; 1. Use XeLaTeX to generate the intermediate file for dvisvgm + ;; This handles UTF-8 (like long dashes) which standard 'latex' crashes on. + (setq org-preview-latex-process-alist + '((dvisvgm + :programs ("xelatex" "dvisvgm") + :description "xdv to svg" + :message "rendering %f via xelatex..." + :image-input-type "xdv" + :image-output-type "svg" + :image-size-adjust (1.0 . 1.0) + :latex-compiler ("xelatex -interaction nonstopmode -no-pdf -output-directory %o %f") + :image-converter ("dvisvgm %f --no-fonts --exact-bbox --scale=%S --output=%O")))) + + (setq org-preview-latex-default-process 'dvisvgm) + + (defun gortium/org-latex-preview-all () + "Render all LaTeX fragments in the buffer." + (interactive) + (when (derived-mode-p 'org-mode) + (org-latex-preview '(16)))) + + ;; 1.5s delay to ensure Doom font-colors load first + ;; (add-hook 'org-mode-hook + ;; (lambda () + ;; (run-with-idle-timer 1.5 nil #'gortium/org-latex-preview-all))) ) (after! ox-latex - (setq org-latex-compiler "xelatex")) + (setq org-latex-src-block-backend 'listings) + (add-to-list 'org-latex-packages-alist '("" "listings") t) + (add-to-list 'org-latex-packages-alist '("" "xcolor") t) + (add-to-list 'org-latex-listings-langs '(text "")) + (setq org-latex-listings-options + '(("breaklines" "true") + ("basicstyle" "\\small\\ttfamily") + ("frame" "single") + ("backgroundcolor" "\\color{gray!10}")))) +#+end_src + +** Fragtog +#+begin_src emacs-lisp +(use-package! org-fragtog + :hook (org-mode . org-fragtog-mode)) + +(defun gortium/org-latex-refresh-on-zoom (&rest _) + (when (derived-mode-p 'org-mode) + (let ((new-scale (+ 2 (* 0.5 (if (boundp 'text-scale-mode-amount) text-scale-mode-amount 0))))) + (setq org-format-latex-options + (plist-put org-format-latex-options :scale new-scale)) + (org-clear-latex-preview) + (gortium/org-latex-preview-all)))) + +(advice-add 'text-scale-increase :after #'gortium/org-latex-refresh-on-zoom) +(advice-add 'text-scale-decrease :after #'gortium/org-latex-refresh-on-zoom) #+end_src ** Org Capture @@ -555,6 +616,7 @@ org-modern instead #+end_src * Markdown + #+begin_src emacs-lisp (after! grip-mode (setq grip-binary-path "/home/tpouplier/.local/bin/grip") @@ -599,6 +661,7 @@ org-modern instead #+end_src * PlantUML + #+begin_src emacs-lisp ;; Enable plantuml-mode for PlantUML files (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)) @@ -623,6 +686,7 @@ fc --> UC3 [[/tmp/babel-pg1Nry/plantuml-SHtP1g.png][/tmp/babel-pg1Nry/plantuml-SHtP1g.png]] * Elgantt + Broken for now.. #+begin_src emacs-lisp :tangle no (require 'cl-lib) @@ -842,12 +906,11 @@ Broken for now.. : elgantt-open-current-org-file * Org Roam - ** Static Path #+begin_src emacs-lisp (setq org-roam-directory (file-truename "~/ExoKortex/") - org-roam-db-location (file-truename "~/ExoKortex/2-Areas/IT/Roam/org-roam.db") + org-roam-db-location (file-truename "~/ExoKortex/1-Soma/2-Areas/IT/Roam/org-roam.db") org-attach-id-dir "assets/" org-roam-dailies-directory "~/ExoKortex/2-Areas/Meta-Planning/Org/Journal/Daily") #+end_src @@ -1056,7 +1119,6 @@ Handles org-clock and context link capture for tasks." #+end_src * IDE - ** Dap Mode #+begin_src emacs-lisp @@ -1179,14 +1241,14 @@ Now I can write x) (spellchecking) (after! ispell (setq ispell-program-name "hunspell" ispell-dictionary "en_CA,fr_CA" - ispell-personal-dictionary "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/perso.dic") + ispell-personal-dictionary "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries/perso.dic") ;; Set the dictionary path environment variable - (setenv "DICPATH" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries") + (setenv "DICPATH" "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries") (setq ispell-hunspell-dict-paths-alist - '(("fr_CA" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/fr_CA.aff") - ("en_CA" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/en_CA.aff"))) + '(("fr_CA" "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries/fr_CA.aff") + ("en_CA" "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries/en_CA.aff"))) (setq ispell-hunspell-dictionary-alist '(("fr_CA" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "fr_CA") nil utf-8) @@ -1348,33 +1410,6 @@ I'm a formatting nazi now xD ) #+end_src -** Fragtog - -#+begin_src emacs-lisp -;; Hook fragtog to org-mode to have editable latex preview -(use-package! org-fragtog - :hook (org-mode . org-fragtog-mode)) - -;; Rescale latex preview when changing font size -(defun gortium/org-latex-refresh-on-zoom (&rest _) - "Dynamically rescale and refresh all LaTeX previews after text zoom." - (when (derived-mode-p 'org-mode) - ;; Dynamically update scale relative to zoom level - (setq org-format-latex-options - (plist-put org-format-latex-options - :scale (+ 2 (* 0.5 text-scale-mode-amount)) - )) - ;; Clear all previews - (org-clear-latex-preview) - ;; Re-render all previews - (org-latex-preview '(16)) - )) - -(advice-add 'text-scale-increase :after #'gortium/org-latex-refresh-on-zoom) -(advice-add 'text-scale-decrease :after #'gortium/org-latex-refresh-on-zoom) -(advice-add 'text-scale-set :after #'gortium/org-latex-refresh-on-zoom) -#+end_src - ** Age Allow me to edit encryted age file directly in emacs buffer. @@ -1449,11 +1484,11 @@ Allow retrieval of password from age file formatted like passwordstore ;; rebind function value for pass to passage (fset #'pass (lambda () (interactive) (passage))) (setq age-program "rage") - (setq auth-source-passage-filename (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets")) + (setq auth-source-passage-filename (expand-file-name "~/ExoKortex/4-Automata/dotfiles/secrets")) (setenv "PASSAGE_IDENTITIES_FILE" (expand-file-name age-default-identity)) (setenv "PASSAGE_RECIPIENTS_FILE" (expand-file-name age-default-recipient)) (setenv "PASSAGE_AGE" "rage") - (setenv "PASSAGE_DIR" (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets")) + (setenv "PASSAGE_DIR" (expand-file-name "~/ExoKortex/4-Automata/dotfiles/secrets")) ) #+end_src ** Diff HL Mode @@ -1464,7 +1499,6 @@ Show git changes in the sidebar #+end_src * Terminal - ** +EEE+ #+begin_src emacs-lisp :tangle no @@ -1519,7 +1553,8 @@ The best LLM integration I found. Works with MCP server for more functionnalitie deepseek/deepseek-chat-v3-0324:free meta-llama/llama-4-maverick:free mistralai/devstral-2512:free - qwen/qwen3-coder:free)) + qwen/qwen3-coder:free + nvidia/nemotron-3-nano-30b-a3b:free)) (gptel-make-gemini "Gemini" :key (auth-source-passage-get 'secret "gemini") :stream t @@ -1961,7 +1996,6 @@ Useful for the user, but also when you have someone over your shoulder trying to #+end_src * Email - ** Org-Msg #+begin_src emacs-lisp @@ -1971,14 +2005,54 @@ Useful for the user, but also when you have someone over your shoulder trying to (setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t" org-msg-startup "hidestars indent inlineimages" org-msg-greeting-fmt "\nBonjour%s,\n\n" - ;; org-msg-recipient-names '(("jeremy.compostella@gmail.com" . "Jérémy")) org-msg-greeting-name-limit 1 org-msg-default-alternatives '((new . (text html)) (reply-to-html . (text html)) (reply-to-text . (text))) - org-msg-convert-citation t - org-msg-signature " + org-msg-convert-citation t) + ) +#+end_src +** Mu4e + +#+begin_src emacs-lisp +(after! mu4e + (setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter) + + (setq mu4e-contexts + (list + ;; --- CONTEXT: TDNDE --- + (make-mu4e-context + :name "TDNDE" + :match-func (lambda (msg) + (when msg + (string-prefix-p "/TDNDE/" (mu4e-message-field msg :maildir)))) + :vars + '((user-mail-address . "tpouplier@tdnde.com") + (user-full-name . "Thierry Pouplier") + + ;; Folders + (mu4e-sent-folder . "/TDNDE/Sent") + (mu4e-drafts-folder . "/TDNDE/Drafts") + (mu4e-trash-folder . "/TDNDE/Trash") + (mu4e-refile-folder . "/TDNDE/Archive") + + ;; Shortcuts + (mu4e-maildir-shortcuts . (("/TDNDE/Inbox" . ?i) + ("/TDNDE/Sent" . ?s) + ("/TDNDE/Drafts" . ?d) + ("/TDNDE/Trash" . ?t) + ("/TDNDE/Archive" . ?a))) + + ;; SMTP + (smtpmail-smtp-user . "tpouplier@tdnde.com") + (smtpmail-stream-type . ssl) + (smtpmail-smtp-server . "smtp.hostinger.com") + (smtpmail-smtp-service . 465) + (mu4e-sent-messages-behavior . sent) + + ;; Context-specific signature + (org-msg-signature . " Cdlt, ,#+begin_signature @@ -1992,97 +2066,66 @@ Tél. : +1 (450) 667-1884 \\\\ Cell. : +1 (514) 887-1674 \\\\ tpouplier@tdnde.com \\\\ www.tdnde.com \\\\ -,#+end_signature") - ) -#+end_src +,#+end_signature"))) -** Mu4e - -#+begin_src emacs-lisp -(after! mu4e - (setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter) - (setq mu4e-contexts - (list + ;; --- CONTEXT: Perso --- (make-mu4e-context - :name "TDNDE" - :match-func - (lambda (msg) - (when msg - (string-prefix-p - "/TDNDE/" - (mu4e-message-field msg :maildir)))) - :vars - '((user-mail-address . "tpouplier@tdnde.com") - (user-full-name . "Thierry Pouplier") + :name "Perso" + :match-func (lambda (msg) + (when msg + (string-prefix-p "/Perso/" (mu4e-message-field msg :maildir)))) + :vars `((user-mail-address . "thierrypouplier@gmail.com") + (user-full-name . "Thierry Pouplier") + (mu4e-sent-folder . "/Perso/Sent") + (mu4e-drafts-folder . "/Perso/Drafts") + (mu4e-trash-folder . "/Perso/Trash") + (mu4e-refile-folder . "/Perso/Archive") + + (mu4e-maildir-shortcuts . (("/Perso/Inbox" . ?i) + ("/Perso/Sent" . ?s) + ("/Perso/Trash" . ?t) + ("/Perso/Drafts" . ?d) + ("/Perso/Archive" . ?a))) + + (smtpmail-smtp-user . "thierrypouplier@gmail.com") + (smtpmail-smtp-server . "smtp.gmail.com") + (smtpmail-smtp-service . 587) + (smtpmail-stream-type . starttls) + (mu4e-sent-messages-behavior . sent) + (org-msg-signature . " +Cordialement, - ;; Folders - (mu4e-sent-folder . "/TDNDE/Sent") - (mu4e-drafts-folder . "/TDNDE/Drafts") - (mu4e-trash-folder . "/TDNDE/Trash") - (mu4e-refile-folder . "/TDNDE/Archive") - - ;; Shortcuts - (mu4e-maildir-shortcuts - . - (("/TDNDE/Inbox" . ?i) - ("/TDNDE/Sent" . ?s) - ("/TDNDE/Drafts" . ?d) - ("/TDNDE/Trash" . ?t) - ("/TDNDE/Archive" . ?a))) - - ;; Bookmarks - (mu4e-bookmarks - . - ((:name "Airbus-6825" - :query "maildir:/TDNDE/* AND label:proj:6825 AND label:client:airbus" - :key ?A) - (:name "Bombardier-3154" - :query "maildir:/TDNDE/* AND label:proj:3154 AND label:client:bombardier" - :key ?B) - (:name "Daher-5304" - :query "maildir:/TDNDE/* AND label:proj:5304 AND label:client:daher" - :key ?D) - (:name "TDNDE" - :query "maildir:/TDNDE/* AND label:tdnde" - :key ?T))) - - ;; SMTP - (smtpmail-smtp-user . "tpouplier@tdnde.com") - (smtpmail-stream-type . ssl) - (smtpmail-smtp-server . "smtp.hostinger.com") - (smtpmail-smtp-service . 465))) - ;; Other context here - ;; (make-mu4e-context ...) - ) - ) +,#+begin_signature +-- +,*Thierry Pouplier* \\ +(514) 887-1674 | thierrypouplier@gmail.com +,#+end_signature"))))) ;; End of mu4e-contexts list (setq mu4e-context-policy 'pick-first mu4e-compose-context-policy 'ask) (require 'mu4e-icalendar) (mu4e-icalendar-setup) - (setq mu4e-icalendar-diary-file nil) (setq gnus-icalendar-org-capture-file +org-capture-journal-file gnus-icalendar-org-capture-headline '("Journal")) (gnus-icalendar-org-setup) - ) +;; Separate after! blocks for clarity (after! mu4e (setq mu4e-modeline-support nil)) (after! mu4e ;; restore label key - (evil-define-key 'normal mu4e-headers-mode-map"l" #'mu4e-headers-mark-for-label) + (evil-define-key 'normal mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) (evil-define-key 'visual mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) (evil-define-key 'emacs mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) (evil-define-key 'normal mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) (evil-define-key 'visual mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) - (evil-define-key 'emacs mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) - ) + (evil-define-key 'emacs mu4e-view-mode-map "l" #'mu4e-view-mark-for-label))) #+end_src * IRC @@ -2248,7 +2291,7 @@ If FILE is nil, refile in the current file." (add-hook 'ediff-prepare-buffer-hook 'org-ediff-prepare-buffer) #+end_src -* Custom function for reschedulling +* Custom function for scheduling #+begin_src emacs-lisp ;;; ============================================================ @@ -2697,6 +2740,143 @@ and ensure the standard property drawer exists without overwriting existing data #+RESULTS: : gortium/add-ids-to-subtree +* Custom function for maintenance +#+begin_src emacs-lisp +(after! org + ;; --------------------------------------------------------- + ;; 1. THE REUSABLE ENGINE (Domain Agnostic) + ;; --------------------------------------------------------- + (defun my/org-metric-sync-engine (&key prop-name value-current prop-last-done prop-interval target-tag log-label) + "Core logic to sync parent metrics to sub-tasks and log changes. + PROP-NAME: The property triggering the change (e.g., LAST_KM). + VALUE-CURRENT: The new value being set. + PROP-LAST-DONE: Sub-task property for 'baseline' (e.g., LAST_DONE_KM). + PROP-INTERVAL: Sub-task property for 'frequency' (e.g., INTERVAL_KM). + TARGET-TAG: Filter for specific sub-tasks (e.g., SERVICE). + LOG-LABEL: The prefix used in the Logbook entry." + (save-excursion + ;; A. Log the update to the Logbook with a timestamp + (when log-label + (org-add-log-setup 'note (format "%s: %s" log-label value-current) nil 'findpos)) + + ;; B. Narrow scope to current header and scan sub-tasks + (org-narrow-to-subtree) + (org-map-entries + (lambda () + (let* ((last-done (string-to-number (or (org-entry-get nil prop-last-done) "0"))) + (interval (string-to-number (or (org-entry-get nil prop-interval) "0"))) + (current (string-to-number value-current))) + ;; Only process tasks that have an interval set + (when (> interval 0) + (if (>= current (+ last-done interval)) + (org-todo "TODO") + (org-todo "HOLD"))))) + target-tag) + (widen))) + + (defun my/org-metric-prompt-engine (&key state tag prop-parent-metric prop-task-done label-prompt) + "Core logic to prompt for values when a task is marked DONE. + STATE: The new TODO state (e.g., DONE). + TAG: The tag identifying relevant tasks. + PROP-PARENT-METRIC: The property on the parent to update (e.g., LAST_KM). + PROP-TASK-DONE: The property on the task to update (e.g., LAST_DONE_KM). + LABEL-PROMPT: The text shown in the minibuffer." + (when (string= state "DONE") + (let ((tags (org-get-tags))) + (when (member tag tags) + (save-excursion + ;; Inherit parent metric as the default value for the prompt + (let* ((current-val (org-entry-get nil prop-parent-metric t)) + (input-val (read-string (format "%s (default %s): " label-prompt (or current-val "0")) + nil nil current-val))) + ;; 1. Update the task property + (org-set-property prop-task-done input-val) + ;; 2. Update the parent property (triggers the sync engine) + (save-excursion + (when (org-up-heading-safe) + (org-set-property prop-parent-metric input-val))) + (message "Synchronized %s to %s" label-prompt input-val))))))) + + ;; --------------------------------------------------------- + ;; 2. THE DISPATCHERS (The Switchboard) + ;; --------------------------------------------------------- + (defun my/org-property-change-handler (prop value) + "Routes property changes to the correct sync parameters." + (cond + ;; Case: F-150 Odometer + ((string= prop "LAST_KM") + (my/org-metric-sync-engine + :prop-name prop :value-current value :prop-last-done "LAST_DONE_KM" + :prop-interval "INTERVAL_KM" :target-tag "SERVICE" :log-label "Odometer Update")))) + + ;; Case: Equipment/Industrial Hours + ;; ((string= prop "LAST_HOURS") + ;; (my/org-metric-sync-engine + ;; :prop-name prop :value-current value :prop-last-done "LAST_DONE_HOURS" + ;; :prop-interval "INTERVAL_HOURS" :target-tag "MAINTENANCE" :log-label "Usage Hours Logged")))) + +(defun my/org-todo-state-handler () + "Safely routes DONE completions, catching any argument errors." + (interactive) + (condition-case err + (when (string= org-state "DONE") + (my/org-metric-prompt-engine + :state "DONE" + :tag "SERVICE" + :prop-parent-metric "LAST_KM" + :prop-task-done "LAST_DONE_KM" + :label-prompt "Current Odometer")) + (error (message "Handler suppressed error: %s" (error-message-string err))))) + ) +;; (my/org-metric-prompt-engine +;; :state org-state :tag "MAINTENANCE" :prop-parent-metric "LAST_HOURS" +;; :prop-task-done "LAST_DONE_HOURS" :label-prompt "Equipment Hours")) + +;; --------------------------------------------------------- +;; 3. THE HOOKS +;; --------------------------------------------------------- +(add-hook 'org-after-prop-change-hook #'my/org-property-change-handler) +(add-hook 'org-after-todo-state-change-hook #'my/org-todo-state-handler) +#+end_src + +* Custom for rescheduling task later +#+begin_src emacs-lisp +(defun my/org-smart-advance-date () + "Jump to next repeater occurrence or add 1 day. +Suppresses all logging, notes, and custom odometer hooks." + (interactive) + (let ((org-log-done nil) + (org-log-repeat nil) + (org-todo-repeat-to-state "LOOP") + (org-after-todo-state-change-hook nil) + (org-treat-insert-todo-log-note nil) + (inhibit-modification-hooks t)) + (let ((is-agenda (derived-mode-p 'org-agenda-mode))) + (cond + ;; Case 1: Task has a repeater (teleport to next date) + ((if is-agenda + (org-agenda-with-point-at-orig-entry nil (org-get-repeat)) + (org-get-repeat)) + + ;; This surgical strike prevents the Logbook entry entirely + (cl-letf (((symbol-function 'org-add-log-setup) (lambda (&rest _args) nil))) + (if is-agenda (org-agenda-todo "DONE") (org-todo "DONE"))) + + (message "Silently jumped to next occurrence.")) + + ;; Case 2: No repeater, fallback to standard +1 day shift + (t + (if is-agenda + (org-agenda-date-later 1) + (org-timestamp-change 1 'day)) + (message "No repeater found: Added 1 day.")))))) + +(map! :after org + (:map org-mode-map + :localleader "L" #'my/org-smart-advance-date) + (:map org-agenda-mode-map + :localleader "L" #'my/org-smart-advance-date)) +#+end_src * Custom function Open link in other frame @@ -3022,4 +3202,3 @@ Need chrome... :( :localleader :desc "Edit Edna rules" "E" #'org-edna-edit)) #+end_src - diff --git a/doom/.config/doom/config.el b/doom/.config/doom/config.el index 0a5b590..ee75e4d 100644 --- a/doom/.config/doom/config.el +++ b/doom/.config/doom/config.el @@ -50,12 +50,11 @@ (defalias 'getf #'cl-getf)) (setq - org-directory "~/ExoKortex/" - +org-capture-todo-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" - +org-capture-notes-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" - +org-capture-journal-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" - +org-capture-emails-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-todo-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" + +org-capture-notes-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" + +org-capture-journal-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" + +org-capture-emails-file "~/ExoKortex/3-Telos/5-Kernel/inbox.org" ;; +org-capture-central-project-todo-file '"refile.org" ;; +org-capture-project-todo-file '"refile.org" org-agenda-files (directory-files-recursively "~/ExoKortex/" "\\.org$") @@ -236,15 +235,34 @@ ("p" . "Personal commands") ("wg" "GTD view" ( - (agenda "" ((org-agenda-span 'week))) - (todo "STRT" ((org-agenda-overriding-header gtd/started-head))) - (todo "NEXT" ((org-agenda-overriding-header gtd/next-action-head))) - (todo "WAIT" ((org-agenda-overriding-header gtd/waiting-head))) + (agenda "" ((org-agenda-span 'week) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "STRT" ((org-agenda-overriding-header gtd/started-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "NEXT" ((org-agenda-overriding-header gtd/next-action-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "WAIT" ((org-agenda-overriding-header gtd/waiting-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) ;; (todo "DONE" ((org-agenda-overriding-header gtd/complete-head))) (stuck "" ((org-stuck-projects '("TODO=\"PROJ\"" ("NEXT" "STRT") nil "")) - (org-agenda-overriding-header gtd/project-head))) - (todo "IDEA" ((org-agenda-overriding-header gtd/someday-head))) + (org-agenda-overriding-header gtd/project-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) + (todo "IDEA" ((org-agenda-overriding-header gtd/someday-head) + (org-agenda-prefix-format " %12t") ;; reserve time space + (org-agenda-todo-keyword-format " %-5s") ;; fixed-width TODO + )) ) + ((org-agenda-tag-filter-preset '("+work"))) ) ("pg" "GTD view" @@ -346,10 +364,10 @@ ))) ((org-agenda-tag-filter-preset '("+BA_ON_SITE"))) + ) ) ) ) -) (after! org ;; Rust code block setting @@ -357,17 +375,59 @@ ) (after! org - (setq + (setq org-format-latex-options (plist-put org-format-latex-options :scale 2.0)) - ;; LaTeX math preview - org-format-latex-options '(:foreground default :background default :scale 2 :html-foreground "Black" - :html-background "Transparent" :html-scale 1.0 - :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")) - ) + ;; 1. Use XeLaTeX to generate the intermediate file for dvisvgm + ;; This handles UTF-8 (like long dashes) which standard 'latex' crashes on. + (setq org-preview-latex-process-alist + '((dvisvgm + :programs ("xelatex" "dvisvgm") + :description "xdv to svg" + :message "rendering %f via xelatex..." + :image-input-type "xdv" + :image-output-type "svg" + :image-size-adjust (1.0 . 1.0) + :latex-compiler ("xelatex -interaction nonstopmode -no-pdf -output-directory %o %f") + :image-converter ("dvisvgm %f --no-fonts --exact-bbox --scale=%S --output=%O")))) + + (setq org-preview-latex-default-process 'dvisvgm) + + (defun gortium/org-latex-preview-all () + "Render all LaTeX fragments in the buffer." + (interactive) + (when (derived-mode-p 'org-mode) + (org-latex-preview '(16)))) + + ;; 1.5s delay to ensure Doom font-colors load first + ;; (add-hook 'org-mode-hook + ;; (lambda () + ;; (run-with-idle-timer 1.5 nil #'gortium/org-latex-preview-all))) ) (after! ox-latex - (setq org-latex-compiler "xelatex")) + (setq org-latex-src-block-backend 'listings) + (add-to-list 'org-latex-packages-alist '("" "listings") t) + (add-to-list 'org-latex-packages-alist '("" "xcolor") t) + (add-to-list 'org-latex-listings-langs '(text "")) + (setq org-latex-listings-options + '(("breaklines" "true") + ("basicstyle" "\\small\\ttfamily") + ("frame" "single") + ("backgroundcolor" "\\color{gray!10}")))) + +(use-package! org-fragtog + :hook (org-mode . org-fragtog-mode)) + +(defun gortium/org-latex-refresh-on-zoom (&rest _) + (when (derived-mode-p 'org-mode) + (let ((new-scale (+ 2 (* 0.5 (if (boundp 'text-scale-mode-amount) text-scale-mode-amount 0))))) + (setq org-format-latex-options + (plist-put org-format-latex-options :scale new-scale)) + (org-clear-latex-preview) + (gortium/org-latex-preview-all)))) + +(advice-add 'text-scale-increase :after #'gortium/org-latex-refresh-on-zoom) +(advice-add 'text-scale-decrease :after #'gortium/org-latex-refresh-on-zoom) (after! org (setq @@ -446,7 +506,7 @@ (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)) (setq org-roam-directory (file-truename "~/ExoKortex/") - org-roam-db-location (file-truename "~/ExoKortex/2-Areas/IT/Roam/org-roam.db") + org-roam-db-location (file-truename "~/ExoKortex/1-Soma/2-Areas/IT/Roam/org-roam.db") org-attach-id-dir "assets/" org-roam-dailies-directory "~/ExoKortex/2-Areas/Meta-Planning/Org/Journal/Daily") @@ -675,14 +735,14 @@ Handles org-clock and context link capture for tasks." (after! ispell (setq ispell-program-name "hunspell" ispell-dictionary "en_CA,fr_CA" - ispell-personal-dictionary "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/perso.dic") + ispell-personal-dictionary "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries/perso.dic") ;; Set the dictionary path environment variable - (setenv "DICPATH" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries") + (setenv "DICPATH" "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries") (setq ispell-hunspell-dict-paths-alist - '(("fr_CA" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/fr_CA.aff") - ("en_CA" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/en_CA.aff"))) + '(("fr_CA" "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries/fr_CA.aff") + ("en_CA" "/home/tpouplier/ExoKortex/1-Soma/2-Areas/IT/Dictionaries/en_CA.aff"))) (setq ispell-hunspell-dictionary-alist '(("fr_CA" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "fr_CA") nil utf-8) @@ -805,29 +865,6 @@ Handles org-clock and context link capture for tasks." (add-to-list 'auto-mode-alist '("\\.hledger\\'" . hledger-mode)) ) -;; Hook fragtog to org-mode to have editable latex preview -(use-package! org-fragtog - :hook (org-mode . org-fragtog-mode)) - -;; Rescale latex preview when changing font size -(defun gortium/org-latex-refresh-on-zoom (&rest _) - "Dynamically rescale and refresh all LaTeX previews after text zoom." - (when (derived-mode-p 'org-mode) - ;; Dynamically update scale relative to zoom level - (setq org-format-latex-options - (plist-put org-format-latex-options - :scale (+ 2 (* 0.5 text-scale-mode-amount)) - )) - ;; Clear all previews - (org-clear-latex-preview) - ;; Re-render all previews - (org-latex-preview '(16)) - )) - -(advice-add 'text-scale-increase :after #'gortium/org-latex-refresh-on-zoom) -(advice-add 'text-scale-decrease :after #'gortium/org-latex-refresh-on-zoom) -(advice-add 'text-scale-set :after #'gortium/org-latex-refresh-on-zoom) - (use-package! age :demand t :config @@ -891,11 +928,11 @@ Handles org-clock and context link capture for tasks." ;; rebind function value for pass to passage (fset #'pass (lambda () (interactive) (passage))) (setq age-program "rage") - (setq auth-source-passage-filename (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets")) + (setq auth-source-passage-filename (expand-file-name "~/ExoKortex/4-Automata/dotfiles/secrets")) (setenv "PASSAGE_IDENTITIES_FILE" (expand-file-name age-default-identity)) (setenv "PASSAGE_RECIPIENTS_FILE" (expand-file-name age-default-recipient)) (setenv "PASSAGE_AGE" "rage") - (setenv "PASSAGE_DIR" (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets")) + (setenv "PASSAGE_DIR" (expand-file-name "~/ExoKortex/4-Automata/dotfiles/secrets")) ) (diff-hl-mode +1) @@ -934,7 +971,8 @@ Handles org-clock and context link capture for tasks." deepseek/deepseek-chat-v3-0324:free meta-llama/llama-4-maverick:free mistralai/devstral-2512:free - qwen/qwen3-coder:free)) + qwen/qwen3-coder:free + nvidia/nemotron-3-nano-30b-a3b:free)) (gptel-make-gemini "Gemini" :key (auth-source-passage-get 'secret "gemini") :stream t @@ -1150,14 +1188,50 @@ DIFF: (setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t" org-msg-startup "hidestars indent inlineimages" org-msg-greeting-fmt "\nBonjour%s,\n\n" - ;; org-msg-recipient-names '(("jeremy.compostella@gmail.com" . "Jérémy")) org-msg-greeting-name-limit 1 org-msg-default-alternatives '((new . (text html)) (reply-to-html . (text html)) (reply-to-text . (text))) - org-msg-convert-citation t - org-msg-signature " + org-msg-convert-citation t) + ) +(after! mu4e + (setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter) + + (setq mu4e-contexts + (list + ;; --- CONTEXT: TDNDE --- + (make-mu4e-context + :name "TDNDE" + :match-func (lambda (msg) + (when msg + (string-prefix-p "/TDNDE/" (mu4e-message-field msg :maildir)))) + :vars + '((user-mail-address . "tpouplier@tdnde.com") + (user-full-name . "Thierry Pouplier") + + ;; Folders + (mu4e-sent-folder . "/TDNDE/Sent") + (mu4e-drafts-folder . "/TDNDE/Drafts") + (mu4e-trash-folder . "/TDNDE/Trash") + (mu4e-refile-folder . "/TDNDE/Archive") + + ;; Shortcuts + (mu4e-maildir-shortcuts . (("/TDNDE/Inbox" . ?i) + ("/TDNDE/Sent" . ?s) + ("/TDNDE/Drafts" . ?d) + ("/TDNDE/Trash" . ?t) + ("/TDNDE/Archive" . ?a))) + + ;; SMTP + (smtpmail-smtp-user . "tpouplier@tdnde.com") + (smtpmail-stream-type . ssl) + (smtpmail-smtp-server . "smtp.hostinger.com") + (smtpmail-smtp-service . 465) + (mu4e-sent-messages-behavior . sent) + + ;; Context-specific signature + (org-msg-signature . " Cdlt, #+begin_signature @@ -1171,93 +1245,66 @@ Tél. : +1 (450) 667-1884 \\\\ Cell. : +1 (514) 887-1674 \\\\ tpouplier@tdnde.com \\\\ www.tdnde.com \\\\ -#+end_signature") - ) +#+end_signature"))) -(after! mu4e - (setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter) - (setq mu4e-contexts - (list + ;; --- CONTEXT: Perso --- (make-mu4e-context - :name "TDNDE" - :match-func - (lambda (msg) - (when msg - (string-prefix-p - "/TDNDE/" - (mu4e-message-field msg :maildir)))) - :vars - '((user-mail-address . "tpouplier@tdnde.com") - (user-full-name . "Thierry Pouplier") + :name "Perso" + :match-func (lambda (msg) + (when msg + (string-prefix-p "/Perso/" (mu4e-message-field msg :maildir)))) + :vars `((user-mail-address . "thierrypouplier@gmail.com") + (user-full-name . "Thierry Pouplier") + (mu4e-sent-folder . "/Perso/Sent") + (mu4e-drafts-folder . "/Perso/Drafts") + (mu4e-trash-folder . "/Perso/Trash") + (mu4e-refile-folder . "/Perso/Archive") + + (mu4e-maildir-shortcuts . (("/Perso/Inbox" . ?i) + ("/Perso/Sent" . ?s) + ("/Perso/Trash" . ?t) + ("/Perso/Drafts" . ?d) + ("/Perso/Archive" . ?a))) + + (smtpmail-smtp-user . "thierrypouplier@gmail.com") + (smtpmail-smtp-server . "smtp.gmail.com") + (smtpmail-smtp-service . 587) + (smtpmail-stream-type . starttls) + (mu4e-sent-messages-behavior . sent) + (org-msg-signature . " +Cordialement, - ;; Folders - (mu4e-sent-folder . "/TDNDE/Sent") - (mu4e-drafts-folder . "/TDNDE/Drafts") - (mu4e-trash-folder . "/TDNDE/Trash") - (mu4e-refile-folder . "/TDNDE/Archive") - - ;; Shortcuts - (mu4e-maildir-shortcuts - . - (("/TDNDE/Inbox" . ?i) - ("/TDNDE/Sent" . ?s) - ("/TDNDE/Drafts" . ?d) - ("/TDNDE/Trash" . ?t) - ("/TDNDE/Archive" . ?a))) - - ;; Bookmarks - (mu4e-bookmarks - . - ((:name "Airbus-6825" - :query "maildir:/TDNDE/* AND label:proj:6825 AND label:client:airbus" - :key ?A) - (:name "Bombardier-3154" - :query "maildir:/TDNDE/* AND label:proj:3154 AND label:client:bombardier" - :key ?B) - (:name "Daher-5304" - :query "maildir:/TDNDE/* AND label:proj:5304 AND label:client:daher" - :key ?D) - (:name "TDNDE" - :query "maildir:/TDNDE/* AND label:tdnde" - :key ?T))) - - ;; SMTP - (smtpmail-smtp-user . "tpouplier@tdnde.com") - (smtpmail-stream-type . ssl) - (smtpmail-smtp-server . "smtp.hostinger.com") - (smtpmail-smtp-service . 465))) - ;; Other context here - ;; (make-mu4e-context ...) - ) - ) +#+begin_signature +-- +*Thierry Pouplier* \\ +(514) 887-1674 | thierrypouplier@gmail.com +#+end_signature"))))) ;; End of mu4e-contexts list (setq mu4e-context-policy 'pick-first mu4e-compose-context-policy 'ask) (require 'mu4e-icalendar) (mu4e-icalendar-setup) - (setq mu4e-icalendar-diary-file nil) (setq gnus-icalendar-org-capture-file +org-capture-journal-file gnus-icalendar-org-capture-headline '("Journal")) (gnus-icalendar-org-setup) - ) +;; Separate after! blocks for clarity (after! mu4e (setq mu4e-modeline-support nil)) (after! mu4e ;; restore label key - (evil-define-key 'normal mu4e-headers-mode-map"l" #'mu4e-headers-mark-for-label) + (evil-define-key 'normal mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) (evil-define-key 'visual mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) (evil-define-key 'emacs mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) (evil-define-key 'normal mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) (evil-define-key 'visual mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) - (evil-define-key 'emacs mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) - ) + (evil-define-key 'emacs mu4e-view-mode-map "l" #'mu4e-view-mark-for-label))) (defun gortium-circe-nickserv-password (server) (auth-source-passage-get 'secret "irc")) @@ -1821,6 +1868,138 @@ and ensure the standard property drawer exists without overwriting existing data (lambda () (org-id-get-create)) nil 'tree))) +(after! org + ;; --------------------------------------------------------- + ;; 1. THE REUSABLE ENGINE (Domain Agnostic) + ;; --------------------------------------------------------- + (defun my/org-metric-sync-engine (&key prop-name value-current prop-last-done prop-interval target-tag log-label) + "Core logic to sync parent metrics to sub-tasks and log changes. + PROP-NAME: The property triggering the change (e.g., LAST_KM). + VALUE-CURRENT: The new value being set. + PROP-LAST-DONE: Sub-task property for 'baseline' (e.g., LAST_DONE_KM). + PROP-INTERVAL: Sub-task property for 'frequency' (e.g., INTERVAL_KM). + TARGET-TAG: Filter for specific sub-tasks (e.g., SERVICE). + LOG-LABEL: The prefix used in the Logbook entry." + (save-excursion + ;; A. Log the update to the Logbook with a timestamp + (when log-label + (org-add-log-setup 'note (format "%s: %s" log-label value-current) nil 'findpos)) + + ;; B. Narrow scope to current header and scan sub-tasks + (org-narrow-to-subtree) + (org-map-entries + (lambda () + (let* ((last-done (string-to-number (or (org-entry-get nil prop-last-done) "0"))) + (interval (string-to-number (or (org-entry-get nil prop-interval) "0"))) + (current (string-to-number value-current))) + ;; Only process tasks that have an interval set + (when (> interval 0) + (if (>= current (+ last-done interval)) + (org-todo "TODO") + (org-todo "HOLD"))))) + target-tag) + (widen))) + + (defun my/org-metric-prompt-engine (&key state tag prop-parent-metric prop-task-done label-prompt) + "Core logic to prompt for values when a task is marked DONE. + STATE: The new TODO state (e.g., DONE). + TAG: The tag identifying relevant tasks. + PROP-PARENT-METRIC: The property on the parent to update (e.g., LAST_KM). + PROP-TASK-DONE: The property on the task to update (e.g., LAST_DONE_KM). + LABEL-PROMPT: The text shown in the minibuffer." + (when (string= state "DONE") + (let ((tags (org-get-tags))) + (when (member tag tags) + (save-excursion + ;; Inherit parent metric as the default value for the prompt + (let* ((current-val (org-entry-get nil prop-parent-metric t)) + (input-val (read-string (format "%s (default %s): " label-prompt (or current-val "0")) + nil nil current-val))) + ;; 1. Update the task property + (org-set-property prop-task-done input-val) + ;; 2. Update the parent property (triggers the sync engine) + (save-excursion + (when (org-up-heading-safe) + (org-set-property prop-parent-metric input-val))) + (message "Synchronized %s to %s" label-prompt input-val))))))) + + ;; --------------------------------------------------------- + ;; 2. THE DISPATCHERS (The Switchboard) + ;; --------------------------------------------------------- + (defun my/org-property-change-handler (prop value) + "Routes property changes to the correct sync parameters." + (cond + ;; Case: F-150 Odometer + ((string= prop "LAST_KM") + (my/org-metric-sync-engine + :prop-name prop :value-current value :prop-last-done "LAST_DONE_KM" + :prop-interval "INTERVAL_KM" :target-tag "SERVICE" :log-label "Odometer Update")))) + + ;; Case: Equipment/Industrial Hours + ;; ((string= prop "LAST_HOURS") + ;; (my/org-metric-sync-engine + ;; :prop-name prop :value-current value :prop-last-done "LAST_DONE_HOURS" + ;; :prop-interval "INTERVAL_HOURS" :target-tag "MAINTENANCE" :log-label "Usage Hours Logged")))) + +(defun my/org-todo-state-handler () + "Safely routes DONE completions, catching any argument errors." + (interactive) + (condition-case err + (when (string= org-state "DONE") + (my/org-metric-prompt-engine + :state "DONE" + :tag "SERVICE" + :prop-parent-metric "LAST_KM" + :prop-task-done "LAST_DONE_KM" + :label-prompt "Current Odometer")) + (error (message "Handler suppressed error: %s" (error-message-string err))))) + ) +;; (my/org-metric-prompt-engine +;; :state org-state :tag "MAINTENANCE" :prop-parent-metric "LAST_HOURS" +;; :prop-task-done "LAST_DONE_HOURS" :label-prompt "Equipment Hours")) + +;; --------------------------------------------------------- +;; 3. THE HOOKS +;; --------------------------------------------------------- +(add-hook 'org-after-prop-change-hook #'my/org-property-change-handler) +(add-hook 'org-after-todo-state-change-hook #'my/org-todo-state-handler) + +(defun my/org-smart-advance-date () + "Jump to next repeater occurrence or add 1 day. +Suppresses all logging, notes, and custom odometer hooks." + (interactive) + (let ((org-log-done nil) + (org-log-repeat nil) + (org-todo-repeat-to-state "LOOP") + (org-after-todo-state-change-hook nil) + (org-treat-insert-todo-log-note nil) + (inhibit-modification-hooks t)) + (let ((is-agenda (derived-mode-p 'org-agenda-mode))) + (cond + ;; Case 1: Task has a repeater (teleport to next date) + ((if is-agenda + (org-agenda-with-point-at-orig-entry nil (org-get-repeat)) + (org-get-repeat)) + + ;; This surgical strike prevents the Logbook entry entirely + (cl-letf (((symbol-function 'org-add-log-setup) (lambda (&rest _args) nil))) + (if is-agenda (org-agenda-todo "DONE") (org-todo "DONE"))) + + (message "Silently jumped to next occurrence.")) + + ;; Case 2: No repeater, fallback to standard +1 day shift + (t + (if is-agenda + (org-agenda-date-later 1) + (org-timestamp-change 1 'day)) + (message "No repeater found: Added 1 day.")))))) + +(map! :after org + (:map org-mode-map + :localleader "L" #'my/org-smart-advance-date) + (:map org-agenda-mode-map + :localleader "L" #'my/org-smart-advance-date)) + ;; Open link in another frame (defun gortium/org-open-link-in-other-frame () "Open the Org link at point in another frame."