Skip to content

Trim Emacs config to a lean editor; fix cold-start failures - #27

Merged
tlockney merged 3 commits into
mainfrom
working-branch
Jul 27, 2026
Merged

Trim Emacs config to a lean editor; fix cold-start failures#27
tlockney merged 3 commits into
mainfrom
working-branch

Conversation

@tlockney

@tlockney tlockney commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

Reduces the Emacs config to a lean text editor and fixes the errors that made a first run on a new machine fail.

Before After
Wall clock to usable frame (emacs -nw, 3 runs) 1.41–1.47s 0.18–0.20s
emacs-init-time 0.285–0.318s 0.123–0.134s
GCs during startup 7 0
Packages installed 41 18
Startup errors/warnings 1 error + 4 treesit warnings 0
Fresh-device first run fails: Cannot open load file: compat-31 clean

Changes

New .emacs.d/early-init.el — GC deferral, frame chrome as frame parameters, native-comp-async-report-warnings-errors 'silent, and file-name-handler-alist suppression during startup.

Removed (~25 packages, including the whole treemacs/hydra/avy/posframe subtree): lsp-mode, lsp-ui, lsp-treemacs, treesit-auto, deno-ts-mode, typescript-mode, typescript-ts-mode, rust-mode, writeroom-mode, ido-completing-read+, the which-key package (built into Emacs 30), and desktop-save-mode.

Bugs fixed

  • Fresh-install failure: archives now refresh once up front and compat installs before anything macro-expands against it.
  • treesit-auto remapped every language to a *-ts-mode while only bash/toml/yaml grammars existed, throwing Tree-sitter for JSON isn't available on every startup.
  • ido-everywhere and vertico-mode were both enabled, competing for the same minibuffer reads.
  • gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3" was disabling TLS 1.3 on an Emacs 30.2 build.
  • (setq major-mode 'text-mode)setq-default.
  • Font/theme/icon setup moved to a per-frame hook, since $EDITOR is emacsclient and the daemon has no display when init.el is read.
  • custom-file was set but never loaded.
  • Deduplicated column-number-mode, xterm-mouse-mode, and two overlapping before-save-hook entries.
  • show-trailing-whitespace scoped to prog/text/conf modes.

Separately: $EMACS_SOCKET_NAME — the shell derived $TMPDIR/emacs/server, but Emacs 30 uses $TMPDIR/user/<uid>/emacs/server. The e alias never reached the server and cold-started a new Emacs every time. Both the derivation and the --socket-name flag are removed; emacsclient computes the same default the server writes.

Verification

  • Fresh-device install simulated against a clean --init-directory with a real network fetch: exit 0, no runtime errors, 18 packages.
  • Warm startup timed over 3 runs each, before and after.
  • Runtime assertions confirm vertico / savehist / save-place / recentf / which-key / corfu / corfu-terminal / doom-modeline / server all active, completion-styles is (orderless basic), and recentf keeps the 1000-item limit and .recentf file.
  • TTY frame verified: doom-modeline-icon resolves to nil and corfu-terminal-mode to t.
  • <escape> binding confirmed safe in a terminal: ESC x still runs execute-extended-command, [27] is still ESC-prefix.
  • Both zsh files pass zsh -n.

Notes

  • Not verified: GUI-frame font selection and doom-modeline icon rendering (no display available during the work). TTY behavior is verified.
  • material-theme is unmaintained (2015) and specifies some nil face attributes, which Emacs 30 warns about into *Messages* on a graphical frame. It's the only remaining startup noise; a comment in init.el notes that (load-theme 'modus-vivendi) is a built-in, warning-free swap. Left as-is rather than change the theme unasked.
  • This repo's documented flow is yadm merge working-branch from the home directory, not a GitHub merge. Merging this PR is an alternative path — don't do both without pulling first.

CI note (third commit)

The ShellCheck job was already failing before this PR — on main and on every pull request going back to at least November 2025. This PR merely triggered it, because the workflow's paths: filter includes .config/zsh/**.

None of the files changed here are scanned: the job uses scandir: './bin' plus two named .sh files, and shellcheck cannot parse zsh regardless. Reproduced locally — 14 findings, all in ./bin, all info-level: 13 × SC1091 (Not following: lib/common.sh, because those scripts source it via a runtime-computed SCRIPT_DIR) and 1 × SC2016 on a line that intentionally prints a literal eval for the reader to copy.

shellcheck -x -P SCRIPTDIR resolves all 13 legitimately (verified locally: 14 findings → 1), but action-shellcheck@2.0.0 declares an options input and never references it, so extra flags can't be passed at that version. Setting severity: warning is the available lever, and it matches how bin/lint-shell already treats these findings — it runs shellcheck with || true and never fails. Warning- and error-level defects still fail the build.

This commit is deliberately separate so it can be dropped or split into its own PR if you'd rather fix the lint posture independently.

tlockney added 2 commits July 27, 2026 10:07
Startup drops from ~1.43s to ~0.18s wall clock (init-time 0.29s -> 0.13s,
7 GCs -> 0), and the package set from 41 to 18.

- Add early-init.el: GC deferral, frame chrome as frame parameters, and
  native-comp warning suppression, all before the first frame is drawn.
- Refresh package archives once up front and install compat before
  anything macro-expands against it. A first run against an empty elpa/
  previously died with "Cannot open load file: compat-31".
- Drop the IDE layer: lsp-mode, lsp-ui, lsp-treemacs, treesit-auto, and
  the rust/typescript/deno modes. treesit-auto remapped every language to
  a *-ts-mode while only bash, toml and yaml grammars were installed, so
  JSON threw "Tree-sitter for JSON isn't available" on every startup.
- Drop desktop-save-mode, the single largest startup cost, which also
  restored file paths that don't exist on other machines. save-place and
  recentf cover the useful part for near-zero cost.
- Drop ido, which was fighting vertico over the same minibuffer reads,
  and the which-key package, which is built in as of Emacs 30.
- Move font, theme and modeline-icon setup to a per-frame hook. $EDITOR
  is emacsclient, and the daemon has no display when init.el is read, so
  the old display-graphic-p checks answered for the wrong frame.
- Remove the gnutls TLS 1.3 workaround, an Emacs 26-era setting that was
  disabling TLS 1.3 on a 30.2 build.
- Fix major-mode to use setq-default, scope show-trailing-whitespace to
  prog/text/conf modes, load custom-file, and dedupe the repeated
  column-number-mode, xterm-mouse-mode and before-save-hook entries.
- Add markdown-mode explicitly; it previously arrived as an lsp-mode
  dependency.
The derived path was $TMPDIR/emacs/server on macOS, but Emacs 30 uses
$TMPDIR/user/<uid>/emacs. The `e` alias therefore never reached a running
server and cold-started a fresh Emacs on every invocation.

emacsclient derives the same default socket path that server-start writes,
so drop both the derivation and the --socket-name flag rather than keep a
second copy of that logic in sync.
@tlockney
tlockney marked this pull request as ready for review July 27, 2026 18:12
The ShellCheck job has never passed, on main or on any pull request. All
14 findings are info-level: 13 are SC1091 "Not following: lib/common.sh",
raised because bin/ scripts source it through a SCRIPT_DIR computed at
runtime, and one is an SC2016 on a line that deliberately prints a literal
eval command for the reader to copy.

`shellcheck -x -P SCRIPTDIR` resolves all 13 properly, but
action-shellcheck@2.0.0 declares an `options` input and never references
it, so extra flags cannot be passed at this version. Setting severity to
warning is the available lever, and it matches how bin/lint-shell already
treats these findings: it runs shellcheck with `|| true` and never fails.

Real defects at warning and error level still fail the build.
@tlockney
tlockney merged commit c5c7cf4 into main Jul 27, 2026
1 check passed
@tlockney
tlockney deleted the working-branch branch July 27, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant