Architecture Overview
Jotain is organised into distinct layers, each with a clear responsibility.
File Structure
jotain/
├── early-init.el # Pre-package/GUI initialisation
├── init.el # Entry point — loads modules in order
├── lisp/ # Modular Elisp config (init-*.el, one per concern)
├── emacs.nix # GNU Emacs build from source (cache-parity invariant)
├── default.nix # Non-flake entry point (flake-compat wrapper)
├── overlay.nix # Standalone nixpkgs overlay (wraps nix/mk-overlay.nix)
├── module.nix # Home Manager module for the daemon
├── module-system.nix # Shared NixOS / nix-darwin module
├── module-nix-on-droid.nix # nix-on-droid module (terminal-only build)
├── nix/
│ ├── mk-overlay.nix # Overlay implementation — distribution assembly
│ ├── checks.nix # Flake checks (lint, compile, tests, builds)
│ ├── use-package.nix # use-package scanner (Elisp → epkgs mapping)
│ └── ... # docs, site, extra packages, treefmt helpers
├── devenv.nix # Development shell (tooling only — no Emacs)
├── devenv.yaml # devenv inputs (pinned to flake.lock revs)
├── flake.nix # Flake entry point — source of truth for pins
├── Justfile # Task runner recipes
├── docs/ # Documentation sources (rendered into the site,
│ # the Info manual, and the man page; docs.json
│ # defines page order for all three)
├── website/ # page.jylhis.com/jotain site shell (assembled by nix build .#site)
├── test/ # ERT tests (every *.el here is loaded by elisp-test)
├── bench/ # Startup-benchmark wrapper init files
├── templates/ # Tempel snippet templates (jotain.eld)
├── config/ # Auxiliary tool config (e.g. eca)
├── scripts/ # Bootstrap helpers (e.g. bootstrap-agent-env.sh)
└── journal/ # Development journal entries
Layers
Emacs Lisp Layer
The Elisp configuration is split into three parts:
early-init.el— loaded beforepackage.el, before the first frame, beforeinit.el. Handles anything that must happen that early: the startup GC threshold,use-package-always-ensure, frame chrome defaults, native-comp and eln-cache redirection, terminal aliases.init.el— tiny entry point. Registers MELPA/NonGNU ELPA as fallback archives, putslisp/on the load-path, pointscustom-fileatvar/custom.el(write-only — never loaded back), andrequires each module in order. Archive refresh is off the startup path entirely — there is no background warm-up; archives are fetched only whenpackage-installfinds an empty cache, or on an explicitM-x package-refresh-contents.lisp/init-*.el— one file per concern. See Modules for the full list and their responsibilities.
Nix Layer
The Nix layer provides reproducible Emacs builds:
emacs.nix— wraps the git-based attrs fromnix-community/emacs-overlay(git/unstable/igc;unstableis the default) or nixpkgs' defaultemacsattribute (mainline, the cache-parity canary), with the supported build flags exposed as arguments. The build matrix is deliberately narrow: pgtk/Wayland GUI or terminal-only on Linux, patched NS GUI or terminal-only on Darwin — everything else is asserted away. A cache-parity invariant guarantees the Linux and terminal builds produce the same store paths as the matching prebuilt attrs, so their default-rev builds are cache hits against Hydra andnix-community.cachix.org. Customrevpins and the Darwin GUI (whose nix-giant patches are on by default) run throughoverrideAttrsand intentionally rebuild from source. See Nix build layer for the invariant and its verification command.default.nix— flake-compat wrapper for the default package set.module.nix— Home Manager module that runs Jotain as a user-session Emacs daemon (services.jotain), generates a desktop entry foremacsclient, and can install itself asEDITOR/VISUAL. Supports systemd on Linux and launchd on macOS;services.jotain.packageswaps in any other Jotain-shaped distribution.
Development Layer
devenv.nix— development shell, tooling only: there is noemacsbinary in it (see its top-of-file note). It ships Nix tooling and linters (nil,nixfmt-rfc-style,treefmt,statix,deadnix), the language servers and CLIs the Elisp config shells out to, the docs toolchain, and the fonts the UI config probes. Build and launch the editor withjust run-built(see the current-state note in Launching Emacs).Justfile— every recipe you run day-to-day:run-built/run-built-debug(build via Nix, then launch),check,test, thebuild-*matrix,fmt,update,verify,clean,clean-all. The former direct-launch and in-shell compile recipes (run,debug,tty,check-elisp,compile,bench, …) were removed when Emacs left the dev shell; their coverage lives in theelisp-lint/elisp-compile/elisp-testflake checks.- Pinning —
flake.lockis the single source of truth;default.nixandemacs.nixread it directly viafetchTarballso non-flakenix-buildconsumers get the same pin.devenv.yamlmirrors the flake's shared input revs (nixpkgs,treefmt-nix,emacs-overlay), andjust updatekeepsdevenv.lockin lockstep.