| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
| |
C-c p d (cj/open-project-daily-prep) always forced the prep doc into another window. I switched it to cj/--find-file-respecting-split, the same helper the sibling C-c p t (project todo) uses: it opens the other window when the frame is split and reuses the current window when it isn't. The two project-open commands now behave the same way.
|
| |
|
|
|
|
|
|
|
|
| |
The F10 music playlist opened at a fixed fraction every time, so any manual resize was lost the moment it was toggled closed. Now the toggle captures the window's size on close and reopens at that height, for the rest of the session.
The mechanism is generic, not music-specific. cj/window-size-fraction (geometry-lib) is the pure kernel: a clamped window/frame ratio. cj/side-window-capture-size and cj/side-window-display (toggle-lib) wrap it for any display-buffer-in-side-window consumer — height for top/bottom, width for left/right — storing the remembered fraction in a caller-supplied state var. It mirrors the direction-split toggle pattern the vterm dispatchers already use, but for atomic side windows that can't be split.
music-config wires F10 to it: cj/music-playlist-window-height is the default, cj/--music-playlist-height holds the remembered value (in-memory, resets each session).
12 new tests across the two libs, Normal/Boundary/Error each covered.
|
| |
|
|
|
|
|
|
| |
I reshaped the Signal client task into a parent with child issues (contact picker, notify behavior, the upstream input-clobber bug, link command, wiring) and recorded the fork decision and the linking milestone as dated entries.
I also filed the headline-indicator wrap bug: the org-tidy dot and the fold ellipsis spill to a second row on property-drawer headings that are folded, because the tag right-align reserve is too small once the heading runs long. The right-align fix itself is intact, so this is a follow-on, not a regression.
The cleanup passes archived the finished daily-prep task and synced three child priority cookies up to their parents.
|
| |
|
|
|
|
|
|
|
|
| |
I'm building a Signal client in Emacs on signal-cli (linked as a secondary device) with a fork of the signel package as the front end. signel is on MELPA but effectively abandoned, and the behavior I want needs internal edits, so owning a fork beats advising a dead package. Full rationale and the rejected alternatives are in docs/design/signal-client.org.
This lands the signal-cli-independent foundation: contact-list parsing for a completing-read picker, and the predicate that suppresses a notification for the chat being actively viewed. Both are pure and unit-tested without a linked account. cj/signal--parse-contacts was corrected against a live account (signal-cli 0.14 puts givenName/familyName at the top level, not under profile), and verified across all 94 real contacts.
The use-package wiring loads the fork from ~/code/signel, sources the account from a gitignored signal-config.local.el (a phone number is an identifier, not a credential, and this keeps it off the mirror without a GPG prompt), and turns off auto-open so an incoming message can't steal a window. Verified live: signel-start spawns the jsonRpc process, loads the account, and receives over the channel.
The fork edits (notify routing, the upstream input-clobber bug) and the contact-picker command are still to come.
|
| |
|
|
|
|
| |
Setting debug-on-quit t during startup turns C-g into a debugger trigger instead of an abort, so the normal "break out of a hang" reflex drops you into a recursive-edit you can't C-g out of. Worse, debug-on-error is also on during startup. An init error pops the debugger before the emacs-startup-hook cleanup runs, and debug-on-quit stays t for the whole session. I hit exactly that: a quit cascaded into a debugger loop that needed killall to escape.
I dropped debug-on-quit from the startup block and removed its now-redundant cleanup line. debug-on-error stays on during startup so init errors are still caught. C-g stays an escape hatch.
|
| |
|
|
| |
I closed the daily-prep keybinding task (shipped in 8e5efcab) and rewrote the popper-config removal into a dated log entry (1cca84c5). I also recorded the researched Signal-client candidates and the Claude-Code-in-eat findings against their tasks, and folded the "no remote install" constraint into the TRAMP task.
|
| |
|
|
| |
assets/M-x_butterfly.png.bak was a tracked duplicate of the banner PNG, an accidental backup that got committed. I removed it and added *.bak to .gitignore so editor and image backups stay out of the tree.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
Close the window-placement and tag-alignment work that shipped (Slack, mu4e, dupre tags and the right-align feature), and queue the open follow-ups: the EAT consolidation, the ai-vterm/mu4e/Slack window tickets, and a Signal-client task. Record the researched Signal-client candidates and the Claude-Code-in-eat findings against their tasks, log the decision to remove the disabled popper-config, and fold the "no remote install" constraint into the TRAMP task.
Fix the two invalid org tags (no-sync, which has a hyphen org rejects, so it read as title text) to nosync, and tag the previously untagged GPTel and Signal tasks. Realign every heading's tags to a single space to match org-tags-column 0, which reverts stale column-77 padding that had drifted in.
|
| |
|
|
| |
Research notes weighing the three Emacs terminal backends (vterm, eat, ghostel) on maintenance risk, rendering fidelity, and best-fit role. Referenced by the "Consolidate to EAT" task as the basis for that evaluation.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
| |
org-level-1..4 inherit dupre-heading-1..4, which set :weight bold, so org headings rendered bold. I overrode the weight to normal on the org-level faces themselves rather than on dupre-heading-*, so the info-manual, markdown, and shr headings that also inherit those faces keep their bold. I also dropped the bold from org-document-title for consistency.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
| |
org-block-begin-line and org-block-end-line carried a bg+1 (#252321) background, so the #+begin_src and #+end_src lines showed a lighter box that matched neither the block body nor the normal background. I pointed both at the theme background so the delimiter lines sit flush, keeping their gray foreground.
|
| |
|
|
|
|
| |
A level-2 heading's whole line carries org-level-2 (height 157). org layers that level face onto a bare TODO keyword, but drops it when the heading has a priority cookie, so the keyword fell back to the body height (143). The same keyword rendered at 157 without a cookie and 143 with one, which read as inconsistent keyword sizes across the Linear and todo views.
I gave org-todo, org-done, and the dupre-org-* keyword and priority faces an explicit :height of 143 so they no longer float with the heading face. The height is absolute on purpose. A float would re-inherit the level height inside the face list and bring the inconsistency back. Keywords now render at the body height everywhere, a step down from the heading title.
|
| |
|
|
|
|
| |
org-todo was intense-red (#ff2a00), the brightest red in the palette. Every TODO-state keyword that isn't in org-todo-keyword-faces falls back to org-todo. So the Linear states (IN-PROGRESS, BACKLOG-PRIORITIZED, and the rest) all rendered in that hot red, and the whole view shouted.
I moved org-todo to red-1 (#a7502d) and org-warning to red (#d47c59). They're distinct now, so an overdue deadline reads a shade warmer than a plain keyword. intense-red stays reserved for the FAILED face.
|
| |
|
|
| |
Bring .claude/hooks/validate-el.sh and .claude/rules/interaction.md back in line with the shared bundle, and add the new keybinding-display.md rule.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
The previous #151311 was already the palette's darkest entry. #0d0b0a is about 40% darker again. Both bg and bg+0 move together because bg+0 feeds hl-line, which should stay equal to the background.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
hardening tasks
|
| |
|
|
| |
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.
|
| |
|
|
| |
tasks :discuss:
|
| | |
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
dispositions
|
| |
|
|
|
|
| |
prog-lisp.el had no tests. I added four for the config it owns directly: cj/elisp-setup and cj/common-lisp-setup are each registered on their mode hook and set the buffer-local conventions this config picks (4-space, no tabs, fill-column 120 for elisp; 2-space, fill-column 100 for Common Lisp).
The module loads with use-package stubbed to a no-op, so no packages load, ensure, or download during the run. That keeps it batch-safe and independent of which Lisp packages happen to be installed.
|
| | |
|
| |
|
|
|
|
| |
mu4e-org-contacts-integration.el had no tests. I added ten characterization tests for the completion glue. The capf only fires inside a header field of a compose buffer, so I check it both ways (wrong mode, wrong field) plus the bounds and table it returns when contacts exist and the empty-contacts case. TAB dispatches three ways depending on context, so each branch gets a test: completion-at-point in a header, org-cycle in the org-msg body, indent elsewhere. Comma completion and the direct-insert path round it out.
The header predicate, the mode actions, and cj/get-all-contact-emails are stubbed, so the run stays headless with no mu4e or org-contacts dependency.
|
| |
|
|
| |
decision task
|
| |
|
|
|
|
| |
font-config.el had no direct tests. I added four: cj/font-installed-p returns t or nil depending on find-font, and cj/apply-font-settings-to-frame is a no-op on a non-GUI frame and applies the preset exactly once per frame, so reopening a daemon frame doesn't restack it.
find-font, env-gui-p, and fontaine-set-preset are stubbed so the run stays headless. The module :demand's fontaine and all-the-icons, so a skip-unless on those packages keeps a bare checkout green while the tests still run wherever the fonts are installed.
|
| | |
|
| |
|
|
|
|
| |
system-defaults.el had no coverage for its settings, only its functions (in test-system-defaults-functions.el) and the vc-follow-symlinks default. I added three settings smoke tests: custom-file is redirected to a temp trashbin rather than the repo, backups land under user-emacs-directory/backups, and the minibuffer GC hooks are actually wired onto the minibuffer hooks.
I pulled the sandbox loader the vc-follow-symlinks test had inline into tests/testutil-system-defaults.el so both files share one copy. The backups test clears cj/backup-directory before loading — it's a defvar, so once an earlier test loads the module it keeps that first sandbox path and won't recompute, which made the assertion fail until I forced the recompute.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
| |
I investigated the inherited "WIP need to fix" comment. The module compiles and works, company-auctex is tracked under the corfu migration, and the one real defect (non-idempotent PDF-viewer selection) is fixed in b007a9b8. Closed with a note on what I found.
|
| |
|
|
|
|
| |
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 byte-compile load-paths task shipped as the make compile-file target, so I closed it DONE with a note on what landed and where the parallel hook fix went. I dropped the prog-shell config-home task — it isn't worth tracking as its own item.
|