| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`cj/org-roam-filter-by-tag' called `org-roam-node-tags' directly.
That accessor is generated by `cl-defstruct' and ships with a
compiler-macro that inlines the call to an `aref' against the
`cl-struct-org-roam-node-tags' tag variable at byte-compile time.
In tests, `cl-letf' on `(symbol-function 'org-roam-node-tags)' sets
the function cell but the byte-compiled call site never consults it
-- it executes the inlined `aref' instead. When org-roam isn't
loaded (legitimate for a tag-filter unit test), the inlined code
fails with `void-variable cl-struct-org-roam-node-tags'.
Wrap the accessor in `cj/--org-roam-node-tags' that calls through
`funcall' with a quoted symbol. Quoted symbols skip the
compiler-macro (which only fires on direct call forms), so the
funcall resolves the function cell at runtime and picks up the
test's `cl-letf' stub. Production behavior is unchanged; tests
no longer need org-roam loaded.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`org-web-tools-pandoc-sleep-time' is a plain float with no
custom-set handler that needs to fire. `setopt' adds the entire
customize-variable validation machinery -- which, lazily, depends on
wid-edit being loaded.
The handler's tests stub `require' so org-web-tools never really
loads, then mock `setopt' via `cl-letf' on the function cell. That
mock has no effect on byte-compiled code because `setopt' is a
macro: the production handler has already expanded to a call into
`setopt--set'. When `setopt--set' runs, it walks into the customize
machinery and hits an unbound `widget-field-keymap' (wid-edit not
loaded), and the test fails with a confusing wrong-type-argument.
`setq' has identical runtime effect for this variable and dodges
the customize machinery entirely. Tests now pass without contorted
mocking.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The module had `(eval-when-compile (require 'keybindings))`, which
silences the byte-compiler but doesn't make `cj/custom-keymap'
available when the module is required. The top-level
`(keymap-set cj/custom-keymap "!" cj/system-command-map)' at the tail
of the file then fails with `void-variable cj/custom-keymap'.
Normal Emacs startup happened to work because `init.el' requires
`keybindings' before `system-commands'. But requiring the module in
isolation -- including from `make test-file
FILE=test-system-commands-resolve-and-run.el' -- blows up.
Fix: use a plain `(require 'keybindings)' so the load-time
dependency matches the load-time reference.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Info-mode entry in cj/buffer-source-functions copied the bare
target string info:(manual)Node. Per the task body that introduced
the dispatcher, the intended output is the labeled org-link form
[[info:(manual)Node][(manual) Node]] -- a paste into notes lands as
a clickable link with a human-readable label, not a bare URI.
The label uses (manual) Node so the manual name and node name are
both grep-friendly in note files.
Existing test on a compressed .info.gz file now asserts the bracket
form. Added a boundary test for an uncompressed .info file (the
other branch of the suffix-stripping logic) so both compression
shapes are locked in.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
make compile warned that cj/toggle-gptel is not known to be defined
when ai-vterm.el is byte-compiled. The M-F9 binding still worked
during normal startup because init.el loads ai-config.el after
ai-vterm.el, but the dependency was implicit -- byte-compile saw the
function symbol unresolved, and loading ai-vterm.el in isolation
left M-F9 bound to an undefined function.
Declare cj/toggle-gptel as an interactive autoload pointing at
ai-config. This silences the warning, keeps ai-vterm.el free of a
load-time (require 'ai-config), and makes the load-order contract
explicit: the binding works as long as ai-config eventually loads.
Test asserts that requiring ai-vterm in isolation leaves
cj/toggle-gptel fboundp as an autoload sigil (not a real function).
A regression that adds (require 'ai-config) at the top of
ai-vterm.el would flip this, and a regression that drops the
autoload form would leave fboundp nil.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Add a tiny source-level architecture suite at
tests/test-architecture-startup-contracts.el with two checks:
- Only keybindings.el may globally own the exact C-; prefix. Catches
accidental cross-module rebinding before it ships.
- Top-level timer scheduling (run-with-timer / run-at-time /
run-with-idle-timer) must be guarded by (unless noninteractive ...)
so requiring a module in batch / test mode does not schedule
startup timers. Timer calls inside defuns are exempt -- the test
only rejects forms that execute their body when the module loads.
Four modules had unguarded top-level timer scheduling and would have
tripped the new test. Wrap their startup hooks/timers in
(unless noninteractive ...):
- modules/org-agenda-config.el: 10s idle cache build
- modules/org-refile-config.el: 5s idle cache build
- modules/quick-video-capture.el: after-init-hook + 2s fallback
- modules/wrap-up.el: emacs-startup-hook bury-buffers delay
The contract being protected is "requiring a module in batch should
not start a clock running." Test failures will now point straight at
the offending file/form.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Add two dispatchers to cj/buffer-source-functions so C-; b p yields a
useful link form in two more major modes.
mu4e-view-mode returns "mu4e:msgid:<id>" so the result pastes into org
as a clickable link and matches mu4e's own org-protocol handler.
Falls through to buffer-file-name when point isn't on a real message.
Info-mode returns "info:(manual)node" -- the form org-info-store-link
produces. file-name-base only strips one extension, so a compressed
"emacs.info.gz" comes back as "emacs.info"; trim the trailing ".info"
to get the bare manual name. Falls through when Info hasn't populated
its current-file / current-node vars yet.
Tests cover normal + boundary fallthrough for each new mode.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`cj/org-noter-insert-note-dwim' is the most-used action in a noter
session; it deserves the doubled-prefix letter. Move it from
`C-; n i' to `C-; n n'.
Sibling-stepping moves off `n'/`p' (which were sync-next /
sync-prev) onto the angle-bracket pair `>'/`<' to free up `n' and
to read more naturally as direction. `.' stays as
sync-current-note.
Updated `which-key' labels to match. Four new ERT tests in
`tests/test-org-noter-config-keymap.el' lock the keymap shape so a
casual edit doesn't silently drift the layout.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`make compile' had been flagging `cj/calibredb-clear-filters' and
`cj/nov-jump-to-calibredb' as "defined multiple times in this file"
since 2026-05-12. Investigation: there's only one `(defun ...)' of
each in the source -- use-package's `:bind' expansion makes the
byte-compiler count the referenced symbol as a definition when the
function is defined in the same file, then it sees the real
`defun' later and warns about a redefinition.
Reorder so each `defun' appears before the `use-package' that
references it via `:bind':
- `cj/calibredb-clear-filters' moved above (use-package calibredb).
- `cj/nov--metadata-get', `cj/nov--file-path', and
`cj/nov-jump-to-calibredb' moved above (use-package nov). The
two helpers had to move with the public function so the
byte-compiler doesn't emit fresh free-function warnings.
Source content unchanged; only line positions move. Both
duplicate-definition warnings are gone after this; full unit suite
still green.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Old behavior: `C-; b p' called `cj/copy-path-to-buffer-file-as-kill',
which only worked in file-visiting buffers and errored otherwise.
That meant the most useful "give me a clickable handle on this
buffer" key did nothing in eww, elfeed, dired (file-at-point ≠
buffer's default-directory), and other browsing-shaped modes.
Replace with a `major-mode'-aware dispatch:
- `cj/buffer-source-functions' alist maps major-mode → thunk
returning a string (or nil to fall through).
- `cj/copy-buffer-source-as-kill' looks up the current mode,
calls the thunk, falls back to `buffer-file-name', errors only
when both yield nil.
- `cj/copy-path-to-buffer-file-as-kill' kept as a `defalias' for
backwards compat (the old name is referenced in adjacent tests).
First-batch dispatches:
- eww-mode -> (eww-current-url)
- elfeed-show-mode -> (elfeed-entry-link elfeed-show-entry)
- dired-mode -> (dired-get-filename nil t)
- dirvish-mode -> same
- doc-view / pdf-view: covered by the buffer-file-name fallback
(they already set buffer-file-name correctly).
10 new ERT tests cover the dispatch paths, the
buffer-file-name fallback, the user-error on nil source, the alias
target, and the `C-; b p' keymap entry.
which-key label flipped from "copy file path" to "copy buffer
source" to match.
Deferred to a follow-up task: mu4e-view-mode, org-mode at a
heading, help-mode, Info-mode, magit-log/commit/status, xref/grep/
compilation, image-mode, archive-mode -- each needs a format
decision before implementation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
M-F9 used to invoke `cj/ai-vterm-pick-buffer' (a buffer picker
narrowed to alive AI-agent buffers). In practice the F9 plain-key
toggle + C-F9 project picker covered the common cases, and the
buffer picker rarely earned its keystroke. Rebind M-F9 to
`cj/toggle-gptel' so the F9 family covers the two main in-Emacs AI
surfaces at one keystroke each:
<f9> ai-vterm toggle (unchanged)
C-<f9> ai-vterm picker (unchanged)
M-<f9> gptel *AI-Assistant* (NEW)
Removed entirely:
- `cj/ai-vterm-pick-buffer' (the command itself).
- `cj/--ai-vterm-pick-buffer-candidates' (its helper).
- `tests/test-ai-vterm--pick-buffer-candidates.el' (deleted).
Updated:
- `tests/test-ai-vterm--f9-in-vterm.el' binding assertions
(vterm-mode-map and global) flipped to `cj/toggle-gptel'.
- Module commentary + `cj/ai-vterm' docstring describe the new
M-F9 behavior.
- `cj/toggle-gptel' lives in `modules/ai-config.el'; the binding
stays in `ai-vterm.el' next to the rest of the F9 family so the
dispatch shape is visible in one place.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Anthropic: bump Opus 4.6 → 4.7 (current frontier). Sonnet 4.6
and Haiku 4.5 stay -- still current. Default `gptel-model' setq
also bumped to claude-opus-4-7 in both places it was set.
OpenAI: drop the cohort retired from ChatGPT on 2026-02-13
(gpt-4o, gpt-5 original, gpt-4.1, o1). Replace with the current
lineup: gpt-5.5 (flagship), gpt-5.4-mini (fast/cheap), o3
(reasoning). All three are documented at
developers.openai.com/api/docs/models/.
Drive-by: stale docstring example in cj/gptel--current-model-selection
bumped to match.
gptel's bundled :models list only knows up to May-2025 IDs but
gptel-make-anthropic / gptel-make-openai accept any string and pass
it straight to the API, so newer names work without a gptel upgrade.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`#+begin_src markdown ... #+end_src' blocks rendered and exported
fine but `org-lint' warned on every one of them ("Unknown source
block language: 'markdown'"), and `C-c '' inside the block fell
back to `fundamental-mode' instead of opening it in
`markdown-mode' for editing.
Add a `with-eval-after-load 'org' form that pushes
`("markdown" . markdown)' onto `org-src-lang-modes'. New ERT test
in `tests/test-markdown-config.el' asserts the entry resolves to
`markdown' after `(require 'markdown-config)'.
Surfaced while clearing `org-lint' on `todo.org' from 55 issues
down to 1 -- the last one was this warning on a Linear ticket-body
draft that was genuinely markdown. Registering the language is
the right fix; relabeling the block as `text' or `example' would
lose accuracy.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Narrowing and sparse-tree commands existed in the `:bind' block
on `C-c'-style shortcuts but nothing in `cj/org-map' surfaced
them, so which-key never showed them and discoverability was
poor.
Add direct bindings under `C-; O', flat (no sub-prefixes for
narrow / sparse-tree). Lowercase creates; capital of the same
letter cancels:
- `n' / `N' narrow-to-subtree / widen
- `s' / `S' match-sparse-tree / show-all
- `t' / `T' show-todo-tree / show-all
- `>' / `<' forward / backward sibling narrow (kept as-is)
- `R' reveal-context (no lowercase pair -- `r' is the
table-row sub-prefix)
Both `S' and `T' resolve to the same `org-show-all' command so
the mental model is just "capital cancels the lowercase I just
ran" without having to recall which letter the cancel actually
lives on.
Free up F2: the old `(<f2> . org-reveal)' binding in the org-mode
`:bind' block is now redundant with `C-; O R'. Drop it; F2
becomes available for whatever wants it next.
Four new ERT assertions in `test-org-config-keymap-ownership.el'
lock the shape -- the old sparse-tree-submap test was rewritten
for the flat layout and the narrow-submap test became
narrow-bindings (also flat).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Telegram had landed alone on a third row of one icon, with the
first two rows holding a mixed bag (Code next to Email next to
Agenda next to Files next to Music; Feeds next to IRC next to
Slack next to Flashcards next to Books next to Terminal). No
category showed up grouped, and the asymmetry was bugging me
every dashboard open.
Regroup by what the icons actually do. Three rows of four:
- Row 1 Work: Code / Files / Terminal / Agenda
- Row 2 Read & Learn: Feeds / Books / Flashcards / Music
- Row 3 Communication: Email / IRC / Slack / Telegram
Reorder the `define-key' calls on `dashboard-mode-map' to mirror
the row layout -- reading the keymap top-to-bottom now matches
reading the icons left-to-right.
Drive-by fix in the same commit: Music had an icon but no
`dashboard-mode-map' keybinding (mouse-only). Bound to `m'.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pressing `T' in dired/dirvish on an audio file already transcribed
it; on a video file it bounced with "Not an audio file". Real
recordings ship as .mp4 / .mkv at least as often as raw .m4a, so
the one-key flow ended at the wrong place.
Pipeline now:
- audio path -> direct into `cj/--start-transcription-process'
(unchanged).
- video path -> async ffmpeg extracts the audio track to a temp
.mp3 under `temporary-file-directory' (libmp3lame, VBR q:a 4,
~165kbps -- right size for speech, accepted by every backend),
then transcribes that file with the temp marked for cleanup
after the transcription sentinel fires.
Surface changes:
- `cj/video-file-extensions' added to user-constants.el (mp4, mkv,
mov, webm, avi, m4v, wmv, flv, mpg, mpeg, 3gp, ogv).
- New predicates `cj/--video-file-p' / `cj/--media-file-p'.
- New `cj/--extract-audio-from-video' (async ffmpeg with success
callback; surfaces `cj/--notify' on failure; user-errors if
ffmpeg isn't on PATH).
- `cj/--start-transcription-process' gains optional `cleanup-file'.
Sentinel deletes it after the existing logic runs. Backwards
compatible -- the audio flow doesn't pass it.
- `cj/transcribe-audio' renamed to `cj/transcribe-media' (dispatcher
on audio vs video). `cj/transcribe-audio-at-point' renamed to
`cj/transcribe-media-at-point'. Both old names kept as
`defalias' so M-x history and any external references still work.
- `T' in dired-mode-map + dirvish-mode-map points at
`cj/transcribe-media-at-point'.
- Module commentary USAGE block updated.
15 new ERT tests in `tests/test-transcription-video.el' cover the
predicates (happy/boundary/error), ffmpeg invocation (correct args
+ missing-ffmpeg path), the dispatcher (audio direct, video via
extraction, non-media rejected), the aliases, and the T binding.
One existing test in `test-transcription-status-and-commands.el'
updated to stub the new delegate name.
Verified locally that ffmpeg is on PATH with libmp3lame, and that
the exact arg list my code uses produces a valid MP3 from a
synthetic test video.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The transcription menu wasn't earning its top-level keymap slot --
the commands (transcribe-audio, switch-backend, view-transcriptions,
kill-transcription) are run rarely enough that `M-x' is fine. Drop
the `cj/transcribe-map' keymap, its `(keymap-set cj/custom-keymap
"T" ...)' binding, and the which-key labels. Commands stay
callable by name.
That frees `C-; T' for telega, where the mnemonic actually fits.
Move the launcher from `C-; G' to `C-; T'. Update the
which-key label, the module commentary, and the keymap-binding
test assertion. The dashboard `g' single-letter binding stays put
-- `t' there is vterm, so dashboard letters and the global
`C-;' prefix don't share a key space anyway.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Drop the `T' sub-prefix so table operations sit directly under
`C-; O': `O r i' / `O r d' for rows, `O c i' / `O c d' for columns.
Move `cj/org-clear-element-cache' from `c' (which now hosts the
table-column sub-prefix) to capital `C'. Single-key org commands
under this menu live on capitals from here on so the lowercase
letters stay free for table sub-prefixes.
Drop `cj/org-table-map' entirely -- its bindings now live directly
on `cj/org-map'. Three tests in `test-org-config-keymap-ownership.el'
updated/added: `C' for clear-cache, plus row and column binding
assertions.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`(keymap-set cj/custom-keymap "T" cj/org-table-map)' at top level
silently collided with `cj/transcribe-map' bound to the same key in
`modules/transcription-config.el'. Whichever module loaded last won,
the other prefix became unreachable, and which-key still showed both
labels in their respective sections -- so the visible documentation
didn't match what actually fired.
Move the table map under the existing `cj/org-map' (`C-; O') as the
"T" sub-prefix, so `C-; T r i' becomes `C-; O T r i' and friends.
The org menu only had one entry before (clear element cache); table
operations are a natural neighbor. Frees `C-; T' at the top level
for the transcription menu, which was the only other module fighting
over it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
missing
Without the guard, both `C-; G' and the dashboard Telegram icon
trigger telega's autoload stub directly. When the package isn't
installed yet the user sees `Cannot open load file: telega' in
`*Messages*' with no hint about what to do.
Wrap the launcher in `cj/telega' that checks `featurep' /
`locate-library' first. If telega is present, delegate to it.
Otherwise signal a `user-error' pointing at `scripts/setup-telega.sh'
and the manual `M-x package-install RET telega' fallback. Rebind
`C-; G' and the dashboard "g" key + Telegram icon callback to the
wrapper.
Two new tests in `test-telega-config.el' cover the wrapper paths
(absent -> user-error with the recovery hint; present -> delegates
to `telega') alongside the updated binding assertion.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`cj/system-cmd' had `(interactive (list (read-shell-command "System
command: ")))' -- the destructured-list interactive spec. Undercover.el
relies on edebug instrumentation, which doesn't see past that form, so
the entire function body registered as 0 hits under coverage even
though tests call the function directly.
Switch to the equivalent string spec `(interactive "sSystem command: ")'.
Same UX (prompt, history, single-string result), but the body now
instruments correctly and coverage moves from 34/49 to 50/51.
Add one more test that captures the `run-at-time' lambda in
`cj/system-cmd-restart-emacs' and invokes it directly so the inner
`call-process-shell-command' branch registers, taking coverage to 51/51.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
`cj/org-roam-copy-todo-to-today' tried to save the target journal
buffer via `org-after-refile-insert-hook' bound to `#'save-buffer',
but that value is the wrong shape (single function instead of a
hook list), and the only other save mechanism -- the `:after' advice
on `org-refile' that calls `org-save-all-org-buffers' -- doesn't
attach until `:defer .5' elapses, so the very first DONE transition
after startup leaves the journal unsaved.
Drop the broken hook binding and save the target buffer explicitly
after the refile call. New ERT test asserts `buffer-modified-p' on
the journal buffer is nil after the function returns.
|
| |
|
|
|
|
| |
The original fix (9600611) set a flag at toggle-off and let the next toggle-on detect it. The flag mechanism is right, but the toggle-off itself wasn't observable when bury-buffer couldn't switch the lone window onto a different buffer -- `bury-buffer' falls back to `switch-to-prev-buffer', which no-ops when the window's prev-buffer history contains only the agent itself (common right after a `C-x 1' that cleared the surrounding windows' histories). Without an observable swap, the second F9 found the agent still displayed and routed back through toggle-off, looping the user with no visible effect.
Dispatcher now explicitly forces the window onto another buffer (`(other-buffer agent-buf t)`) when the lone window is still showing the agent after `bury-buffer'. The round-trip test now exercises the real `bury-buffer' path instead of simulating it; a new test asserts the lone window's buffer is non-agent after toggle-off.
|
| |
|
|
|
|
|
|
|
|
| |
The main "d" agenda view grows two new blocks. A VERIFICATION block lists tasks in the VERIFY TODO state, placed just above the day's SCHEDULE. An IN-PROGRESS block lists tasks in the DOING TODO state, placed just under SCHEDULE. The full block order is now: OVERDUE -> HIGH PRIORITY -> VERIFICATION -> SCHEDULE -> IN-PROGRESS -> PRIORITY B.
Scope matches the other blocks (every entry in `org-agenda-files`). Scheduled and deadlined entries are included -- a VERIFY task with a date appears in both VERIFICATION and SCHEDULE, mirroring how HIGH PRIORITY behaves. Habits are skipped via `cj/org-skip-subtree-if-habit`; PROJECT-keyword parents wouldn't match `(todo "VERIFY")` exact-state filters anyway, so no extra skip there.
Two new header defvars (`cj/main-agenda-verify-title`, `cj/main-agenda-doing-title`) for symmetry with the existing four. Both blocks reference the shared `cj/--main-agenda-prefix-format` so a format tweak still lands in one place.
Five new tests in `test-org-agenda-config-skip-functions.el` lock the block order, each new block's header / prefix-format / skip-function, and the include-scheduled-entries contract.
|
| |
|
|
|
|
| |
The use-package form defaulted to `:ensure t`, which made init crash when MELPA's archive index pointed at a dated snapshot tarball (`telega-20260503.1332.tar`) that had already rotated off the server -- the install fetch hit a 404 and took the whole startup down with a `file-error`.
Switched to `:ensure nil` so the module configures telega without trying to install it. The `:commands telega` autoload stub still wires `C-; G`; pressing it before the package is installed signals a void-function instead. Commentary now documents the one-time install (`M-x package-refresh-contents` + `M-x package-install RET telega`).
|
| |
|
|
|
|
| |
`org-agenda-custom-commands` inlined ` %i %-15:c%?-15t% s` four times across the "d" command's overdue / high-priority / schedule / priority-B blocks. New `cj/--main-agenda-prefix-format` defvar holds the literal once; every block now references the symbol so a format tweak lands in one place.
Regression test walks the "d" command's blocks and asserts each `org-agenda-prefix-format` cell resolves to the shared symbol -- a block that silently diverges fails the check.
|
| |
|
|
|
|
| |
`dashboard-navigator-buttons' grows a Row 3 with a single Telegram entry (using the `nf-fa-telegram' icon, launching `telega'). The dashboard-mode-map gets a single-letter `g' shortcut to match the other launcher keys.
Two follow-up TODOs filed under the parent telegram task: the TDLib docker setup script (so a fresh-clone install can boot telega without a system-wide TDLib build) and a dashboard-icon-balance pass (Row 3 with one entry is asymmetric; decide whether to leave it or reorganize).
|
| |
|
|
|
|
|
|
| |
New `modules/telega-config.el` configures telega.el as an in-Emacs Telegram client. `telega-use-docker' is on so TDLib runs in a container instead of needing a system-level build -- pairs with a follow-up `scripts/setup-telega.sh' for fresh-clone installs. First-run auth (phone + verification code) is interactive inside `M-x telega' and isn't scripted here.
Launcher binding: `C-; G` (mnemonic: teleGram). `C-; t` and `C-; m t` were both taken (test-runner, music's "repeat track"), so the launcher landed on a free top-level letter.
Two tests cover the wiring: module loads, launcher is bound.
|
| |
|
|
|
|
| |
`cj/vterm-tmux-history' previously used `pop-to-buffer', which routed the history view through display-buffer-alist -- in an agent window that often meant a split or a hand-off to another window, costing the agent its frame slot. `switch-to-buffer' instead drops the history into the selected window directly; the existing quit handler already restores the origin in that same window via `set-window-buffer'.
New test asserts the in-place behavior: starting single-window with a vterm origin, invoking the command leaves `(one-window-p)` t with the history buffer in the original slot. The existing render test no longer needs its `pop-to-buffer' stub since `switch-to-buffer' works in batch.
|
| |
|
|
|
|
|
|
| |
When the agent buffer is the only window in the frame, F9 buries it (correct) but the next F9 redisplayed it as a side split instead of restoring the full-frame layout -- the display-saved path always called `display-buffer-in-direction`, which insists on a split.
New `cj/--ai-vterm-last-was-bury` flag tracks which toggle-off path ran. `cj/ai-vterm` sets it to t in the bury branch (one-window-p) and nil in the delete-window branch. `cj/--ai-vterm-display-saved` checks the flag at toggle-on: if t and the frame is still single-window, it replaces the selected window's buffer in place rather than splitting. Either branch consumes the flag so it never stays stale.
5 tests in test-ai-vterm--single-window-toggle.el cover the flag's set/clear paths, the still-one-window guard, and the end-to-end roundtrip.
|
| |
|
|
|
|
| |
The %c column on agenda blocks rendered every project's todo.org as "todo:" -- org defaults the buffer category to the filename without extension, so every entry looked alike. An org-mode-hook now overrides org-category with the parent directory's basename (stripping a single leading dot, so ~/.emacs.d/todo.org reads as "emacs.d") whenever a todo.org file opens and its category is still the filename default. Explicit #+CATEGORY: keywords still win.
14 tests in test-org-agenda-config-category.el cover the helper's normal/boundary/error paths and the hook's override + explicit-category-preserved cases.
|
| |
|
|
|
|
|
|
| |
toggle-window-split swapped buffers between two windows via two set-window-buffer calls. If either window was strongly dedicated (e.g. *Org Agenda* via the display-buffer-alist rule), the dedicated window rejected the second swap. Both panes ended up showing the dedicated buffer. The non-dedicated buffer never made it across.
The fix clears dedicated on both windows before the swap. The user explicitly invoked a layout change, so preserving per-window dedicated through the operation would just re-trigger the same wedge on the next toggle.
Tests in tests/test-ui-navigation--toggle-window-split.el cover the no-dedicated baseline, the bug-trigger (dedicated selected window), the post-toggle cleared state, and the one-window / three-window no-op boundaries.
|
| |
|
|
|
|
|
|
| |
The "d" command's (agenda ...) block had no org-agenda-skip-function. The global org-agenda-skip-scheduled-if-done is nil. CANCELLED tasks with a SCHEDULED date rendered in the forward-looking schedule unfiltered.
The fix adds an org-agenda-skip-function to the SCHEDULE block: (org-agenda-skip-entry-if 'todo '("CANCELLED")). The scope is deliberate. Only CANCELLED is filtered, not DONE or FAILED. A scheduled DONE task is a record of when something happened and stays visible.
Tests cover the configuration: the form must appear on the agenda block and must not appear on the overdue, hi-pri, or priority-B blocks.
|
| |
|
|
|
|
| |
The fallback chain was checking `nov-epub-filename` and `nov-epub-file`, but neither symbol exists in nov.el — the real var is `nov-file-name`, set by `nov-mode` from the visited file. Both `boundp` arms always returned nil, so the fallbacks were dead code. The bug was dormant rather than active: `buffer-file-name` always holds the EPUB path for normal nov buffers and covered the first arm of the `or`.
I replaced both wrong-named arms with a single live arm on `nov-file-name`, and added a Boundary test that exercises it.
|
| |
|
|
| |
I pulled the image-centering math out of `cj/nov-center-images` into `cj/--nov-image-padding-cols`. The wrapper still loops over the image text properties and writes the line/wrap-prefix. The helper takes col-width, img-px, and font-width-px and returns the left padding. With the math in a pure helper I can unit-test the centering rule directly instead of building a buffer with image-display properties.
|
| |
|
|
| |
`cj/webdev-setup` called `(electric-pair-mode t)`, which flips the global minor mode every time a TS/JS/TSX buffer opens. I switched it to `electric-pair-local-mode` so the pairing stays scoped to those buffers, like the other buffer-local settings in the hook. (Same fix I just made in `prog-python.el`.)
|
| |
|
|
| |
I pulled the prettier command-string construction out of `cj/webdev-format-buffer` into `cj/--webdev-format-command`. The wrapper stays thin: resolve `(or buffer-file-name "file.ts")`, hand the command to `shell-command-on-region`, clamp point. With the command shape in a pure helper I can unit-test it directly.
|
| |
|
|
|
|
|
|
| |
byte-compile warnings
The `cj/drill-*` defuns and `cj/drill-map` lived inside the `use-package org-drill` `:config` block, so the byte-compiler never registered them — every cross-reference between them warned ("function `cj/drill-this-file' is not known", and so on). I moved all of that to module top level, where the compiler sees it. The ten `(setq org-drill-...)` lines became a `:custom` block (no more "assignment to free variable"). Added `(require 'user-constants)` and `(require 'keybindings)` for `drill-dir` and `cj/custom-keymap`, plus `declare-function` for `org-drill`, `org-drill-resume`, `org-capture`, and `org-refile`. The module byte-compiles clean now, and `C-; D` still mounts the drill submenu with the same leaf keys.
I also gave `tests/test-org-drill-first-function.el` a `cj/custom-keymap` stub: its "loads without error" test does a bare `load` of the module, which now runs the keymap mount at load time instead of deferring it inside `:config`.
|
| |
|
|
|
|
| |
vterm binds `<f1>`..`<f12>` to `vterm--self-insert`, so a plain `<f9>` typed while point is in an agent buffer goes to the terminal program instead of the global toggle. That's invisible most of the time — you press F9 from another window — but it bites when the agent buffer is the only window in the frame, because there's nowhere else to press it from.
I re-bound the F9 family in `vterm-mode-map` (via `with-eval-after-load 'vterm`) so that `<f9>`, `C-<f9>`, and `M-<f9>` reach `cj/ai-vterm`, `cj/ai-vterm-pick-project`, and `cj/ai-vterm-pick-buffer` from there too. The C-/M- variants aren't actually in vterm's intercept set, but binding them keeps things uniform. New `tests/test-ai-vterm--f9-in-vterm.el`: 4 ERT tests over the `vterm-mode-map` and global bindings. F12's `cj/vterm-toggle` has the same shape of bug and isn't touched here.
|
| |
|
|
|
|
| |
`S` ("study") in `dirvish-mode-map` opens the `.org` file at point and runs `cj/drill-this-file` on it, so I can drill any deck straight from the file list. It `user-error`s on no file, on a directory, or on a non-`.org` file.
`D` and `O` were already taken (duplicate-file, open-with-command), so I went with `S`. It shadows dired's `dired-do-symlink`, which I never use from dirvish and which stays on `M-x` anyway. New `tests/test-dirvish-config-drill.el`: 6 ERT tests with `dired-get-filename`, `find-file`, and `cj/drill-this-file` mocked. I also fixed the stale `P` line in the module commentary — `P` is the print command now, not copy-path.
|
| |
|
|
|
|
|
|
| |
`org-drill` has no fixed home — with `org-drill-scope` left at its default it just drills the current buffer. So the only thing in my config tied to a location was `cj/drill-start`, which forced a pick from `drill-dir`.
`C-; D f` (`cj/drill-this-file`) drills whatever Org buffer is current, so a drill file living anywhere works. It `user-error`s when the buffer isn't an Org buffer.
`C-u C-; D s` (and `C-u C-; D e`) now prompts for the directory to pick from instead of always using `drill-dir`. Bare `C-; D s` is unchanged. I pulled the picking logic into `cj/--drill-files-in`, `cj/--drill-pick-dir`, and `cj/--drill-pick-file` so it's unit-testable. New `tests/test-org-drill-config.el`: 12 ERT tests over those helpers, `cj/drill-this-file`, `cj/drill-start`, and the keymap.
|
| |
|
|
| |
`cj/python-setup` called `(electric-pair-mode t)`, which flips the global minor mode every time a Python buffer opens. I switched it to `electric-pair-local-mode` so the pairing stays scoped to Python buffers, like the other buffer-local settings in the hook.
|
| |
|
|
| |
I pulled the command-string construction out of `cj/python-mypy` and `cj/python-debug` into `cj/--python-mypy-command` and `cj/--python-debug-command`. The wrappers stay thin — resolve the target, hand off to `compile` or `pdb`. With the command shape in a pure helper I can unit-test it directly instead of stubbing `compile` and `pdb`.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The 80% width from `4d9a206' wasn't actually narrowing the page: `cj/nov-apply-preferences' set `nov-text-width' to t (nov renders the text unfilled, one long line per paragraph) and counted on `visual-fill-column-mode' to set the window's display margins, but those margins never got applied in nov-mode buffers (even after manually re-running the layout), so the text wrapped at the full window width. The cause is still unknown.
This drops `visual-fill-column' from nov entirely:
- `nov-text-width' is a column count (~80% of the window's natural width), so nov's `shr' fills the text itself.
- `cj/nov-update-layout' sets the window's left/right margins directly to `(natural - text-width) / 2' each, centering the block, and pushes the fringes out to the window edge so they don't show as thin lines beside the text. When the width changes it re-renders, restoring the reading position approximately.
- `cj/nov-apply-preferences' adds a `kill-buffer-hook' that drops the margins and fringes when the EPUB buffer goes away, so a later buffer in that window isn't left indented.
- `+'/`=' and `-'/`_' adjust `cj/nov-margin-percent' and re-flow + re-center.
The text-width math moved into a `cj/nov--natural-window-width' helper alongside the existing `cj/nov--text-width'. Known nit: the centering is a touch left-of-center because shr wraps at word boundaries, so the rendered text is a bit narrower than `nov-text-width' and the right margin ends up slightly larger. Logged as a follow-up.
|
| | |
|
| |
|
|
| |
`1c5c8bd' had set `cj/nov-margin-percent' to 12 (~76% text); 10 gives a round 80%. Adjust per-buffer with the `+'/`-' keys; clamp is unchanged (0..25, i.e. 50%..100%).
|
| |
|
|
|
|
| |
`C-; p SPC' (`cj/reveal-present') is the new fast path: it inserts reveal.js headers if the buffer doesn't have them (prompting for a title), saves the buffer, exports to self-contained HTML, and opens it in the browser. It's the export-and-open you'd otherwise reach by doing `C-; p h' then `C-; p e'. `C-; p H' (`cj/reveal-remove-headers') strips the header block this module inserts, and `C-; p h' (`cj/reveal-insert-header') now errors instead of duplicating headers when a reveal block is already present.
The header logic moved into small helpers (`cj/--reveal-has-header-p', `cj/--reveal-remove-headers', `cj/--reveal-ensure-header', `cj/--reveal-keyword-regexp', and the `cj/--reveal-header-keywords' list), and `test-org-reveal-config-headers.el' drives them directly: detection on a reveal vs. a plain Org buffer, removal of the generated block (and only the leading block), body preservation, and the no-duplicate-headers error.
|
| |
|
|
|
|
| |
The agenda buffer's `display-buffer-alist' rule used `(window-height . fit-window-to-buffer)', so a sparse agenda opened as a sliver a few lines tall. The rule now takes `(window-height . cj/org-agenda-window-height)', a defcustom defaulting to 0.75 (the fraction of the frame the agenda window gets), and the rule itself moved into `cj/--org-agenda-display-rule' so it's testable. New `test-org-agenda-config-display.el' checks that the configured fraction flows through, that it's no longer `fit-window-to-buffer', and (integration) that `display-buffer' produces a window near that size.
`(use-package alert)' gained an `:if (or (not noninteractive) (require 'alert nil t))' guard: the batch test runner loads this module without `package-initialize', so the optional notification package may be installed but not yet on the load path, and the unconditional `:config' setq's would error.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
`cj/nov--text-width-for-window' computed the target column as a percentage of `(window-body-width)'. But body width is the column count *after* the display margins. `cj/nov-update-layout' runs from `window-configuration-change-hook': it sets `visual-fill-column''s margins, which changes the body width, which fires the hook, which re-runs the layout against the now-narrower body, and so on. It's a shrinking feedback loop that bottoms out at `cj/nov-min-text-width' (40 columns) no matter what `cj/nov-margin-percent' is. That's why the column was a thin strip regardless of the margin setting.
The width is now computed from the window's *natural* column count (body width plus any margins already set), so re-running the layout is idempotent. The margin math moved into a pure `cj/nov--text-width' helper, which is what the unit tests drive, and there's a regression test that the result is the same whether or not margins are already in place.
Also:
- `+'/`=' (`cj/nov-widen-text') and `-'/`_' (`cj/nov-narrow-text') step `cj/nov-margin-percent' by `cj/nov-margin-step' and re-lay-out, reporting the new percentage. `cj/nov-margin-percent' is now clamped to 0..25, so the text column runs from 50% (the floor) to 100% (the full window).
- `cj/nov-margin-percent' default is 12 (≈76% text) for a comfortable starting width.
- `cj/nov-apply-preferences' re-renders the document at the end again. `b3b537f' removed that on the theory `visual-fill-column' would re-trigger the render. The first page came up off-center until a manual resize, so it's back.
- `cj/nov-update-layout' is now a command.
The visible result (a ~75% centered column on first open, `+`/`-` to adjust) needs a restart to confirm. The tests cover the width math and clamping, idempotency, the adjust commands and their keybindings, the command status, and the re-render.
|
| |
|
|
|
|
| |
`P' in dirvish/dired now runs `cj/dirvish-print-file': it sends the file at point to the default printer via CUPS (`lp', falling back to `lpr'), after a confirmation prompt. It refuses directories and file types outside `cj/dirvish-print-extensions' (pdf, txt, org, images, source files, ...). CUPS filters handle PDFs directly, so `lp <file>' covers everything in the list, and no separate print dialog is needed.
`P' was the project/home-relative variant of `cj/dired-copy-path-as-kill', now dropped (the `p' absolute-path and `l' org-link bindings stay, and `M-x cj/dired-copy-path-as-kill' still works). Plain dired's built-in `P' was `dired-do-print', which `dirvish-mode-map' had already shadowed, so this just replaces it with a type-aware version. Tests cover the predicates and the command's confirm / decline / no-file / directory / non-printable / no-printer / print-failure paths.
|