| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
| |
prog-general uses code-dir, projects-dir, and snippets-dir (all from user-constants) in its projectile and yasnippet configs, but it only declared them as compile-time defvars and leaned on init.el loading user-constants first. That load-order assumption broke when the test suite ran combined: if another test file loaded prog-general before user-constants, yasnippet's :config hit a void snippets-dir and yas-global-mode never enabled, so eight yas-activation tests failed.
I replaced the three compile-time defvars with a runtime (require 'user-constants) so the module declares its real dependency and is self-contained. The combined prog-general suite is now 15/15, and the module no longer depends on init.el's load order for constants it reads at load time.
|
| |
|
|
| |
popper-config was use-package :disabled t, so use-package elided the whole form and none of it ran. It was a no-op sitting in the load graph, required only from init.el. I removed the module and its require. No behavior change. validate-modules passes and init still loads clean. The config stays in git history if popper is ever wanted again.
|
| |
|
|
|
|
| |
I added cj/open-project-daily-prep on C-c p d. It opens inbox/today-prep.org under the Projectile project root in another window, project-scoped, so a project without a prep file just reports it rather than erroring. The binding had only ever been eval'd live into the daemon in a past session and vanished on the next restart, so this persists it to the module.
Freeing d meant reworking the deadgrep bindings. deadgrep-in-dir moves to C-c p G (replacing plain deadgrep, which stays M-x-callable), and deadgrep-here keeps C-c p g. Plain project-wide deadgrep dropped off the projectile prefix because it overlapped the context-aware and arbitrary-directory variants. Tests cover the open, missing-file, and not-in-a-project paths.
|
| |
|
|
|
|
| |
mu4e's main view displays with a display-buffer-full-frame action, which tears down every other window on launch, so opening mu4e from a split collapsed the layout. mu4e's own mu4e-display-buffer docstring points to display-buffer-alist as the supported override.
I added an entry routing the *mu4e-main* buffer to the current window (reuse a window already showing it first, then same-window), so launching mu4e in a split keeps the rest of the layout intact. It's registered eagerly rather than inside mu4e's deferred config so it applies on the first launch. Tests cover the entry registration and that the main buffer no longer collapses a split under mu4e's full-frame action.
|
| |
|
|
|
|
| |
slack-buffer-function defaulted to switch-to-buffer-other-window, which gives no real guarantee about placement: with three or more panes it picks a least-recently-used window, and it offers nothing that keeps Slack out of the window point is in. So opening a room in a split could land it wherever, including over the buffer I was working in.
I set slack-buffer-function to cj/slack--display-buffer, a pop-to-buffer call with inhibit-same-window and a reuse / use-some-window / pop-up-window action list. In a split it reuses one of the other windows and leaves the selected window alone; with a lone window it splits. Tests cover both the split-placement case and the selected-window-preserved invariant.
|
| |
|
|
| |
The right-aligned tags stopped one column short of the edge, which kept the line from wrapping but left no room for the folded-heading ellipsis (org-ellipsis, " ▾") that org appends after the tags. The ▾ overflowed onto its own line and showed up as a stray triangle between headings. I widened the margin to 5 columns so the tag and the trailing ellipsis both stay on the heading line.
|
| |
|
|
|
|
| |
org-tags-column only right-aligns tags to a fixed column, and it does so by baking literal spaces into the file, so it can't track window width or splits. I set it to 0 (org keeps a single space, no padding) and add a font-lock rule that stretches that space with a display property pinned to the window's right edge via :align-to. That value resolves at redisplay, so the tags follow the window width and adapt to splits live, and nothing alignment-specific is written to disk.
The tags stop one column short of the edge on purpose. A glyph filling the final column wraps a non-truncated line, which dropped every tagged heading's tags onto the next line in the first cut. org-agenda-tags-column gets 'auto, the native equivalent for agenda views. Tests cover the heading regexp and the align-spec helper.
|
| |
|
|
|
|
| |
The prettify-symbols alist already mapped #+begin_src and #+end_src to a lambda, but only some markers actually rendered as one. prettify-symbols-default-compose-p decides composition from a syntax heuristic on the characters around the match, and inside org's src-block fontification that heuristic vetoed most of the markers. In todo.org only one of five composed.
I added cj/prettify-compose-block-markers-p, a compose predicate that always composes the block markers (case-insensitive, since the alist carries upcased variants) and defers to the default for everything else like lambda. Every marker composes now. Tests cover the marker branch and the deferral to the default.
|
| |
|
|
|
|
|
|
| |
The butterfly banner is a transparent PNG. On this X11 build Emacs composites image alpha against one background color and caches the flat pixmap. So when auto-dim remaps a non-selected dashboard's background to near-black, the cached image keeps its old composite and the transparent edges show as a lighter rectangle.
I exempted the *dashboard* buffer from dimming through the fork's never-dim-buffer hook, so its background never shifts. Live alpha compositing would need a pgtk build, which is out because of its fractional-scaling input lag, and every theme-level workaround changes dimming for all buffers. Scoping the exemption to one short-lived buffer is the narrow fix. The trade is no focus cue when the dashboard is shown in a split.
I also dropped the :mask heuristic prop from the prior banner commit. The PNG already carries a real alpha channel, so heuristic masking was the wrong tool. Once the background is stable, the native alpha over the theme background reads clean on its own. I added Normal/Boundary/Error tests for the predicate.
|
| |
|
|
|
|
|
|
| |
cj/verify-or-create-dir and cj/verify-or-create-file caught every creation failure and only messaged it, so a broken environment for a path the config actually needs stayed quiet until some later module failed in a more confusing way. I gave both an optional required flag and routed failures through a shared cj/--report-path-failure: a required failure raises a prominent display-warning, an optional one is still just logged so it never blocks startup.
The initializer now groups its paths by that distinction. Required: the backbone directories (sync, org, roam) and the calendar stubs (gcal/pcal/dcal), since org-agenda-list hangs prompting for those when they're missing. Optional: the secondary dirs and the content files, each populated by its own workflow. I went with a warning rather than a user-error for required failures so a directory hiccup surfaces loudly without aborting init.
Added error-path tests: an optional failure logs and never warns, and a required dir or file failure raises a user-constants warning.
|
| |
|
|
|
|
|
|
| |
(require 'user-constants) created ~8 directories and ~10 org/calendar files at load time, via a top-level dolist for the calendar stubs and a top-level call to cj/initialize-user-directories-and-files. That meant any bare require — tests, byte-compile, batch tools — wrote to disk. It's why a stray sync/org/ tree kept appearing in the repo during test runs.
I removed both top-level forms and folded the gcal/pcal/dcal creation into the initializer. The path defconsts stay exactly as they were, so every consumer that just reads a path is unaffected. init.el now calls the initializer right after requiring the module, guarded by (unless noninteractive), so interactive and daemon startup create everything in the same order as before while a bare require stays side-effect-free.
Added tests/test-user-constants.el: loading the module creates nothing, and the initializer creates the backbone dirs and the configured files. Updated the module header — top-level side effects are now none and it's safe to load in tests.
|
| |
|
|
| |
custom-file points at a throwaway temp file so Customize edits never persist — deliberate, since config lives in Elisp, but silent. A user who clicks "Save for Future Sessions" loses the edit on exit with no hint. I advise custom-save-all (the chokepoint both customize-save-variable and the Customize button funnel through) with a one-shot :before warning that explains the discard and points at the Elisp init files. The advice removes itself after firing, so it warns once per session, and the body never runs at load, so startup stays quiet.
|
| |
|
|
| |
The eshell SSH-jump aliases (gocj, gosb, gowolf) were hardcoded inline in the alias setup, which tied the module to my machines. I moved them into a cj/eshell-ssh-hosts defcustom (an alias→remote-path alist, defaulting to my current hosts) and build the aliases by iterating it. A different machine can override the variable or set it to nil instead of editing the module. Extracted a pure cj/--eshell-ssh-alias-commands helper so the alias construction is testable without a live eshell.
|
| |
|
|
|
|
| |
Two font-config robustness fixes. The font-browser (cj/display-available-fonts) hardcoded a "Light Blue" foreground for each family label, which goes nearly unreadable on a light theme. I switched it to font-lock-keyword-face so the label follows the theme's contrast, keeping it bold.
The emoji-fontset cond ran once at module load behind (env-gui-p). In daemon mode there's no GUI frame at load, so env-gui-p is nil and the fontset never gets set — a later emacsclient -c GUI frame then has no emoji font. I wrapped it in cj/setup-emoji-fontset (GUI-guarded, idempotent) and, mirroring how the fontaine preset is already applied, run it from server-after-make-frame-hook in daemon mode and directly otherwise. The daemon TTY-then-GUI path can't be exercised in batch, so I left a manual-test entry for it.
|
| |
|
|
|
|
|
|
| |
cj/json-format-buffer, cj/yaml-format-buffer, and cj/webdev-format-buffer ran their formatters through shell-command-on-region, which goes via a shell. I moved each to call-process-region with an explicit program and argv list, so a filename or buffer content can't be word-split or read as shell syntax. The webdev path dropped its shell-quote-argument dance once the filename became a plain argv element.
Point preservation is unchanged. One deliberate improvement, and it's tested: shell-command-on-region with replace replaced the buffer with the formatter's error text on a non-zero exit. The new per-formatter helper captures output to a temp buffer, checks the exit code, replaces only on success, and otherwise raises a user-error carrying stderr — so a failed format leaves the buffer alone.
I kept a small format-region helper in each of the three modules rather than one shared helper. They have no common module to live in short of system-lib, and coupling three unrelated domain modules through it wasn't worth saving sixteen lines.
|
| |
|
|
|
|
| |
mouse-trap--build-keymap ran on every major-mode hook and rebuilt the whole keymap (~8 prefixes by ~30 events) from scratch each time, so rapid mode-switching paid that cost over and over. I moved the build into mouse-trap--build-keymap-1 and cache its result in mouse-trap--keymap-cache, keyed on the profile name plus its allowed-categories list. The same profile reuses the cached keymap, and editing a profile's categories changes the key so it rebuilds.
Sharing one keymap object across buffers is safe here: the map only binds disallowed events to ignore and is never mutated after it's built. Added mouse-trap--clear-keymap-cache to force a fresh build after editing profiles by hand.
|
| |
|
|
|
|
|
|
|
|
|
| |
Four export/publishing commands shelled out to external tools without checking they exist, so a missing tool surfaced as an opaque process error — or, for reveal.js, a silently broken presentation. I added a command-time guard to each that names the tool and what's needed:
- zathura, in the pandoc PDF export-and-open command
- the hugo binary and the platform file-manager opener, in hugo-config
- the local reveal.js checkout (run scripts/setup-reveal.sh), shared by the reveal export and preview commands
- pandoc, in the web-clip protocol handler
The checks run only when the command runs, so startup stays quiet. Each guard has a test asserting the user-error fires when the tool is absent, and the existing happy-path tests now stub the lookups so they exercise the real path rather than tripping the new guard.
|
| |
|
|
|
|
| |
The fallback kicks in when persist/emacs-theme is missing — a fresh machine, or one that's never saved a theme. It was modus-vivendi, which ships with Emacs but has none of the dimming colors this config chooses, so an unconfigured machine looked and dimmed differently from a configured one. I hit exactly that on a second box this week.
dupre is bundled in themes/ and carries those colors, and it loads wherever this config does, so it's the better default. I added a regression test asserting the default is dupre; its loadability is already covered by test-dupre-theme.el. The docstring no longer claims the fallback must be a built-in theme, since dupre isn't one.
|
| |
|
|
|
|
| |
cj/--latex-select-pdf-viewer runs on every LaTeX-mode buffer and was blindly pushing an (output-pdf VIEWER) entry onto TeX-view-program-selection, so each LaTeX buffer opened in a session stacked another duplicate. The head still won, so the viewer worked, but the list grew and the docstring's idempotency claim was false. I drop any existing output-pdf entry before consing the chosen viewer, which also makes "wins over any default" actually true.
Added a test file (the module had none) covering selection, preference order, the PDF-Tools fallback, idempotency, and default override, with executable-find mocked so the run doesn't depend on which viewers are installed.
|
| |
|
|
|
|
| |
The dashboard banner showed the butterfly PNG without its transparency, because the image descriptor was built before the mask props were set. I moved the banner setup (dashboard-startup-banner, dashboard-banner-logo-title) ahead of dashboard-setup-startup-hook and added dashboard-image-extra-props with :mask heuristic, so the mask is in place when the startup hook builds the buffer. If a *dashboard* buffer already exists I refresh it, so the change shows without a restart.
I also re-encoded the PNG smaller and kept the previous encoding as M-x_butterfly.png.bak.
|
| |
|
|
|
|
|
|
| |
Window dimming via face-remap never reached vterm. The terminal resolves its own colors per cell while redrawing, so it bypasses the remapped faces, and agent and shell windows stayed bright when they lost focus.
I advise vterm--get-color to blend each looked-up color toward the auto-dim faces whenever every window showing the buffer is dimmed. The foreground and background blend amounts are separate defcustoms (foreground stays more legible, background fades harder). After a dim-state change I force a full vterm repaint by briefly nudging the terminal size, because vterm only repaints the rows libvterm marked dirty. A post-command hook and a select-window advice cover the windmove and Shift-arrow focus paths that window-selection-change-functions misses.
Tests cover the dimmed-buffer predicate, the color blend, the selection-change scheduling, and the auto-dim-before-repaint ordering.
|
| |
|
|
|
|
|
|
| |
The dupre theme defined its own faces (dupre-accent, the headings, and the org status faces) only through custom-theme-set-faces, never defface. That leaves them unregistered, so they render through :inherit but silently fail when applied directly as a text property. org-todo-keyword-faces and org-priority-faces apply faces that way, so the org keyword and priority colors never showed as dupre tones.
I added a defface registration block to dupre-faces.el for all of dupre's own faces, so they're real faces. The theme still sets their colors. Then I pointed org-todo-keyword-faces and org-priority-faces (in org-config.el) at named dupre-org-* faces, each the closest palette color to its former hard-coded name, and gave each a dimmed variant that auto-dim-config.el swaps in for unfocused windows. A keyword in a dimmed window now shows a darker shade of its own color rather than flat gray or full brightness.
A regression test asserts dupre's faces stay registered, since that was the latent bug behind all of this.
|
| |
|
|
|
|
|
|
| |
I added auto-dim-config, a module that loads my local auto-dim-other-buffers fork and dims windows that don't have focus so the selected window stands out. A non-selected window drops to a pure-black background with faded gray text. The dimmed faces live in the dupre theme (themes/dupre-faces.el) so they track theme switches, and the module remaps default, the font-lock faces, and org-block onto them so syntax-highlighted code fades too rather than staying lit. Fringe is left out because dimming it forces a full-frame refresh that flickers on this non-pgtk build.
dim-on-focus-out is nil, so tabbing to a browser or terminal on Hyprland doesn't dim the whole frame. vterm and agent windows don't dim either, because the terminal paints its own per-cell colors past the face remap. I'm keeping that, since the agent's output stays readable while I work in code on the other side.
The module loads after the theme, carries a load-graph header, joins the header-contract allowlist, and the inventory moves to 103 of 103 classified.
|
| |
|
|
|
|
| |
elfeed-config was the only init module without a load-graph header. It was deferred because annotating the header triggers a byte-compile, which broke its tests. With that test rewritten to use real structs, I added the header (Layer 4, optional, currently eager but a command-loaded deferral candidate, runtime requires user-constants, system-lib, media-utils), added elfeed-config to the header-contract allowlist, and moved it from the inventory's deferred and pending sections into the Batch 8 table.
That brings the inventory to 102 of 102 modules classified, completing the Phase 1 classification pass.
|
| |
|
|
|
|
| |
C-c p t (cj/open-project-root-todo) called find-file, which always opened todo.org in the selected window, replacing whatever I was looking at. Now it opens in the other window when the frame is split and in the current window when it isn't, through a small cj/--find-file-respecting-split helper. The helper is a top-level defun rather than buried in the projectile :config block so it can be unit-tested without loading projectile.
I left cj/project-switch-actions alone. Opening the todo on a project switch is a different trigger and not what this fixes.
|
| |
|
|
|
|
| |
F9 split a third window into a frame that was already divided in two, wedging the agent into the middle or a skinny extra column instead of taking the half it should occupy. The display rule only knew how to reuse a window already showing an agent or to split a fresh one. With a plain two-pane layout it fell through to the split and added a window.
I added a display action, cj/--ai-vterm-reuse-edge-window, that reuses the window already forming the target half (the right column on a desktop, the bottom row on a laptop), found by a new cj/window-at-edge helper. It records the displaced buffer with display-buffer-record-window, so toggling off restores that buffer through the native quit-restore-window. The slot's buffer swaps between the agent and whatever it displaced, and no window is created or deleted. The split path still handles a single-window frame or a layout split on the other axis, and the lone fullscreen agent keeps its bury-and-restore-in-place behavior.
|
| |
|
|
|
|
| |
Migrated all 31 cj/custom-keymap registration sites across 24 modules from direct (keymap-set cj/custom-keymap ...) calls to cj/register-prefix-map and cj/register-command. Consumers no longer reference cj/custom-keymap directly, so keybindings.el is the sole owner of the C-; prefix and modules reach it only through the API (each already requires keybindings from Phase 2).
Behavior-preserving: I dumped every C-; binding before and after the migration and they're identical: 279 bindings, each resolving to the same command. The which-key label blocks are untouched, since they use string key descriptions and never assumed the keymap existed. I byte-compiled all 24 files (no new free-variable warnings, because the cj/custom-keymap references are gone), and make test, validate-modules, and an init load all pass.
|
| |
|
|
|
|
| |
Phase 3 of the load-graph project. cj/register-prefix-map and cj/register-command bind a prefix map or command under the C-; prefix and register the which-key label once which-key loads. Feature modules will route their registration through these instead of mutating cj/custom-keymap directly, so keybindings.el stays the sole owner of the prefix and modules stop assuming the keymap already exists at load.
Adds test-init-keymap-registration.el covering prefix-map and command resolution, the optional label, and invalid-key rejection. No modules are migrated yet; that follows in batches.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Phase 2 of the load-graph project. I fixed the seven hidden dependencies the classification surfaced, so each module declares what it uses instead of relying on init order.
- system-defaults now requires host-environment and user-constants at runtime. They were eval-when-compile only, but env-bsd-p and user-home-dir are read at load, so the compiled module couldn't load standalone.
- custom-buffer-file, dev-fkeys, calendar-sync, and video-audio-recording require keybindings and drop their (when (boundp 'cj/custom-keymap) ...) shims. The shim silently dropped the C-; binding when the module loaded before keybindings. The explicit require makes the dependency real.
- flycheck-config and mail-config require keybindings for their cj/custom-keymap bindings (a use-package :map and a direct keymap-set).
- Removed a dead eval-when-compile (defvar cj/custom-keymap) in transcription-config; nothing there used the variable.
No init.el load-order change. keybindings and the foundation modules already load before these, so the requires are no-ops at startup and only fix standalone and test loading.
I verified each fix with a fresh emacs --batch (require 'X), then swept all modules standalone: every one loads or fails only with a clear missing-package message. Full make test, make validate-modules, and an init smoke all pass. Module headers and the inventory's hidden-dependency section are updated to mark the seven resolved.
|
| |
|
|
|
|
| |
Final classification batch: the last 19 modules — linear-config, local-repository, lorem-optimum, mail-config, markdown-config, music-config, pdf-config, quick-video-capture, reconcile-open-repos, restclient-config, slack-config, system-commands, telega-config, tramp-config, transcription-config, video-audio-recording, vterm-config, weather-config, wrap-up. I annotated each header, added a Batch 9 table to the inventory, and extended the validation allowlist. 101 of 102 modules are now classified; only elfeed-config remains, deferred on its test fix.
Two more hidden dependencies turned up. video-audio-recording uses the boundp shim for its C-; r binding, and mail-config registers C-; e directly without requiring keybindings, so it errors standalone rather than degrading. Both recorded for Phase 2.
|
| |
|
|
|
|
|
|
|
|
| |
Eighth classification batch: 17 domain/integration/optional modules — ai-config, ai-vterm, browser-config, calendar-sync, calibredb-epub-config, chrono-tools, dirvish-config, dwim-shell-config, erc-config, eshell-config, eww-config, flyspell-and-abbrev, games-config, gloss-config, httpd-config, jumper, latex-config. I annotated each header, added a Batch 8 table to the inventory, and extended the validation allowlist. 82 of 102 modules are now classified.
Almost all are eager only by init order and become command/hook/mode-loaded. calendar-sync stays eager when its .local.el is present. One new hidden dependency: calendar-sync guards its C-; g registration with a boundp shim and doesn't require keybindings, so the binding drops standalone.
I deferred elfeed-config rather than annotate it. Its header edit triggers byte-compilation, and the existing tests only pass when the module loads as interpreted source — the compiled cj/elfeed-process-entries inlines an elfeed struct accessor the stubs can't intercept, and the batch test environment has no elfeed package to build real structs. It needs its tests rewritten first, recorded in the inventory and a new todo task.
Also made the header allowlist scoping test durable: it used games-config (now classified) as its unclassified example; switched to a sentinel name plus a duplicate-entry guard.
|
| |
|
|
|
|
| |
Seventh classification batch: the thirteen Org modules — config, agenda, babel, capture, contacts, drill, export, noter, refile, reveal, roam, webclipper, hugo. I annotated each header, added a Batch 7 table to the inventory, and extended the validation allowlist. 65 of 102 modules are now classified.
The daily workflows (config, agenda, capture, refile, roam) keep their eager reason per the spec's Phase 6 target. Babel and contacts move to after-load; export, reveal, drill, noter, webclipper, and hugo become command-loaded. The agenda and refile idle-timer caches are recorded as the side effects the spec already tracks for cache-lifecycle work. No new hidden dependencies.
|
| |
|
|
|
|
| |
Sixth classification batch: prog-general plus the language modules — prog-c, prog-go, prog-lisp, prog-python, prog-webdev, prog-json, prog-yaml, prog-shell, prog-training. I annotated each header, added a Batch 6 table to the inventory, and extended the validation allowlist. 52 of 102 modules are now classified.
prog-general owns the shared defaults and tree-sitter/LSP policy and stays eager. The language modules are eager only by init order and should load by major mode, so they're tagged Phase 6 deferral candidates. prog-shell's after-save executable hook is the one side effect worth scoping. No new hidden dependencies.
|
| |
|
|
|
|
| |
Fifth classification batch: the development-workflow entry points and package config — coverage-core, coverage-elisp, dev-fkeys, diff-config, help-config, help-utils, flycheck-config, test-runner, vc-config. I annotated each header, added a Batch 5 table to the inventory, and extended the validation allowlist. 42 of 102 modules are now classified.
Two more hidden dependencies turned up, both about cj/custom-keymap. dev-fkeys repeats the custom-buffer-file boundp shim for its C-; P binding. flycheck-config binds (:map cj/custom-keymap ...) through use-package without requiring keybindings, so it fails to load standalone. Both recorded for the Phase 2 dependency pass.
|
| |
|
|
|
|
| |
Fourth classification batch: the modules that shape the first interactive frame — ui-config, ui-theme, ui-navigation, font-config, selection-framework, modeline-config, mousetrap-mode, popper-config, dashboard-config, nerd-icons-config. I annotated each header, added a Batch 4 table to the inventory, and extended the validation allowlist. 33 of 102 modules are now classified.
These mostly stay eager: each has a real first-frame reason (theme, font, modeline, completion stack, landing page). No new hidden dependencies. popper-config carries the spec's open question about its enabled/disabled state, noted for the deferral phase.
|
| |
|
|
|
|
| |
Third classification batch: the remaining core and library command modules from init.el's early block — external-open, media-utils, auth-config, keyboard-macros, system-utils, text-config, undead-buffers. I annotated each with the load-graph header contract, added a Batch 3 table to the inventory, and extended the validation allowlist. 23 of 102 modules are now classified.
No new hidden dependencies in this batch. auth-config stays eager because other modules need credentials early; the command libraries (external-open, media-utils, keyboard-macros) are eager only by init order and flagged as Phase 4 deferral candidates.
|
| |
|
|
|
|
| |
Second classification batch: the nine custom-* text/editing command helpers (case, comments, datetime, buffer-file, line-paragraph, misc, ordering, text-enclose, whitespace). I annotated each with the load-graph header contract and added a Batch 2 table to the inventory. They're all Layer 2, eager only to register a C-; submap at load, with no necessary eager reason, so all are Phase 3/4 deferral candidates.
The inventory records a second hidden dependency for Phase 2: custom-buffer-file guards its C-; b registration with (when (boundp 'cj/custom-keymap) ...) and declares the keymap only via eval-when-compile, so the binding silently drops when the module loads without keybindings.
|
| |
|
|
|
|
| |
I started the init.el load-graph classification with the foundation batch. I added docs/design/module-inventory.org as the living per-module inventory and annotated the seven foundation modules (system-lib, user-constants, host-environment, system-defaults, keyboard-compat, keybindings, config-utilities) with the load-graph header contract: layer, category, load shape, eager reason, top-level side effects, runtime requires, and direct-test-load safety.
I changed no load order, so init.el keeps its current eager order. The inventory records one hidden dependency for Phase 2: system-defaults uses host-environment and user-constants symbols at load while declaring them eval-when-compile, so the compiled module cannot load standalone.
|
| |
|
|
|
|
|
|
| |
cj/youtube-to-elfeed-feed-format called url-retrieve-synchronously with no timeout, so a hung YouTube request would block Emacs indefinitely, and it only killed the temporary URL buffer when an ID was successfully extracted — a page without the expected markers leaked the buffer.
Passed cj/elfeed-url-fetch-timeout (10s) to the synchronous fetch, and moved the fetch+parse into an unwind-protect that always kills the temp buffer (live-p guarded), including the parse-failure path. Tests mock the network boundary and cover a normal channel parse, that a timeout is passed, and that the buffer is not leaked when parsing fails.
Also added tests for the EWW user-agent advice (no code change): it already injects the desktop UA only from eww-mode buffers, so package.el and other non-EWW url callers pass through untouched — the tests pin that scoping and the replace-not-duplicate header behavior.
|
| |
|
|
|
|
| |
org-export-config.el set org-export-with-tasks twice in a row — first to ("TODO"), then to nil. The final value won (export no tasks), but the stale first assignment and its "export with tasks by default" comment contradicted it, so the intended policy was ambiguous on a read.
Removed the leftover ("TODO") line. nil is the deliberate default: it is the value that was already winning, its comment matches, and it sits with the adjacent "export without tags / section numbers by default" settings. Added a smoke test that fires the deferred ox :config and pins org-export-with-tasks to nil so a future flip is caught.
|
| |
|
|
|
|
| |
custom-ordering.el binds cj/org-sort-by-todo-and-priority (owned by org-config) and custom-text-enclose.el binds change-inner/change-outer (the change-inner package). Both work at runtime — org-config loads eagerly and text-config autoloads change-inner via use-package :commands — but byte-compiling either module standalone warned "not known to be defined", and the dependency was implicit.
Added declare-function for each so the compile is clean and the cross-module relationship is explicit at the top of the file. No autoload needed: the runtime autoload/eager-load already exists, so only the compiler needed telling. custom-buffer-file.el byte-compiles clean already, so it needed no change.
|
| |
|
|
|
|
| |
cj/move-org-branch-to-roam cut the subtree from the source buffer before writing the new roam file, so a failure in the demote/format/write/db-sync steps left the subtree gone from the source and not persisted anywhere — a destructive operation with no rollback.
Reordered so the node file is written and verified on disk before org-cut-subtree runs; a failed write now aborts with the source untouched. Added a no-clobber guard (refuse an existing target file) and a confirmation prompt for large subtrees (>= cj/move-org-branch-confirm-lines, 30) or buffers with unsaved changes. The source buffer is deliberately left modified and undoable rather than auto-saved, so the move stays reversible. New test drives the write-failure-preserves-source invariant via an unwritable roam dir; the existing creates-roam-file test gained the confirm mock.
|
| |
|
|
|
|
| |
The linear-emacs package was renamed to pearl (~/code/pearl, feature pearl, all symbols pearl-*). Swapped every linear-emacs-* reference to pearl-* across linear-config.el (the use-package form, :load-path, the 26 :commands, the api-key/default-team-id/org-file-path vars, and the lazy-key advice targets pearl--graphql-request-async and pearl-check-setup), the dashboard launcher, and the two test files.
Kept the Linear-domain naming intact, since pearl is just a client for the Linear service: the C-; L prefix, the cj/linear-* wrapper helpers, the "Linear" dashboard label, the api.linear.app authinfo host, and the data/linear.org synced file are unchanged. Verified the wiring in a live daemon — pearl loads, the team id and org-file path apply, and the key advice installs on both entry points.
|
| |
|
|
|
|
| |
The append/prepend/indent/dedent *-in-region-or-buffer commands each inlined the same (if (use-region-p) (region-beginning) (point-min)) / (region-end)/(point-max) block — four copies of the "operate on the region, else the whole buffer" contract. Extracted cj/--region-or-buffer-bounds as the single source of that decision and routed all four through it. Behavior is unchanged; the public-wrapper tests still pass.
This was the "extract a shared helper that decides the target range" option from the reconcile task. The sibling custom-ordering.el helpers (cj/--arrayify, cj/--unarrayify) already document an explicit (start end) contract accurately and are region-required by design, so they needed no docstring change — each pair now has one clear, consistent contract. Tests cover the helper for the region case, the no-region whole-buffer case, and an empty buffer.
|
| |
|
|
|
|
| |
The VC modeline cache keyed on (list file cj/modeline-vc-show-remote). If file was a symlink whose target moved to a different VC tree (shared drives, CI workspaces), the key was unchanged and the cache kept serving the old branch/state.
Added the resolved file-truename to the key, so a symlink re-pointed at a new target produces a different key and the cache refreshes. The extra file-truename is one stat per modeline refresh, cheap next to the VC calls the cache exists to avoid. Tests cover truename inclusion, key stability for an unchanged file, and a symlink whose target moves.
|
| |
|
|
|
|
|
|
| |
The refile target scan caught permission-denied and silently dropped the directory, and would crash outright on a missing root (only permission-denied was caught, so a missing code-dir/projects-dir raised file-missing and aborted the whole build). The agenda build had the same crash: cj/add-files-to-org-agenda-files-list called directory-files on projects-dir with no existence check.
Extracted cj/--org-refile-scan-dir, which warns (display-warning) and returns nil for a missing, unreadable, or permission-denied root so the rest of the scan continues. Guarded the agenda scan the same way. Both now log a concise warning naming the skipped directory rather than failing silently or fatally.
Also fixed a latent bug surfaced here: org-refile-targets was never declared special, so under make compile cj/org-refile-in-file let-bound it lexically and the scoped targets never reached org-refile. Added (defvar org-refile-targets) so the binding stays dynamic when byte-compiled. Tests cover the helper (missing/permission-denied/normal) and the agenda missing-dir guard.
|
| |
|
|
|
|
| |
quick-video-capture scheduled an after-init-hook idle timer plus a 2-second fallback run-with-timer to call cj/setup-video-download, which require-d org-protocol and org-capture and registered both the protocol handler and the capture template. That loaded Org protocol/capture plumbing at every startup even when the video workflow was never used.
Split the two concerns the way org-webclipper already does. The org-protocol handler is registered in a with-eval-after-load (quote org-protocol) block — a lightweight add-to-list that needs no org-capture — so it is in place whenever org-protocol loads (org-config requires it at startup). cj/setup-video-download now registers only the capture template, lazily, on the first capture (org-capture-mode-hook) or the first protocol call (the handler ensures it). Both startup timers are gone. Tests pin that setup registers the template idempotently and no longer touches the protocol alist; verified in a live daemon that the protocol registers on load.
|
| |
|
|
|
|
| |
org-webclipper passed the org-protocol URL and title through globals cj/webclip-current-url / cj/webclip-current-title: the protocol handler setq them, and the "W" capture template plus its handler read them, with the handler clearing them afterward. An aborted or erroring capture left the stale values for the next clip.
Renamed them to cj/--webclip-url / cj/--webclip-title and let-bind them around the org-capture call in the protocol entry point instead of mutating globals. The template %(identity ...) forms and the handler run within that dynamic extent, so they see the values while the capture runs, and an abort/error unwinds the binding automatically — no stale state, no manual clear. This mirrors the quick-video-capture fix. Tests updated to the new contract: URL/title visible during the capture, nothing left bound after, and an aborted capture leaves no stale state.
|
| |
|
|
|
|
| |
quick-video-capture passed the org-protocol URL through a global cj/video-download-current-url: the protocol handler setq the global, the capture handler read and cleared it. If a capture was aborted or errored between those steps, the stale URL survived into the next manual capture.
Renamed it to cj/--video-download-url and let-bind it around the org-capture call in the protocol handler instead of setq-ing a global. The binding lives only for the dynamic extent of the capture, so the handler still sees the URL while the capture runs, and an abort or error unwinds the binding automatically — no stale state, no manual clear. The handler still prompts when invoked manually with no URL bound. Tests cover the bound-URL download, the manual prompt, the empty-URL error, that the URL is visible during the capture, and that an aborted capture leaves nothing behind.
|
| |
|
|
|
|
| |
cj/git-clone-clipboard-url shelled out via shell-command and derived the clone directory with file-name-nondirectory, which mishandles scp-style SSH URLs with no slash (git@host:repo.git became git@host:repo). It also ran git in default-directory and only checked whether the clone dir appeared afterward, so a failed clone was silent.
The clone now runs as a direct git process (call-process, no shell) with clone -- url dir so a URL beginning with - cannot be read as a flag. The destination path comes from cj/--git-clone-dir-name, which takes the last component splitting on / and :, handling HTTPS, scp-style and ssh:// SSH, and local paths. It validates the clipboard is non-empty and the target is a writable directory that does not already contain the destination, and surfaces a non-zero git exit as a user-error with the *git-clone* output. Tests cover the deriver across URL schemes plus the empty-clipboard and clone-failure paths.
|