diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-15 10:24:40 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-15 10:24:40 -0500 |
| commit | 45e0f6e896b2c34de25d5c3aa18474c79d6a1e72 (patch) | |
| tree | 3f4d822aa5da53f4e6bbebbdc7fb400a2b212189 /docs/design/init-load-graph.org | |
| parent | a5c9f48220cd52770f10f7627922b9fc8e2204cc (diff) | |
| download | dotemacs-45e0f6e896b2c34de25d5c3aa18474c79d6a1e72.tar.gz dotemacs-45e0f6e896b2c34de25d5c3aa18474c79d6a1e72.zip | |
docs: move specs to docs/specs/ with lifecycle-status filenames
Separate the 27 formal specs from working notes. Specs move to docs/specs/, notes stay in docs/design/. Each spec carries its lifecycle in the filename (-spec, -spec-doing, -spec-implemented, -spec-superseded) plus an authoritative ID and STATUS property drawer. The status came from checking each spec against the code, not the doc's own field: 6 implemented, 8 in progress, 12 not started, 1 superseded.
Inbound links become org-id links so future status renames don't break them; code-comment paths repoint to docs/specs/. Working notes, inventories, reviews, and brainstorms stay in docs/design/.
Diffstat (limited to 'docs/design/init-load-graph.org')
| -rw-r--r-- | docs/design/init-load-graph.org | 829 |
1 files changed, 0 insertions, 829 deletions
diff --git a/docs/design/init-load-graph.org b/docs/design/init-load-graph.org deleted file mode 100644 index 3db2fe854..000000000 --- a/docs/design/init-load-graph.org +++ /dev/null @@ -1,829 +0,0 @@ -#+TITLE: Design: Untangle the init.el Load Graph -#+AUTHOR: Craig Jennings -#+DATE: 2026-05-04 - -* Status - -Draft. Specification only. No load-order implementation is part of this design -document. - -* Problem - -=init.el= is currently both the startup script and the dependency graph. It -eagerly requires almost every module in a fixed order, so many modules work -because some earlier require happened to define a variable, keymap, path -constant, hook owner, package, or helper function. - -That creates four practical problems: - -- Standalone module loading is unreliable. A module may byte-compile but fail at - runtime unless enough of =init.el= was loaded first. -- Startup has unnecessary work. Optional workflows, heavy packages, timers, - network-facing integrations, and media tools load even when not used. -- Side effects are hard to audit. Keybindings, timers, global hooks, server - setup, package configuration, and command definitions are mixed together. -- Test boundaries are blurry. Tests often need to simulate init order instead of - loading the unit under test directly. - -The target is not "lazy load everything." The target is an explicit, testable -load graph where eager startup is a small documented set, optional workflows -load from commands/hooks/autoloads, and module dependencies are declared by the -modules that use them. - -* Goals - -- Make module ownership obvious: libraries, keymap ownership, package - configuration, commands, and startup side effects should be distinguishable. -- Make dependencies explicit with ordinary =require=, =autoload=, or documented - hook/package boundaries. -- Reduce eager startup load without breaking existing keybindings or daily - workflows. -- Keep the migration incremental and reversible. Each batch should be small - enough to test and inspect. -- Preserve interactive behavior for configured workflows, including calendar - sync, Org capture/agenda, mail, F-keys, and media commands. -- Improve testability: modules should either load directly or fail with a clear - missing external package/config message. - -* Non-Goals - -- Rewriting the whole configuration into one framework or literate init. -- Removing =use-package=. This design assumes package config modules continue to - use it where appropriate. -- Eliminating all top-level forms. Some top-level configuration is appropriate, - especially for foundational Emacs settings and hook registration. -- Solving package bootstrap in =early-init.el=. That is tracked by the separate - "Move package bootstrap out of =early-init.el= where possible" project. -- Rotating calendar feed URLs or designing secret storage beyond the local - calendar config path already introduced. Token rotation remains a separate - security task. -- Consolidating all scattered utility helpers. Utility consolidation is a - sibling project because it changes helper ownership, tests, and call sites - without necessarily changing startup load order. - -* Principles - -** Eager Requires Are Allowed Only With A Reason - -An eager require in =init.el= should satisfy one of these conditions: - -- It establishes basic Emacs behavior needed for the rest of startup. -- It defines shared constants or helpers used by many eager modules. -- It owns the global key prefix/keymap registration system. -- It configures core UI behavior that should be visible in the first frame. -- It starts a user-approved startup service that cannot be triggered lazily. - -Everything else should be a candidate for autoload, hook-based loading, -=with-eval-after-load=, or a command wrapper. - -** Modules Declare What They Use - -If a module calls a function or reads a variable at runtime, it should not rely -on init order unless that dependency is an explicit startup contract. - -Preferred dependency forms: - -- Runtime dependency: =(require 'module)=. -- Optional runtime dependency: =(require 'module nil t)= with a clear degraded - behavior. -- Macro/compile-time dependency: =(eval-when-compile (require 'module))=. -- Command-only dependency: =(autoload 'command "module" nil t)= or a lazy - command wrapper. -- Package-bound dependency: =use-package :after=, =:hook=, =:commands=, or - =with-eval-after-load=. - -Avoid test-only shims in production modules such as "define this keymap if it -does not exist." Tests should provide stubs or load the real owner. - -** Utility Extraction Should Stay Small And Evidence-Based - -Some hidden dependencies exist because generic helpers live in feature modules -where they were first needed. Moving those helpers into =system-lib= can make -dependencies clearer, but utility extraction should not become part of every -load-order change by default. - -Extract a helper only when: - -- at least two callers need substantially the same behavior, -- the helper can stay dependency-light enough for foundation startup, -- tests can move with the helper, -- the extraction is atomic and easy to review. - -Avoid building a broad utility suite speculatively. Prefer one helper, one -tested extraction, one commit. - -** Keymaps Have Owners - -=keybindings.el= should own global prefixes, especially =cj/custom-keymap= and -the =C-;= prefix. Feature modules may define local maps or command maps, but -registration into global prefixes should go through a small convention/helper so -load order is not a hidden dependency. - -** Side Effects Are Named And Isolated - -Side effects include: - -- starting timers, -- starting processes, -- calling network-facing sync/fetch commands, -- setting global keybindings, -- mutating global hooks, -- opening files/buffers, -- enabling global modes, -- loading large packages solely for optional commands. - -Each side effect should have one of: - -- a documented eager reason, -- an interactive command, -- a hook/package boundary, -- a noninteractive/batch guard, -- a test that proves the side effect does not happen in the wrong context. - -* Target Architecture - -** Layer 0: Early Startup - -Owned by =early-init.el=. Should remain limited to startup mechanics that must -happen before package/UI initialization. - -Examples: - -- package archive/bootstrap policy, -- native-comp/cache startup knobs that must be early, -- disabling expensive default UI before first frame. - -This design does not refactor =early-init.el= except to avoid adding new load -graph responsibilities to it. - -** Layer 1: Foundation - -Small eager set required before most other modules can safely load. - -Expected contents: - -- =system-lib= -- =user-constants= -- =host-environment= -- =system-defaults= -- =keyboard-compat= -- =keybindings= -- maybe =config-utilities=, if debug helpers are intentionally eager - -Foundation modules should be able to load in batch mode without package, -network, timer, or UI-package side effects. - -Adding a new Layer 1 module requires a coordinated update to the -=system-lib.el= dependency budget in [[file:utility-consolidation.org][utility-consolidation.org]]. - -Topic libraries introduced by the utility project join Layer 1 only when their -first consumer is foundation-eager. Otherwise they are Layer 2 and loaded by an -explicit =require= from their eager consumers. Add each new topic library to the -module category table before migrating its first consumer. - -** Layer 2: Core UX - -Eager or near-eager modules that shape the first interactive session. - -Expected contents: - -- basic text/editing defaults, -- core UI frame/theme/font/modeline behavior, -- selection/completion framework, -- F-key development entry points, -- VC/test/coverage command entry points. - -Core UX modules may configure packages, but heavy features should still use -=:commands=, =:hook=, or =:defer= where practical. - -** Layer 3: Domain Workflows - -Org, programming, mail, browser, media, AI, and integration modules. These -should generally load through hooks, commands, package =:after= clauses, or -workflow-specific entry commands. - -Examples: - -- Org capture/agenda can remain eager if the user's daily workflow needs it, - but exporters and optional extensions can be deferred. -- Language modules should load from mode hooks or file associations, not because - every startup might edit every language. -- Mail/media/AI/rest tools should register commands eagerly if needed, then load - heavy packages only on use. - -** Layer 4: Optional And Experimental - -Entertainment, modules in test, diagnostics, and rarely used tools. These should -not be required by default unless the user explicitly chooses that behavior. - -Examples: - -- =games-config= -- =music-config= -- =lorem-optimum= -- =gloss-config= -- optional IRC/Slack/feed/media modules when not in active use - -* Module Categories - -This is a first-pass classification to guide implementation. It is not an -architectural truth table; each module should be confirmed while refactoring. - -Category key: - -- =F= foundation or shared library/config. -- =C= core eager UX. -- =P= package configuration that should usually be hook/command/package loaded. -- =D= domain workflow that may have a user-visible eager reason. -- =S= startup side-effect or timer/process owner. -- =O= optional, entertainment, experimental, or rarely used. -- =L= pure-ish library/command helpers that should be easy to load directly. - -| Module | Category | Expected final load shape | Notes | -|--------+----------+---------------------------+-------| -| =early-init= | F | early | Layer 0; see Non-Goals. | -| =system-lib= | F/L | eager | Low-level helpers. Keep side-effect free. | -| =cj-process= | F/L | TBD per first consumer | Topic library placeholder; see utility-consolidation Group 3. | -| =cj-org-text= | F/L | TBD per first consumer | Topic library placeholder; see utility-consolidation Group 6. | -| =cj-cache= | F/L | TBD per first consumer | Topic library placeholder; see utility-consolidation Group 7. | -| =user-constants= | F | eager, then split | Split pure path constants from directory creation/failure behavior. | -| =host-environment= | F/L | eager | Predicate helpers. | -| =system-defaults= | F/S | eager | Owns global Emacs defaults, server/recentf/minibuffer hooks. | -| =keyboard-compat= | F/S | eager | Terminal/GUI keyboard setup hooks. | -| =keybindings= | F/C | eager | Owner of =cj/custom-keymap= and global prefixes. | -| =config-utilities= | C/O | eager or command-loaded | Debug keymap may be eager; heavy org parsing commands can lazy require. | -| =custom-case= | L/C | autoload commands + key registration | Text command helper. | -| =custom-comments= | L/C | autoload commands + key registration | Text command helper. | -| =custom-datetime= | L/C | autoload commands + key registration | Text command helper. | -| =custom-buffer-file= | L/C | eager only if remaps required | Has file/process helpers and keymap registration. | -| =custom-line-paragraph= | L/C | autoload commands + key registration | Requires =expand-region= at command boundary if possible. | -| =custom-misc= | L/C | autoload commands + key registration | Misc commands. | -| =custom-ordering= | L/C | autoload commands + key registration | Text command helper. | -| =custom-text-enclose= | L/C | autoload commands + key registration | Text command helper. | -| =custom-whitespace= | L/C | autoload commands + key registration | Text command helper. | -| =external-open= | L/D | autoload commands | Runtime requires environment/process helpers explicitly. | -| =media-utils= | D | command-loaded | Downloads/players should run only by command. | -| =auth-config= | F/D | eager or package-after | Auth setup may be core; GPG commands should remain commands. | -| =keyboard-macros= | C | eager or keymap-only | Lightweight command/key owner. | -| =system-utils= | L/C | eager or command-loaded | Timers/process monitor utilities. | -| =text-config= | C/P | eager hooks | General text defaults and package config. | -| =undead-buffers= | C | eager if remaps desired | Global kill-buffer remaps. | -| =browser-config= | D/P | command/package-loaded | Browser workflow. | -| =coverage-core= | C/L | eager command entry | F7 entry point and backend registry. | -| =coverage-elisp= | C/P | eager after core | Backend registration; keep cheap. | -| =dev-fkeys= | C | eager | F4/F6 command entry points. | -| =ui-config= | C/S | eager | Cursor/UI defaults; post-command hook should be documented. | -| =ui-theme= | C | eager + explicit startup call | Theme load stays explicit in init. | -| =ui-navigation= | C/P | eager | Window keybindings and winner/buffer-move config. | -| =font-config= | C/P/S | eager or first-frame | Font hooks/font installation checks need guards. | -| =selection-framework= | C/P | eager | Completion stack; likely core UX. | -| =modeline-config= | C/S | eager | Mode line and VC cache hooks. | -| =mousetrap-mode= | C | eager if global behavior desired | Prevents accidental mouse edits. | -| =popper-config= | C/P | eager if enabled, else remove/defer | Existing disabled-state question remains. | -| =chrono-tools= | D/P | command-loaded | Calendar/timer commands; sound path dependency explicit. | -| =diff-config= | C/P | eager or package-loaded | Diff/merge UX. | -| =erc-config= | O/D/P | command-loaded | IRC should not be startup load by default. | -| =slack-config= | O/D/P | command-loaded | Slack package/auth and which-key registration should be after-load. | -| =eshell + term-config= | D/P | command/hook-loaded | Shell/terminal packages. | -| =help-utils= | L/D | autoload commands | Search/help commands. | -| =help-config= | C/P | eager or after help | Info/man/help config. | -| =tramp-config= | D/P | package-loaded | Remote shell configuration. | -| =calibredb-epub-config= | O/D/P | command-loaded | Ebook workflow. | -| =dashboard-config= | C/S | eager only if startup dashboard desired | Opens/initializes landing page behavior. | -| =dirvish-config= | D/P | command/hook-loaded | File manager; runtime constants explicit. | -| =dwim-shell-config= | D/P | command-loaded | Shell commands from Dired/Dirvish. | -| =elfeed-config= | O/D/P | command-loaded | Feed reader/podcast workflow. | -| =eww-config= | D/P | command-loaded | Web browsing helpers. | -| =flyspell-and-abbrev= | C/P | hooks | Text-mode spelling/abbrev. | -| =httpd-config= | O/D/P | command-loaded | Local web server. | -| =latex-config= | D/P | hook-loaded | Existing WIP comment should become tasks or be removed. | -| =mail-config= | D/P | command-loaded or eager by choice | Heavy mu4e/org-msg; daily workflow may justify eager command registration. | -| =markdown-config= | D/P | mode-loaded | Markdown package config. | -| =pdf-config= | D/P | file/mode-loaded | Heavy PDF packages should load on PDF open. | -| =quick-video-capture= | O/D/S | command/protocol-loaded | Top-level timers should be removed or guarded. | -| =video-audio-recording= | O/D/S | command-loaded | External process/device probing only on command. | -| =transcription-config= | O/D/P | command-loaded | Auth/process workflow. | -| =weather-config= | O/D/P | command-loaded | Optional command. | -| =prog-general= | C/P/S | eager or hooks | Projectile, treesit policy, LSP ownership concerns. | -| =test-runner= | C/L | eager command entry | Test keymap and project-scoped state. | -| =vc-config= | C/P | eager command entry | Magit/git keymap; clone command hardening separate. | -| =flycheck-config= | C/P | hooks | General linting. | -| =prog-training= | O/D/P | command-loaded | Exercism/Leetcode optional. | -| =prog-c= | D/P | mode-loaded | C hooks and compile command. | -| =prog-go= | D/P | mode-loaded | Go hooks/LSP. | -| =prog-lisp= | D/P | mode-loaded | Lisp package config. | -| =prog-lsp= | C/P | package policy owner | Should consolidate generic LSP policy. | -| =prog-shell= | D/P/S | mode-loaded | after-save executable hook should be opt-in or scoped. | -| =prog-python= | D/P | mode-loaded | Python hooks/LSP. | -| =prog-webdev= | D/P | mode-loaded | Webdev modes/LSP. | -| =prog-json= | D/P | mode-loaded | JSON formatting/mode config. | -| =prog-yaml= | D/P | mode-loaded | YAML formatting/mode config. | -| =org-config= | C/D/P | eager | Core Org behavior likely eager. | -| =org-agenda-config= | D/S | eager by workflow, timers guarded | Agenda cache lifecycle project tracks cleanup. | -| =org-babel-config= | D/P | after Org | Babel languages package config. | -| =org-capture-config= | D/P | eager if capture hot path | Protocol/capture templates. | -| =org-contacts-config= | D/P | after Org/mail | Contacts workflow. | -| =org-drill-config= | O/D/P | command-loaded | Optional drill workflow. | -| =org-export-config= | D/P | command-loaded | Export packages/processes. | -| =hugo-config= | D/P | command-loaded | Blog workflow. | -| =org-reveal-config= | O/D/P | command-loaded | Presentation workflow. | -| =org-refile-config= | D/S | eager by workflow, timers guarded | Refile cache lifecycle project tracks cleanup. | -| =org-roam-config= | D/P/S | eager by workflow | Capture/finalize hooks, db. | -| =org-webclipper= | O/D/P | protocol/command-loaded | Global temp state cleanup tracked separately. | -| =org-noter-config= | O/D/P | command-loaded | PDF notes workflow. | -| =ai-config= | D/P | command-loaded | GPTel commands; avoid loading all AI tooling at startup. | -| =ai-conversations= | D/L/S | after gptel | Autosave hook and persistence path need coverage. | -| =restclient-config= | D/P | command-loaded | API exploration. | -| =calendar-sync= | D/S | eager only if configured, batch safe | Private config path and noninteractive guard exist. | -| =reconcile-open-repos= | D/S | command-loaded | Repo scanning/reconciliation should not run at startup. | -| =local-repository= | O/D/P | command-loaded | Local package mirror workflow. | -| =music-config= | O/D/P/S | command-loaded | EMMS/keymap optional, hooks only after EMMS. | -| =games-config= | O | command-loaded | Optional. | -| =lorem-optimum= | O/L | command-loaded | Module in test. | -| =jumper= | O/L | command-loaded | Navigation helper. | -| =system-commands= | D/S | command-loaded | High-impact commands; defensive work tracked separately. | -| =gloss-config= | O/D/P | command-loaded | Glossary workflow. | -| =wrap-up= | S | eager if desired | End-of-startup buffer bury timer. | -| =ledger-config= | O/D/P | mode-loaded | Not currently required by init. | -| =mu4e-org-contacts-integration= | D/L | after mu4e/org-contacts | Loaded by mail workflow. | -| =mu4e-org-contacts-setup= | D/L | after mu4e/org-contacts | Setup helper. | -| =org-agenda-config-debug= | O/L | command/debug-loaded | Debug helper. | -| =show-kill-ring= | O/L | command-loaded | Not currently required by init. | - -* Module File Header Standard - -Each module should eventually declare its load-graph contract in its own -commentary header. The category table above is the seed view; module headers -are the contributor-facing contract that travels with the code. - -Required header lines, after =;;; Commentary:=: - -1. =;; Layer: <0|1|2|3|4> (<layer name>).= -2. =;; Category: <F|C|P|D|S|O|L>=. -3. =;; Load shape: <eager|hook|mode|command|after-load>=. -4. =;; Eager reason:= one-line justification when load shape is =eager=, - omitted otherwise. -5. =;; Top-level side effects:= timer, process, hook, package, network, - buffer mutation, file write, or =none=. -6. =;; Runtime requires:= explicit runtime module/package list. -7. =;; Direct test load: <yes|conditional|no>=, with a brief reason when not - =yes=. - -Optional: - -- =;; See also:= references to tests and design docs. - -Worked example: - -#+begin_src emacs-lisp -;;; calendar-sync.el --- One-way calendar synchronization to Org -*- lexical-binding: t; -*- -;; -;;; Commentary: -;; -;; Layer: 3 (Domain Workflow). -;; Category: D/S. -;; Load shape: eager only when calendar-sync.local.el configures calendars. -;; Eager reason: daily-driver workflow; user expects calendars synced at first -;; session. Top-level startup is guarded so batch/test loads do not start -;; timers or network fetches. -;; Top-level side effects: timer, network fetch, file writes to calendar Org -;; files. Guarded by noninteractive/config checks. -;; Runtime requires: user-constants, seq, subr-x. -;; Direct test load: yes (batch-safe; private config is optional). -;; -;; See also: docs/design/init-load-graph.org, tests/test-calendar-sync.el. -;; -;;; Code: -#+end_src - -Phase 1 should annotate every module required by =init.el= with this header. -Later validation can assert that every required module declares the seven -required lines. - -* Proposed Load Shape - -Migration commits should use conventional commit prefixes consistently: - -- =refactor:= for behavior-preserving load-order, dependency, keymap, and lazy - loading migrations. -- =feat:= only when adding a new user-visible capability. -- =test:= for test-only follow-up work. -- =docs:= for spec, inventory, design updates, and module-header annotations, - even when those annotations touch =modules/*.el= files. - -Default deferral mechanism: - -- Prefer =use-package :commands= for command-driven deferrals. -- Prefer =use-package :mode= when loading is file-extension or major-mode - driven. -- Prefer =use-package :hook= when the consumer is a mode-hook function. -- Use explicit =(autoload 'command "module" nil t)= only when the command is - not naturally owned by a =use-package= form. - -** Phase 1: Inventory And Contracts - -Do not change load order yet. - -1. Keep the current eager =init.el= order. -2. Create/maintain =docs/design/module-inventory.org= as a living inventory - with: - - module name, - - category, - - eager/deferred target, - - known runtime dependencies, - - top-level side effects, - - tests that cover standalone load or command behavior. -3. Annotate every module required by =init.el= with the module header standard. -4. Convert vague comments in =init.el= into tasks or remove them: - - =latex-config= "WIP need to fix", - - =prog-shell= "combine elsewhere", - - "Modules In Test" section. -5. Add lightweight standalone-load smoke tests for the lowest-level modules. - -Inventory rules: - -- The module table in this spec seeds the inventory. -- =docs/design/module-inventory.org= is the living per-module truth after Phase - 1 starts. -- Every module required by =init.el= must be represented before Phase 2 starts. -- Discoveries during later phases update the inventory. -- This inventory is independent from the helper inventory owned by - [[file:utility-consolidation.org][utility-consolidation.org]]. - -Exit criteria: - -- Every module required by =init.el= has a category and target load shape. -- Every eager survivor has a documented reason. -- The inventory identifies top-level timers/process/network-ish side effects. -- Every module required by =init.el= has the required load-graph header lines. - -** Phase 2: Explicit Dependencies - -Still do not significantly change startup behavior. - -1. For each module batch, load it directly in batch mode. -2. Fix hidden dependencies by adding real =require=, =autoload=, or package - boundaries. -3. Remove production shims that only exist because tests load modules in an - incomplete environment. -4. If a keymap dependency is hidden, document it and make the dependency - explicit with =require= or =autoload=. Do not refactor into the registration - convention until Phase 3. When the hidden dependency is on - =cj/custom-keymap= itself, add =(require 'keybindings)= to the consuming - module; Phase 3 replaces these direct dependencies with the registration - API. -5. When a hidden dependency is really a duplicated generic helper, either: - - hand the extraction to the utility-consolidation sibling project when it - is in scope there, or - - leave it in place and record it under that project. - -Suggested order: - -- Foundation and libraries. -- Text/editing command modules. -- UI modules. -- Programming modules. -- Org modules. -- Optional integrations. - -Exit criteria: - -- Direct module load either succeeds or fails with a clear missing external - package/config message. -- =make test-file FILE=test-all-comp-errors.el= passes. -- New tests cover any helper extracted while fixing dependencies. -- Helper extraction remains dependency-light and does not pull heavy packages - into foundation startup. - -** Phase 3: Keymap Registration Boundary - -Introduce a small keymap registration API before deferring many feature modules. - -Possible API: - -#+begin_src emacs-lisp -(defun cj/register-prefix-map (key map label) - "Register MAP under KEY in `cj/custom-keymap' with LABEL for which-key." - ...) - -(defun cj/register-command (key command label) - "Register COMMAND under KEY in `cj/custom-keymap' with LABEL for which-key." - ...) -#+end_src - -Design rules: - -- =keybindings.el= owns =cj/custom-keymap= and the global =C-;= binding. -- Feature modules may define maps and commands without mutating global keys - directly. -- Which-key labels must be registered after which-key loads. -- Tests can assert key resolution without loading every feature package. - -Exit criteria: - -- Modules no longer need to assume =cj/custom-keymap= exists at top level - except through the registration API. -- Existing =C-;= bindings continue to resolve. -- Which-key labels for documented prefixes remain available. - -** Phase 4: Defer Low-Risk Optional Modules - -Start with modules that are unlikely to affect first-frame startup. - -Candidate batch: - -- =games-config= -- =music-config= -- =weather-config= -- =gloss-config= -- =lorem-optimum= -- =jumper= -- =httpd-config= -- =prog-training= - -For each module: - -1. Keep its user-facing command/key available via the default deferral mechanism - above. -2. Move package loading into =use-package :commands=, =:hook=, =:mode=, or an - explicit autoload/wrapper only when the default does not fit. -3. Run targeted tests and an interactive smoke check. - -Exit criteria: - -- Startup no longer requires the module eagerly. -- User command still works from a fresh Emacs session. -- Module-specific tests pass. - -** Phase 5: Defer Heavy Domain Modules - -Candidate batch: - -- =pdf-config= -- =calibredb-epub-config= -- =video-audio-recording= -- =transcription-config= -- =mail-config= -- =ai-config= -- =restclient-config= -- =elfeed-config= -- =erc-config= -- =slack-config= - -These need more care because they often combine package setup, auth, keymaps, -processes, hooks, and user workflows. - -Exit criteria for each: - -- Commands are discoverable before package load. -- Package load happens through the default deferral mechanism: command, hook, - mode, or explicit startup opt-in. -- Auth and private config are not read until necessary unless the user opts in. -- Batch/test startup does not start network/process work. - -Private config opt-in follows the =calendar-sync.local.el= precedent: a module -reads =<module-name>.local.el= when readable, the file is gitignored, and the -module degrades cleanly when the file is missing. Token rotation is a separate -security task; this convention is about config presence, not secret protection. - -** Phase 6: Revisit Org And Programming Eagerness - -Org and programming modules are daily-use, so the goal is not blindly deferring -everything. - -Programming target: - -- Keep generic programming defaults and F-key command entry points available. -- Load language-specific modules by major mode. -- Consolidate generic LSP policy under =prog-lsp=. - - Move to =prog-lsp=: global LSP toggles such as =lsp-idle-delay=, - =lsp-log-io=, =lsp-enable-folding=, =lsp-enable-snippet=, - =lsp-headerline-breadcrumb-enable=, and file-watch ignore lists. - - Keep per-language: server client settings such as - =lsp-clients-clangd-args= and =lsp-pyright-*=, plus language-mode hook - wiring. -- Tree-sitter grammar auto-install is always on; the project policy is global - allow. =treesit-auto-install= is =t= without per-language conditionals. - -Org target: - -- Keep these daily first-session workflows eager: =org-config=, - =org-agenda-config=, =org-capture-config=, =org-refile-config=, - =calendar-sync= when local config is present, and =org-roam-config=. -- Defer exporters, reveal, drill, noter, webclipper, and optional publishing - pieces behind commands/hooks. -- Normalize agenda/refile cache lifecycle before changing timer behavior. This - is behavioral normalization within the load-graph project; the shared - =cj-cache.el= extraction is owned by utility-consolidation Phase 5 and may - follow. - -The =prog-lsp= consolidation and tree-sitter policy decisions are owned by this -load-graph project. Utility consolidation owns reusable helper extraction, not -programming policy. - -Exit criteria: - -- Common daily Org/programming workflows work from a fresh session. -- Optional exporters/languages load when used. -- Timers are guarded in batch/test contexts. - -* Adjacent Project: Utility Consolidation - -The review of this spec identified a related but distinct architectural -problem: helper functions are scattered across feature modules, sometimes with -duplicated behavior. This matters to the load graph because modules can become -coupled to whichever feature file happened to define a useful helper first. - -This should be tracked as a sibling project, not folded into the load-graph -project. The load-graph project asks "when and why does this module load?" The -utility consolidation project asks "which module should own this reusable -behavior?" Those questions overlap, but their changes have different risk and -rollback shapes. - -This sibling project can run beside Phase 2. When explicit-dependency work finds -a generic duplicated helper, the sibling project owns the extraction commit when -the helper is in scope for that project. See -[[file:utility-consolidation.org][utility-consolidation.org]] for candidate -helpers, naming rules, dependency budgets, migration phases, and test policy. - -* Testing Strategy - -** Static/Batch Tests - -Add or extend tests for: - -- Direct module load smoke tests for modules in each batch. -- Header validation: every module required by =init.el= declares the seven - required load-graph header lines. - - Test file: =tests/test-init-module-headers.el=. - - Assertion shape: inspect every module required by =init.el=, read its - commentary header, and fail with the missing line names for any absent - required header line. -- Keymap registration: prefix maps and commands resolve without requiring the - feature implementation package. -- No startup timers/processes in batch for side-effect modules. -- =init.el= startup smoke in batch, where possible. -- Byte/native compile smoke via existing =test-all-comp-errors.el=. - -Test files for this project use =test-init-<feature>.el=, for example -=test-init-module-headers.el= and =test-init-keymap-registration.el=. This keeps -load-graph validation tests distinct from per-module unit tests. - -Header validation runs directly against module files. It does not depend on the -final =docs/design/module-inventory.org= format, which remains a Phase 1 -authoring decision. - -** Automated Smoke Checks - -Automate every smoke item that can run in batch: - -- Important keybindings resolve to the intended command symbols, including - =C-;= prefixes and F4/F6/F7 entry points. -- Org capture and agenda command entry points load or produce expected - batch-safe guidance. -- Calendar sync status reports configured/no-config state without starting - timers or network fetches in batch. -- Optional commands touched in the batch autoload and resolve. -- Non-graphical interactive flows use =execute-kbd-macro= or - =with-simulated-input= where practical. - -These checks should run under =make test= for every migration commit. - -** Manual Smoke Checks - -Each migration batch should be followed by an interactive restart and checklist: - -- First frame appears with expected theme/font/modeline. -- =C-;= prefix appears and key descriptions are present. -- Magit opens. -- Mail command opens or gives expected package/config guidance. -- Refile target lookup works in an interactive session. -- Any optional command changed in the batch runs end to end. -- If daemon mode is part of normal use, run the visual checklist once via - regular =emacs= and once via =emacsclient= against a running daemon. - -** Performance Checks - -Before and after major batches: - -- Record =emacs-init-time=. -- Record a startup profile baseline and diff, preferably with =benchmark-init= - if enabled for the phase. -- =benchmark-init= is installed via package.el. The activation block in - =early-init.el= is commented; uncomment it locally during phases that need - profiling and do not commit the activation. Profile output goes to - =.profile/=, which should stay gitignored. -- Suggested workflow: - - =make profile-baseline= records =emacs-init-time= and a startup profile to - =.profile/baseline.txt=. - - =make profile-diff= records the current run and compares it to the phase - baseline. -- Keep a simple note of eagerly loaded feature count from - =cj/info-loaded-features= or equivalent. - -Performance is a supporting signal. Correctness and explicit dependencies are -the primary acceptance criteria. Startup regressions larger than roughly 50 ms -against the phase baseline should be investigated and explained; after several -stable baseline runs, this can become a stricter gate. - -* Acceptance Criteria - -The project is complete when: - -- =init.el= contains only documented eager requires and explicit startup calls. -- Optional modules no longer load merely because Emacs started. -- Each module required by =init.el= has a category and eager/deferred rationale. -- Modules that remain eager have no hidden dependencies on arbitrary earlier - init order. -- Global key registration has a central owner/convention. -- Top-level timers/process/network work is either removed, guarded, or - documented as intentional. -- Full =make test= passes. -- Byte/native compile smoke passes. -- Interactive startup checklist passes. - -* Risks And Mitigations - -** Risk: Breaking muscle-memory keybindings - -Mitigation: - -- Change key registration mechanics before changing bindings. -- Add keymap resolution tests for important prefixes. -- Keep a per-batch manual keybinding checklist. - -** Risk: Lazy-loaded packages miss early hook setup - -Mitigation: - -- Prefer =use-package :hook= and =:mode= over ad hoc lazy command bodies for mode - packages. -- Add tests that inspect hook contents where possible. -- Smoke-test opening representative files. - -** Risk: Daily workflows silently stop starting - -Mitigation: - -- Distinguish "safe default" from "local opt-in" for workflows like calendar - sync. -- Use ignored/local config files for private eager opt-ins. -- Report missing config clearly. - -** Risk: Batch tests differ from interactive startup - -Mitigation: - -- Guard timers/process/network work with =noninteractive= only when that is the - intended distinction. -- Add at least one interactive checklist per migration batch. - -** Risk: Refactor becomes too broad - -Mitigation: - -- One batch, one module family. -- Do not mix dependency fixes, keybinding redesign, and package lazy-loading in - the same commit unless tightly coupled. -- Keep rollback easy by preserving user-facing commands and using wrappers. - -* Implementation Backlog - -The project in =todo.org= should remain the source of task state. This design -supports these implementation tickets: - -1. Classify modules by role and startup requirement. -2. Add explicit module dependencies before changing load order. -3. Centralize custom keymap registration. -4. Defer low-risk optional modules. -5. Defer heavy document/media/integration modules. -6. Revisit programming module LSP/tree-sitter ownership. -7. Revisit Org module cache/timer and optional extension loading. -8. Retire or rewrite stale =init.el= comments. -9. Create a sibling utility consolidation project with an inventory pass and - first helper extractions. - -* Open Questions - -- Should =config-utilities= remain eager because debug commands are useful - during startup work, or should it become command-loaded after this project? -- Should local/private opt-ins share one file, or should modules keep - workflow-specific local files such as =calendar-sync.local.el=? -- Should the module inventory become machine-readable for validation, or is an - org table enough? Decide during Phase 1 based on inventory authoring - experience. -- Should =init.el= ultimately become declarative sections plus an explicit - startup contract list? - -* Next Steps - -1. Use this document as the reference for the =Classify modules by role and - startup requirement= task. -2. Build the first inventory directly from the module table above, correcting - category guesses while inspecting each file. -3. Do not defer a module until its direct runtime dependencies are explicit. -4. Implement keymap registration before deferring feature modules that currently - mutate =cj/custom-keymap= at top level. -5. Create the sibling utility consolidation project before Phase 2 work begins, - so duplicated helpers found during dependency cleanup have a clear place to - land. |
