aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
...
* docs: add vterm/eat/ghostel terminal comparisonCraig Jennings10 days1-0/+121
| | | | 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.
* feat(mail): keep mu4e's main view from deleting the window splitCraig Jennings10 days2-0/+54
| | | | | | 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.
* feat(slack): open rooms in another window, never the selected oneCraig Jennings10 days2-0/+65
| | | | | | 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.
* fix(org): widen the tag right-margin so the fold ellipsis fitsCraig Jennings10 days1-3/+5
| | | | 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.
* feat(org): right-align headline tags to the window edgeCraig Jennings10 days2-0/+97
| | | | | | 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.
* fix(dupre): make org headings and the document title non-boldCraig Jennings10 days1-5/+9
| | | | 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.
* fix(text): compose every org src-block marker to the lambda glyphCraig Jennings10 days2-0/+55
| | | | | | 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.
* fix(dupre): flush org src-block delimiter lines with the backgroundCraig Jennings10 days1-2/+2
| | | | 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.
* fix(dupre): pin org keyword and priority faces to a uniform heightCraig Jennings10 days1-26/+32
| | | | | | 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.
* fix(dupre): tone down the org TODO and warning redsCraig Jennings10 days1-2/+2
| | | | | | 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.
* chore(claude): sync rules and hooks with the rulesets bundleCraig Jennings10 days3-0/+60
| | | | 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.
* fix(dashboard): exempt the banner buffer from auto-dimCraig Jennings10 days3-4/+50
| | | | | | | | 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.
* feat(dupre): darken the theme background to #0d0b0aCraig Jennings10 days1-2/+2
| | | | 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.
* docs(todo): archive the byte-compile-paths and latex-config tasks to ResolvedCraig Jennings11 days1-18/+16
|
* docs(todo): close the user-constants filesystem-init epicCraig Jennings11 days1-35/+29
|
* feat(user-constants): make required-path init failures actionableCraig Jennings11 days2-19/+81
| | | | | | | | 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.
* refactor(user-constants): move filesystem creation out of module loadCraig Jennings11 days3-16/+102
| | | | | | | | (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.
* docs(todo): close font-config, eshell, customize-warning, and webclipper ↵Craig Jennings11 days1-38/+52
| | | | hardening tasks
* feat(system-defaults): warn once when Customize tries to saveCraig Jennings11 days2-0/+36
| | | | 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.
* refactor(eshell): move SSH-jump hosts into a defcustomCraig Jennings11 days2-3/+82
| | | | 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.
* fix(font-config): theme-aware browser labels and daemon-safe emoji fontsetCraig Jennings11 days2-12/+46
| | | | | | 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.
* docs(todo): tag babel-confirm, health-check, module-ownership, and strapdown ↵Craig Jennings11 days1-4/+4
| | | | tasks :discuss:
* docs(todo): close mousetrap keymap cache and formatter argv tasksCraig Jennings11 days1-19/+23
|
* refactor(prog): run JSON/YAML/webdev formatters via argv, not a shellCraig Jennings11 days6-47/+213
| | | | | | | | 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.
* perf(mousetrap): cache built keymaps per profileCraig Jennings11 days2-2/+116
| | | | | | 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.
* docs(todo): log external-tool guards across Org export/publishing modulesCraig Jennings11 days1-12/+19
|
* fix(org): guard external-tool assumptions in export and publishing commandsCraig Jennings11 days9-2/+130
| | | | | | | | | | | 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.
* docs(todo): log prog-lisp smoke coverage and the prog-general/prog-training ↵Craig Jennings11 days1-12/+16
| | | | dispositions
* test(prog-lisp): cover the elisp and Common Lisp mode-setup functionsCraig Jennings11 days1-0/+66
| | | | | | 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.
* docs(todo): log mu4e org-contacts completion coverage under hardeningCraig Jennings11 days1-9/+11
|
* test(mu4e-org-contacts): cover the completion-at-point and TAB dispatch logicCraig Jennings11 days1-0/+135
| | | | | | 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.
* docs(todo): log font-config smoke coverage; route popper smoke test to its ↵Craig Jennings11 days1-8/+20
| | | | decision task
* test(font-config): smoke-cover the install check and daemon-frame applierCraig Jennings11 days1-0/+74
| | | | | | 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.
* docs(todo): log system-defaults settings smoke tests under hardeningCraig Jennings11 days1-14/+13
|
* test(system-defaults): cover custom-file, backups, and GC-hook registrationCraig Jennings11 days3-49/+142
| | | | | | 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.
* feat(ui-theme): default the theme fallback to bundled dupreCraig Jennings11 days2-3/+16
| | | | | | 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.
* docs(todo): close the latex-config WIP taskCraig Jennings11 days1-1/+4
| | | | 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.
* fix(latex): make PDF-viewer selection idempotentCraig Jennings11 days2-5/+92
| | | | | | 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.
* docs(todo): close the byte-compile-paths task and drop the prog-shell taskCraig Jennings11 days1-8/+3
| | | | 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.
* build: add compile-file for single-file byte-compile with the project load pathCraig Jennings11 days1-2/+17
| | | | | | Bare emacs -Q --batch byte-compile-file fails on local compile-time requires (undead-buffers for a module, dupre-palette for a theme file) because nothing is on the load path, even though init and the test harness load fine. I added a compile-file target that compiles one file with the project load path (modules, themes, tests, plus package-initialize), and put themes on make compile's path too. Bare emacs -Q stays unsupported by design; compile-file is the documented command, and it guards against a missing FILE. Verified modules/dashboard-config.el and themes/dupre-faces.el both compile through it.
* chore: sync bundled claude rules and git hooksCraig Jennings11 days10-8/+568
| | | | Routine sync of the .claude/rules and git hooks distributed with the language bundle. Adds the cross-project, emacs, interaction, todo-format, triggers, and working-files rules; refreshes the elisp and elisp-testing rules, the elisp validation hook, and the pre-commit hook.
* docs(todo): close shipped tasks and refile completed sub-tasksCraig Jennings11 days1-19/+44
| | | | | | I marked the tasks that shipped as DONE: the ai-vterm third-split fix, projectile open-todo in the other window, the elfeed-config byte-compile-safe tests, the org TODO-keyword color theming, and the manual cj/org-finalize-task verification. The load-graph spec and the elfeed-config header annotation are level-4 sub-tasks, so they become dated event-log entries rather than DONE keywords. I also reformatted a few headings, bumped the dashboard over-scroll task to [#D], and filed a task to make the standalone byte-compile load paths match module dependencies.
* feat(dashboard): render the butterfly banner with a transparency maskCraig Jennings11 days3-4/+10
| | | | | | 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.
* feat(auto-dim): dim vterm windows by blending terminal colorsCraig Jennings11 days2-0/+283
| | | | | | | | 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.
* fix(theme): register dupre faces so org status colors are themedCraig Jennings11 days4-15/+114
| | | | | | | | 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.
* feat(auto-dim): dim non-selected windows via auto-dim-other-buffersCraig Jennings11 days6-4/+111
| | | | | | | | 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.
* docs(load-graph): classify elfeed-config, the last init moduleCraig Jennings11 days3-21/+19
| | | | | | 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.
* test(elfeed-config): use real structs so tests survive byte-compilationCraig Jennings11 days1-26/+49
| | | | | | | | The cj/elfeed-process-entries tests faked entries as bare symbols and stubbed the elfeed-entry-link accessor, which only works while elfeed-config loads as interpreted source. Byte-compiled, the cl-defstruct accessor inlines to an elfeed-entry-p check plus an aref, so the stub is bypassed and the inlined check rejects the fake entry. Three tests failed the moment a .elc existed. I rewrote the five process-entries tests to build genuine elfeed-entry structs with elfeed-entry--create, calling package-initialize so the installed elfeed lands on the load-path, and guarded them with skip-unless for an environment that lacks the package. The elfeed-search UI boundary is still stubbed. The four extract-stream-url tests are unchanged. This unblocks annotating elfeed-config with its load-graph header, which triggers the byte-compile that surfaced the problem.
* fix(prog-general): open the project todo in the other window when splitCraig Jennings11 days2-1/+73
| | | | | | 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.
* fix(ai-vterm): reuse the frame's half instead of splitting a thirdCraig Jennings11 days5-268/+480
| | | | | | 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.