▶ YouTube Summaries

← All summaries

Extracting a modus core library from modus-themes

2026-10-18 Sun ⏱ 2 hr 47 min protesilaos

Prot spends a live stream splitting the Modus theme framework out of the modus-themes package. The new modus.el holds the theme-agnostic core: user options, faces, the palette machinery, commands, and the macros that instantiate themes. modus-themes then keeps only modus-operandi, modus-vivendi and their variants, and requires modus. He works through the file top to bottom, rewording docstrings, dropping assumptions about the Modus themes, and leaving TODOs. He also answers chat questions about Elisp idioms, Emacs forks, light versus dark themes, literate programming and contributing to Emacs.

Why extract the core

  • Since version 5, modus-themes is a theme framework. Many derivative themes build on it, including Prot's own ef-themes. Today a derivative has to pull in the whole package, including palettes and themes it never uses.
  • The aim is a separate library, modus, that derivatives depend on. The framework should know nothing about modus-operandi or modus-vivendi.
  • An immediate reason is that Modus is in core Emacs but is not a "library" in the Emacs sense, which makes life awkward for packages that build on it.
  • He also copied over Emacs's new contribution rules, which restrict LLM-generated code. Nobody knows whether such code can be copyrighted, and so whether it can be licensed under the GPL.

Renaming and compatibility

  • He renamed modus-themes-* to modus-* by brute force (e.g. modus-themes-faces became modus-theme-framework-faces), then fixed the fallout by hand.
  • The version number stays at 5.x, so derivatives can just swap the dependency name.
  • TODO: aliases such as modus-themes-bold for modus-bold, and aliases for the public and autoloaded commands (e.g. modus-themes-contrast), so existing configs and derivatives keep working. Some public symbols might keep the modus-themes prefix.
  • TODO: maybe bring back semantic faces (key binding, language error, etc.). Colour-named faces like "nuanced red" stay out.

Making the framework theme-agnostic

  • modus-themes-to-toggle and modus-themes-to-rotate now default to nil. With nil they fall back to whatever modus-get-themes returns, not to hard-coded operandi/vivendi.
  • He renamed a predicate to the public modus-theme-p so docstrings can refer to it. The :type of the toggle option still needs tightening (TODO).
  • The theme list now comes only from registered themes. The old code merged the built-in Modus themes with registered ones. Now there is no fallback: with no registered themes the result is nil, or an error ("no known modus themes").
  • He found and fixed a bug in the "known theme" check: it called the family-filtered getter where it needed the unfiltered list of all themes.
  • He renamed the palettes: the operandi palette becomes modus-light-palette ("the default modus light palette, same as modus-operandi-palette") and vivendi becomes modus-dark-palette. The other variant palettes move out of the core.
  • He kept the faint and intense preset overrides, since some people use them.
  • TODO: maybe add default "cool" and "warm" palettes for modus-generate-palette, and rethink what cool and warm mean there.
  • FIXME: palette generation hard-codes some hues. That is a problem for deuteranopia and tritanopia themes, and wrong in principle. It needs a better heuristic.
  • The old compatibility warnings, which made sense two years ago, can probably go (FIXME).

Code refinements along the way

  • He rewrote several predicates with and-let*. For example, the background-mode check becomes: properties exist, the background is set, and it equals the requested mode. He uses and-let* when only "non-nil" matters, and when-let* when the body returns something useful.
  • He tidied the theme-selection prompt with named bindings (all-themes, table, selection) so the logic is easier to follow. He kept an explicit if rather than collapsing it to a when passed as an optional argument, because he prefers clarity.
  • In disable-themes he named the list of themes to disable "applicable themes". The color-scheme check now uses and-let*, which copes with themes that lack properties.
  • Helper functions now come before their first caller.
  • Docstrings got British-to-American spelling (core Emacs uses American English), formatting tweaks for the 80-column limit, and he reworded some.
  • The palette preview (modus-themes-preview-colors-current) was already good. You can mark entries and copy them with w, and it follows mappings (cursor → fg-main → hex value).

Result

  • modus.el is reviewed. modus-themes shrinks to the palettes plus the theme declarations and requires modus. The palettes still make it long.
  • Still undecided: whether modus is its own package or repo. TODO: look for chances to merge more palette mappings. Testing is planned for a follow-up stream.

Chat Q&A highlights

  • Core Emacs wishlist: Prot can barely read C. He would like Embark-style "collect" and "export" of minibuffer candidates built in.
  • Vim versus Emacs motions: Vim motions are more efficient in isolation, but how fast you think is the real bottleneck. The draw of Emacs is extensibility and one consistent configuration language.
  • Colourful Lisp: try prism.el (Adam Porter), rainbow-blocks or rainbow-delimiters, and Jonas Bernoulli's paren-face to dim parentheses. He finds depth colouring helpful for about five minutes.
  • Styling the menu bar: it doesn't work under GTK because the toolkit controls it.
  • Contributing to Emacs: send patches via M-x report-emacs-bug or the mailing list. Changes over about 15 lines need an FSF copyright assignment, which is one form and not much red tape.
  • Docstring blank lines: the first line must stand alone (Marginalia shows it). After that, add a blank line only when starting a new paragraph rather than continuing one.
  • Neo-Emacs fork: unlikely to succeed. Vim had a BDFL and a community frustrated with him. Emacs has several maintainers and is more inclusive, so there is no social need for a fork. Neovim did help Vim.
  • Life: plans for insulation, floors, a deck and a separate office. He dresses for work even at home to stay in the right frame of mind.
  • Worktrees in Emacs: packages exist. If you can think of a workflow, you can build it.
  • Light versus dark: no theme is best in a vacuum, only within constraints. Use light themes in bright rooms and dark ones in dim rooms. Lower monitor brightness and saturation too. He runs his monitor at minimum brightness.
  • let, let, when-let*, and-let**: let* lets later bindings see earlier ones. let bindings live only inside the form. when-let* stops at a nil binding and then runs a body. and-let* only checks that every binding is non-nil.
  • Async Emacs: async.el (Wiegley/Thierry) runs external processes, but his denote-data batch-Emacs call is small, so he doesn't need it. denote-data is a good study in Elisp practice: index little and compute the rest cheaply.
  • Finding answers: read the manual, then the source. M-. into dabbrev showed him make-progress-reporter and message-log-max. M-x shortdoc taught him hash-table-count.
  • Writing an xref backend: define a backend symbol, then a cl-defmethod for each xref generic, specialised with (eql 'my-backend). The elisp-mode backend is a good if intimidating reference.
  • Literate programming: mostly busy work for a library like this, because prose and code drift apart. He uses it for his Emacs config and the Emacs Lisp Elements book. Documentation and comments are not optional.
  • Editing a sexp in its own buffer: edit-indirect, or a small command that clones an indirect buffer and narrows it.
  • Email: he uses notmuch. mu4e is just as good. Gnus felt overloaded with newsgroup concepts.
  • cl-lib: not essential but convenient. Modus uses cl-defgeneric and cl-defmethod, and denote-data uses cl-defstruct.
  • Colour-blind themes: he picks colours by hand from experience and colour theory, with no tools. A protanomalous viewer confirmed the deuteranopia themes work for them.
  • ef-themes in core: he won't push for it. Core needs the modus library and a few good defaults. Installing the rest takes one use-package form.
  • Buffer clutter: his beframe package limits each frame's buffer list to buffers opened in that frame. He uses tab-bar tabs inside frames, and the window manager (herbstluftwm) handles frames.