summaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
...
* feat(chime): limit the event tooltip to the next 3 daysCraig Jennings13 days1-2/+2
| | | | The tooltip looked ahead a full week (chime-tooltip-lookahead-hours was 7 * 24), which crowded it with events I don't need at a glance. I dropped it to 3 * 24, so it shows today, tomorrow, and the next day only. I also fixed the comment above it, which still claimed 10 events within 6 days when the code already said 20 within 7.
* feat(dashboard): add a Linear launcher and group the navigator by row sizesCraig Jennings13 days2-26/+50
| | | | | | I added a Linear entry to the launcher table, keyed l, with the nf-oct-issue_tracks octicon, opening the issue list via linear-emacs-list-issues. That makes 13 launchers, which no longer divides into the old rigid 4-per-row grid. So I replaced the fixed chunk-by-4 with an explicit cj/dashboard--row-sizes (4 4 3 2) and reordered the table so Telegram comes before Slack, putting Slack and Linear together on the last row. The button shape moved into cj/dashboard--navigator-button, shared by the grouped loop and a fallback row for any launchers the sizes don't cover. A test pins the row sizes to the launcher count so they can't drift.
* feat(linear): re-enable linear-config and wire the reworked command surfaceCraig Jennings13 days3-13/+89
| | | | | | | | linear-emacs grew a lot of new commands in its rework: filtered lists, saved queries, Custom Views, open-in-browser, comments, delete, and set-assignee/state/priority/labels on the issue at point. The config still listed and bound only the original seven, and the init.el require was commented out while the package was in flux. I re-enabled the require, expanded :commands to all 25 autoloaded commands, and rebuilt the C-; L keymap around them: lists and views up top, an o/r/D set for the issue at point, sync on s/S/u/U, and a C-; L e sub-prefix for editing the issue's fields. The lazy authinfo key-load advice and the data/linear.org path carry over unchanged. I verified the dependency symbols still exist before wiring, but the live connection check (C-; L ?) is still yours to run.
* chore(todo): archive resolved dashboard tasks to ResolvedCraig Jennings13 days1-98/+93
|
* docs(todo): close broad/misleading file-op clarificationCraig Jennings13 days1-14/+2
|
* fix(dwim-shell): make destructive file-op commands match their namesCraig Jennings13 days2-9/+51
| | | | | | | | Two commands did less, or more, than their names implied. remove-empty-directories ran find . -type d -empty -delete from whatever the current directory happened to be, so its scope was implicit and easy to misjudge. It now prompts for a root, names that root in the confirmation, and runs find against the shell-quoted root via cj/dwim-shell--empty-dirs-command. secure-delete ran shred without -u, so it overwrote a file's contents but left the file in place, not the deletion the name and the "permanently destroy" prompt promise. Added -u so it unlinks after overwriting.
* docs(todo): close video-concat filelist rebuildCraig Jennings13 days1-8/+2
|
* fix(dwim-shell): build video-concat filelist in elispCraig Jennings13 days2-6/+58
| | | | | | cj/dwim-shell-commands-concatenate-videos built the ffmpeg concat list with echo '<<*>>' | tr ' ' '\n' | sed 's/^/file /'. That splits on spaces, so any video whose name contains a space produced a broken list, and a name with a quote broke the echo outright. I extracted cj/dwim-shell--build-concat-filelist, which renders each path as an escaped file '...' line. I write that list to a temp file and run ffmpeg against the quoted listfile, with a trailing rm to clean up after the process exits. The <<*>> token stays only as an inert shell comment, since dwim-shell needs it to run one command over all marked files instead of once per file.
* docs(todo): close babel-confirm hardening, file keybinding follow-upCraig Jennings13 days1-11/+7
|
* fix(org-babel): confirm babel evaluation by default, toggle on a keyCraig Jennings13 days2-11/+47
| | | | | | | | org-babel-config set org-confirm-babel-evaluate to nil globally, so a source block in any Org file (a cloned repo, a downloaded note, a web clip) ran with no prompt. That's arbitrary code execution on opening the wrong file and hitting C-c C-c. I set the default to t (confirm) and replaced the old babel-confirm command, which only toggled under a prefix arg, with cj/org-babel-toggle-confirm. It flips confirmation off for the session when I'm in trusted files and back on when I'm done, bound to C-; k. The C-; k binding is a placeholder. I filed a follow-up to give it a permanent Org-prefixed home.
* docs(todo): close dwim-shell input-quoting, file concat-list follow-upCraig Jennings13 days1-20/+11
|
* fix(dwim-shell): quote and validate user-controlled shell inputsCraig Jennings13 days2-8/+120
| | | | | | | | Several dwim-shell commands interpolated user-controlled strings straight into shell templates, so a value with spaces, quotes, or shell metacharacters could break out of the command. The worst was git-clone-clipboard-url, which dropped raw clipboard contents into "git clone <<cb>>". I added three pure validators (git URL, ffmpeg timestamp, rename prefix) and fixed the interpolation sites. git-clone now validates the clipboard and passes the URL through shell-quote-argument instead of <<cb>>. The GPG recipient and the 7z archive name go through shell-quote-argument instead of hand-written single quotes. The thumbnail timestamp and the rename prefix are validated to a safe shape before they reach the command, so the unquoted interpolation that remains is constrained to digits, colons, and filename-safe characters. The fifth case in the ticket, the video-concat filelist built with echo/tr/sed, is a redesign rather than a quoting fix and is filed as a follow-up.
* docs(todo): close password temp-file fix, file 7z argv follow-upCraig Jennings13 days1-15/+11
|
* fix(dwim-shell): delete password temp file after the process exitsCraig Jennings13 days2-82/+211
| | | | | | | | The four password commands (PDF protect/unprotect, remove-zip-encryption, create-encrypted-zip) wrote the password to a temp file, launched an async dwim-shell command, then deleted the file in unwind-protect. Since the command is async, that delete ran the instant it launched, so qpdf or 7z could start after the password file was already gone. I extracted cj/dwim-shell--run-with-password-file and cj/dwim-shell--password-cleanup-callback. The temp file (mode 600) is now deleted from an :on-completion callback that fires after the process exits, on both success and failure, and the synchronous unwind-protect stays only as a backstop for a throw before the async launch. All four commands now go through the one helper. qpdf already reads the password via --password-file, so it stays out of the argv. 7z still takes it as -p"$(cat ...)", which lands on its command line. That's tracked as a separate follow-up.
* docs(todo): close SkyFi key-injection removalCraig Jennings13 days1-12/+2
|
* refactor(restclient): remove SkyFi key-injection featureCraig Jennings13 days3-225/+1
| | | | | | cj/restclient-skyfi-buffer opened the SkyFi template in a file-visiting buffer and rewrote the :skyfi-key line with the live key from authinfo. An accidental save would then persist the plaintext key to disk, which breaks the module's own "key never stored on disk" promise. The template file was gitignored and never tracked, so the exposure was local only, not a repo leak. I removed the feature rather than hardening it: cj/skyfi-api-key, cj/restclient--inject-skyfi-key, cj/restclient-skyfi-buffer, the C-; R s binding, and the two SkyFi test files are gone, along with the local template. The generic restclient setup stays: scratch buffer on C-; R n, open a .rest file on C-; R o.
* fix(linear): load API key for check-setup and pin org file to emacs homeCraig Jennings13 days3-5/+45
| | | | | | | | linear-emacs-check-setup read linear-emacs-api-key directly and bailed to "API key is not set" before making any request, so the lazy :before advice on the GraphQL request never fired for it. A fresh session always reported the key missing even though it was in authinfo. I extracted cj/linear--install-key-advice and put the loader on check-setup as well as the request entry point, with a regression test. I also pinned linear-emacs-org-file-path to data/linear.org inside emacs home, next to the calendar-sync output. Left to its default it falls back to org-directory/gtd/linear.org and silently created a stray ~/org tree on the first pull. The init.el require is commented out for now while linear-emacs is reworked. The config will need rework once that lands.
* feat(linear): wire linear-emacs into the config for DeepSatCraig Jennings13 days3-0/+153
| | | | | | I added modules/linear-config.el to load the local linear-emacs checkout (the same :load-path + :ensure nil shape gptel and org-drill use) and point it at DeepSat's Linear workspace. The personal API key comes from authinfo.gpg, loaded lazily by a named :before advice on the request function, so there's no GPG prompt at startup. The default team is DeepSat's Software Engineering team, and the commands sit under a C-; L prefix. Verified live against DeepSat: the connection authenticates, lists all five workspace teams, and pulls real issues (SE-*, DEE-*). Tests cover the key-loader (loads when unset, keeps an existing key, stays nil when absent) and the keymap wiring.
* docs(todo): triage hardening backlog — close done, tag soloCraig Jennings14 days1-89/+40
| | | | I triaged the ~50 open hardening findings against the codebase. Closed five whose premise was stale because the work already shipped: the personal-calendar-config split, the shared cache helper, the cache idle-timer gating, and AI-conversation-persistence coverage. Tagged 31 findings :solo: — bounded work an agent can finish and verify without a decision from me. The rest carry a preference, policy, or design call and stay untagged.
* docs(mail): document compose-buffer cleanup settingsCraig Jennings14 days1-0/+63
| | | | I added docs/mu4e-org-msg-compose-buffer-cleanup.org explaining the one setting that controls whether mail compose buffers close on exit (message-kill-buffer-on-exit), why org-msg needs no setting of its own (it reads the variable, never sets it), and the trap that a stray setter in org-msg's :config silently wins over the mu4e one.
* refactor(mail): consolidate compose-buffer kill policy to one homeCraig Jennings14 days1-7/+3
| | | | org-msg only reads message-kill-buffer-on-exit (in org-msg--widen-and-undo) and never sets it, so the duplicate setq in org-msg's :config was redundant. I removed it and kept the single t in the mu4e :config, with a comment noting org-msg honors whatever mu4e leaves in place. No behavior change. Compose buffers still kill on exit.
* docs(todo): record kill-compose-buffers-on-exit decisionCraig Jennings14 days1-2/+2
|
* feat(mail): kill org-msg compose buffers on exitCraig Jennings14 days1-8/+7
| | | | | | The earlier setup kept compose buffers after exit (org-msg set message-kill-buffer-on-exit to nil), so HTML draft buffers lingered after a message was sent or aborted. I want them cleaned up, so I set the org-msg value to t to match the mu4e default. Both composers now kill the buffer on exit. The modules byte-compile and the mail-config tests stay green. The kill-on-exit behavior itself only shows up in live use, not in batch.
* docs(todo): close mail compose lifecycle clarificationCraig Jennings14 days1-11/+2
|
* docs(mail): clarify message-kill-buffer-on-exit ownershipCraig Jennings14 days1-2/+8
| | | | | | mail-config.el set message-kill-buffer-on-exit to t in mu4e's config and nil in org-msg's, with no note on which wins. org-msg-mode runs in every compose buffer, so org-msg's nil is the effective policy: compose buffers are kept, not killed. The org-msg comment said "always kill buffers on exit", which is backwards. I rewrote both comments. The mu4e t is the plain-mu4e fallback that only matters if org-msg is ever disabled, and org-msg owns the live policy of keeping a draft buffer on exit so an in-progress HTML message isn't lost. No behavior change.
* docs(todo): close org-babel structure-template fixCraig Jennings14 days1-11/+2
|
* fix(org-babel): correct java structure-template language nameCraig Jennings14 days2-1/+33
| | | | The `<java` Org Tempo template expanded to "#+begin_src javas", a typo for "java", so a Java source block came out tagged with a bogus language. I fixed the entry to "src java" and added a test that pins seven common aliases (bash, zsh, el, py, json, yaml, java) to their intended src language names, so the next typo in the template list gets caught.
* docs(todo): close flyspell-and-abbrev coverageCraig Jennings14 days1-10/+2
|
* test(flyspell-abbrev): cover checker gate, overlay search, mode dispatchCraig Jennings14 days1-0/+101
| | | | | | flyspell-and-abbrev.el had no tests. I added eight: cj/--require-spell-checker (errors when no checker is on PATH, passes when one is), cj/find-previous-flyspell-overlay against synthetic overlays (finds the closest previous flyspell overlay, ignores non-flyspell ones, returns nil when none exist), and cj/flyspell-on-for-buffer-type dispatching to flyspell-prog-mode in code buffers and flyspell-mode in text buffers. I left cj/flyspell-then-abbrev to manual testing. Pinning its flyspell-UI orchestration would mean mocking flyspell internals rather than our own logic.
* docs(todo): close external-open/media-utils coverageCraig Jennings14 days1-13/+2
|
* test(media-utils): cover player discovery and play/download commandsCraig Jennings14 days1-0/+105
| | | | media-utils.el had no tests. I added eight: cj/get-available-media-players filtering by executable-find, cj/media-play-it building a direct command versus the yt-dlp -g stream-URL wrap (plus the missing-player error), and cj/yt-dl-it erroring when yt-dlp or tsp is absent and queueing through tsp + yt-dlp when both are present. Every external boundary is mocked, so nothing launches.
* docs(todo): close title-case edge coverage and mail/system-commands coverageCraig Jennings14 days1-21/+4
|
* test(custom-case): cover leading-quote, paren, and RTL title-case edgesCraig Jennings14 days1-0/+16
| | | | The state machine in cj/title-case-region skips leading non-word characters and passes caseless letters through, but no test pinned that. I added three boundary cases: a leading double-quote and a leading paren each still capitalize the first real word, and a caseless RTL first word (Hebrew) passes through while consuming the is-first slot, so the following minor word stays lowercase.
* docs(todo): close host-env predicate cleanupCraig Jennings14 days1-16/+3
|
* refactor(host-env): fix env-desktop-p doc and normalize the X predicatesCraig Jennings14 days1-4/+7
| | | | | | env-desktop-p's docstring described a laptop, but the function returns t for the desktop case (no battery). env-x-p compared the window system against the string "x" while its sibling env-x11-p used `eq` against the symbol, so the two read differently for the same check. I corrected the docstring and switched env-x-p to the symbol comparison. I also spelled out the difference between env-x-p (any X display, including XWayland) and env-x11-p (a real X11 session, no Wayland). Behavior is unchanged, so the existing display-predicate tests stay green.
* docs(todo): close dirvish hardening and coverage tasksCraig Jennings14 days1-25/+4
|
* fix(dirvish): guard nil file and reject path-traversal playlist namesCraig Jennings14 days3-12/+46
| | | | | | cj/set-wallpaper passed `(dired-file-name-at-point)` straight to `expand-file-name`, so running it with no file at point raised a bare `wrong-type-argument` instead of a clear error. cj/dired-create-playlist-from-marked expanded the raw playlist name under `music-dir` without checking it, so a name like "../foo" or "/etc/foo" would write outside the music directory. I added a nil-file guard to set-wallpaper and a `cj/--playlist-name-safe-p` check that rejects any name carrying a directory separator before the path is built. Both paths now fail cleanly with a user-error. Regression tests went into the existing wrapper and playlist test files.
* docs(todo): add :solo: tag and mark Claude-doable hardening tasksCraig Jennings14 days1-22/+19
| | | | I added a :solo: tag to the legend for tasks Claude can take end to end with no input from me: bounded scope, no design or preference call, verifiable locally. Tagged the nine hardening findings I've already assessed that way. Also closed the dirvish runtime-require task, shipped in b63c4f83.
* fix(dirvish): declare runtime constant/util deps with plain requireCraig Jennings14 days2-2/+32
| | | | | | dirvish-config builds `dirvish-quick-access-entries` from `code-dir`, `music-dir`, `pix-dir`, and the recording dirs at load time, and binds keys to `cj/xdg-open` and `cj/open-file-with-command`. Those come from user-constants and system-utils, but the module only required them under `eval-when-compile`, so the compiled module carries no runtime require and leans on init order having loaded them first. I switched both to plain requires, matching host-environment, system-lib, and external-open-lib right below. Added a dependency-contract smoke test that fails if the requires are dropped.
* docs(todo): record TRAMP/dirvish "?" root cause, leave fix to verifyCraig Jennings2026-05-221-15/+15
| | | | | | I traced why remote dirvish shows "?" for dates: dirvish fetches remote attributes through an async `ls -1lahi` parser that only runs when the connection has direct-async and the remote has GNU ls. It falls back to skipping `file-attributes` (rendering "?") when either gate is shut. That's why the earlier dired-listing-switches attempts missed the real path, and why disabling direct-async made it worse. The config already enables direct-async, so the rest is per-host and needs a live remote. I left the three diagnostic evals and the likely fixes in the task body.
* docs(todo): close org-log-done reconcileCraig Jennings2026-05-221-2/+3
|
* refactor(org): give org-log-done a single homeCraig Jennings2026-05-223-2/+27
| | | | | | `org-log-done` was set in two places: `cj/org-todo-settings` in org-config.el set it nil, and org-roam-config.el's `:config` set it to 'time. Whichever module loaded last won, so the effective value was load-order-dependent and fragile. I set it once in `cj/org-todo-settings` and dropped the org-roam-config setter, leaving a comment at the old site so it doesn't creep back. The value is 'time rather than nil because the dated-completion workflow wants a CLOSED timestamp stamped on every TODO->DONE.
* docs(todo): close always-save-daily taskCraig Jennings2026-05-221-2/+3
|
* fix(org-roam): always save the daily after a journal task-copyCraig Jennings2026-05-222-5/+66
| | | | | | The save lived inside the `unless` branch that only ran when the completed task needed an `org-refile` into a different file. When the task was already in today's daily, the copy left the buffer modified but unsaved. A crash before the next manual save lost it, and shutdown prompted about the unsaved journal buffer. I pulled the save out of the refile branch into a `cj/--org-roam-save-daily` helper that runs on both paths and only writes when the buffer is modified. Extracting it also makes the save logic testable without driving the org-roam capture machinery.
* docs(todo): close auth-source consolidationCraig Jennings2026-05-221-1/+3
| | | | I closed the auth-source-idiom consolidation. The four copies now delegate to one system-lib helper.
* refactor(auth): consolidate the auth-source secret lookup into one helperCraig Jennings2026-05-227-33/+123
| | | | | | | | The auth-source-search + funcall-the-secret block was copied four times: calendar-sync--calendar-url, cj/auth-source-secret (ai-config), cj/--auth-source-password (transcription), and cj/slack--get-credential. Each searched authinfo, pulled :secret, and called it when the netrc backend returned a function. I pulled that into cj/auth-source-secret-value in system-lib (a leaf, so calendar-sync doesn't have to depend on ai-config and drag in the gptel stack). It takes an optional user and returns the secret or nil. The four callers now delegate to it: ai-config layers its required-secret error on top, and the others keep their nil-on-miss behavior. With the direct auth-source-search calls gone, I dropped the now-unused (require 'auth-source) from transcription, slack, and calendar-sync. The helper's autoload covers it. The transcription tests that exercise the delegated path stay green, and the primitive and the error wrapper get their own tests.
* docs(todo): close dashboard navigator/keymap dedupCraig Jennings2026-05-221-1/+3
| | | | I closed the navigator and keymap duplication task. Both now derive from one launcher table.
* refactor(dashboard): derive the navigator and keybindings from one launcher ↵Craig Jennings2026-05-222-81/+147
| | | | | | | | table The 12 dashboard launchers were inlined twice (once as navigator icon buttons, once as dashboard-mode-map keybindings), so adding or reordering one meant editing both lists, and the icon-row order could drift from the key order. I pulled them into a single cj/dashboard--launchers table of (KEY ICON-FN ICON-NAME LABEL TOOLTIP ACTION) tuples. cj/dashboard--navigator-rows chunks it four per row into the navigator buttons, and cj/dashboard--bind-launchers binds each key to its action. The icons and the keys now come from one place, with no behavior change: same icons, labels, order, and keys, locked by tests.
* docs(todo): close dashboard subtitle and color bugs, file navigator-color ↵Craig Jennings2026-05-221-4/+11
| | | | | | follow-up I closed the subtitle-centering and the navigator/item color bugs, and filed a follow-up to give the navigator its own color separate from the list items, which is blocked by the shared dashboard-items-face overlay.
* fix(dashboard): center the banner subtitle and color the navigator and itemsCraig Jennings2026-05-222-2/+2
| | | | | | The banner subtitle sat left of center because dashboard-banner-title-offset was 5, which over-shifts. I dropped it to 3, which lines the subtitle up under the banner image. The navigator and the recentf/project/bookmark list rendered in the default near-white. I set dashboard-items-face to steel+2 so they pick up a theme color, and the section headers stay blue via dashboard-heading. The navigator and the items share dashboard-items-face, because the navigator is drawn with a dashboard-items-face overlay that wins over its per-button dashboard-navigator face, so they take one color by design here.