Declaring Emacs Bankruptcy
I declared Emacs bankruptcy.
Not because I stopped using Emacs —I could not do that— but because the config had accumulated so many layers over the years that fixing one thing would break two others. Opening a new file caused a noticeable freeze, Org-roam completion worked sometimes. Grammar checking occasionally ate all the CPU. The theme had a visual glitch. Nothing catastrophic on its own. Everything catastrophic together.
So I rewrote it from scratch with two hard requirements: modular and fast.
The new one keeps config.el as a thin orchestrator and splits functionality into named modules:
modules/
lr-macos.el macOS-specific frame and system settings
lr-ui.el theme, modeline, dashboard
lr-completion.el Corfu, Vertico, Consult
lr-editor.el evil, editing behaviour
lr-prog.el LSP, flycheck, error navigation
lr-tools.el magit, dired, terminals
lr-writing.el ltex-ls grammar checker
lr-org-core.el org-mode base settings
lr-org-roam.el org-roam, vulpea, dailies
lr-org-noter.el pdf annotation
lr-academic.el citar, bibtex, references
lr-email.el mu4e
lr-irc.el circeconfig.el loads the performance-critical modules immediately and
defers the heavy ones:
;; Load immediately (all fast, no I/O at startup)
(require 'lr-macos)
(require 'lr-ui)
(require 'lr-completion)
(require 'lr-editor)
(require 'lr-prog)
(require 'lr-tools)
(require 'lr-writing)
;; Defer heavy modules until org is first needed
(with-eval-after-load 'org
(require 'lr-org-core)
(require 'lr-org-roam)
(require 'lr-org-noter)
(require 'lr-academic))
;; mu4e deferred until first use
(with-eval-after-load 'mu4e
(require 'lr-email))This alone cut visible startup time substantially. The org stack never loads until you open an org file or an idle timer fires.
The org-roam startup problem
This was the hardest bug to fix and the most instructive.
The symptom: opening any org file would freeze Emacs for several
seconds. The stack trace (sent via pkill -SIGUSR2) showed:
font-lock-fontify-keywords-region → citar-org-activate (font-lock function) → (require 'citar) → citar-org-roam-setup → org-roam-db-sync ← here → org-roam-db-query → +org-roam-try-init-db-a (Doom's lazy-init advice, fires on first query) → org-roam-db-sync again ← second sync → find-file-noselect (for every roam file) → vc-refresh-state → git subprocess per file ← freeze
A font-lock function requiring citar triggered a full org-roam DB
sync, which opened every file in the roam directory, which ran git
status on each one, which blocked. citar-org-activate is a font-lock
keyword handler, it runs during redisplay when you first view a file
containing a citation. You never see it coming.
The fix: block org-roam-db-sync entirely except in two cases — when
called interactively (M-x org-roam-db-sync) or when an idle timer
explicitly allows it.
(defvar salih/--org-roam-allow-sync nil)
(defadvice! salih/org-roam-block-eager-sync-a (&rest _)
:before-while #'org-roam-db-sync
(or salih/--org-roam-allow-sync
(called-interactively-p 'any)))
;; Safe sync after 5s of idle — Emacs is not busy, no UX impact
(run-with-idle-timer
5 nil
(lambda ()
(when (featurep 'org-roam)
(let ((salih/--org-roam-allow-sync t))
(org-roam-db-sync)))))Doom's +org-init-roam-h internally uses cl-letf to replace
org-roam-db-sync with ignore during autosync setup. cl-letf
bypasses nadvice entirely, so startup is already safe — the advice only
catches the citar and lazy-init triggered syncs that happen later.
The iCloud symlink bug
All my org-roam notes live under ~/roam, but the actual files are in
iCloud: ~/roam/main, ~/roam/references, etc. are symlinks pointing
into /Library/Mobile Documents/com~apple~CloudDocs/sync/org/.
Doom Emacs sets find-file-visit-truename t. This means that when you
open /roam/main/foo.org, Emacs sets buffer-file-name to the iCloud
truename, not the ~/roam path.
org-roam-file-p checks whether file-truename(buffer-file-name) is
under file-truename(org-roam-directory). org-roam-directory is
/Users/l/roam. The iCloud truename is
/Users/l/Library/Mobile Documents/.... These share no common prefix.
org-roam-file-p returns nil for every file accessed via a symlink.
That silently broke DB updates on save, find-file hooks, and the
[[roam:Title]] → [[id:...]] link replacement.
The fix: pre-compute the truenames of symlinked subdirectories and
extend org-roam-file-p to return t when a file is under any of them.
(defvar salih/--roam-symlink-truenames nil)
(defun salih/--compute-roam-symlink-truenames ()
(when (bound-and-true-p org-roam-directory)
(let ((roam-dir (expand-file-name org-roam-directory)))
(setq salih/--roam-symlink-truenames
(delq nil
(mapcar (lambda (entry)
(let ((path (expand-file-name entry roam-dir)))
(when (and (file-directory-p path)
(file-symlink-p path))
(file-truename path))))
(directory-files roam-dir nil "^[^.]")))))))
(defadvice! salih/org-roam-file-p-symlink-a (fn &optional file)
:around #'org-roam-file-p
(or (funcall fn file)
(when salih/--roam-symlink-truenames
(let ((path (expand-file-name
(or file (buffer-file-name (buffer-base-buffer))))))
(and path
(not (auto-save-file-name-p path))
(not (backup-file-name-p path))
(string-suffix-p ".org" path t)
(cl-some (lambda (dir)
(string-prefix-p (file-name-as-directory dir) path))
salih/--roam-symlink-truenames))))))And consult-ripgrep does not follow symlinks by default, so the
org-roam full-text search was also missing all symlinked directories.
Fixed by adding --follow to the ripgrep invocation.
The ltex-ls completion conflict
I use ltex-ls for grammar checking in writing modes. ltex-ls registers
itself as an LSP client for org-mode by default (via
lsp-ltex-active-modes).
When ltex-ls starts in an org buffer, lsp-mode adds
lsp-completion-at-point to the buffer's completion-at-point-functions.
ltex-ls does not actually support textDocument/completion, so every
completion attempt produced a noisy error and silently displaced
org-roam's completion functions from the capf list.
The initial instinct was to remove org-mode from the registered LSP client after it loaded:
;; This does not work
(when-let ((client (gethash 'ltex-ls lsp-clients)))
(setf (lsp--client-major-modes client) ...))setf with lsp--client-major-modes requires a generalized-variable
setter that lsp-mode does not define for runtime use — it works at
compile time but not inside with-eval-after-load blocks. The error
message was void-function (setf lsp--client-major-modes).
The correct fix: set lsp-ltex-active-modes before lsp-ltex.el loads.
lsp-register-client evaluates lsp-ltex-active-modes at load time to
build the client's :major-modes list.
;; Must be set before lsp-ltex.el loads (top of lr-writing.el, before after! blocks)
(setq lsp-ltex-active-modes
'(text-mode latex-mode LaTeX-mode markdown-mode gfm-mode
org-mode ; kept so SPC t G can start it manually; not in auto-start hook
rst-mode message-mode mu4e-compose-mode))The global-git-commit-mode error
On first file open, the doom-first-file-hook tried to enable
global-git-commit-mode and crashed with void-variable function. The
magit-commit library was being loaded via a mode-line hook before its
dependencies were ready.
The fix is minimal: remove it from the first-file hook and wire it properly:
(remove-hook 'doom-first-file-hook #'global-git-commit-mode)
(with-eval-after-load 'git-commit
(add-hook 'find-file-hook #'git-commit-setup-check-buffer)
(add-hook 'after-change-major-mode-hook
#'git-commit-setup-font-lock-in-buffer))Full configuration is available at: github
It might be interesting to try out scala again now. Follow up on Emacs and Scala #Emacs #Programming