summaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* docs(todo): close coverage backlog after assessing the sub-60% clusterload-graph-classify-startCraig Jennings12 days1-19/+13
| | | | Read each remaining sub-60% module to separate real untested logic from interactive/config glue. Filled the three genuine gaps (markdown-html, media-utils select-media-player, elfeed helpers — committed separately). The rest — flyspell, dashboard, ai-quick-ask — already have their pure logic tested with Normal/Boundary/Error; their low percentages come entirely from interactive commands. prog-general, restclient, vc-config, quick-video-capture are config/interactive-only with no pure logic to cover. Backlog cleared: every module's testable logic is covered; residual low % is code the testing rules say not to chase.
* test: cover markdown-html filter and media-player selectorCraig Jennings12 days2-0/+53
| | | | Two real-logic gaps from the refreshed coverage backlog. cj/markdown-html (markdown-config) is the impatient-mode filter that wraps a source buffer's text in the strapdown HTML shell — tested for normal content and an empty buffer. cj/select-media-player (media-utils) was the one untested function there — tested that choosing an available player updates cj/default-media-player and that a non-matching selection leaves it unchanged. Both mock at the boundary (completing-read, the source buffer).
* docs(todo): refresh stale coverage backlog against a clean runCraig Jennings12 days1-318/+18
| | | | The per-module percentages in the coverage audit were stale — this session's test-writing covered most of the listed modules (prog-python 0%->100%, hugo-config 17.7%->91.7%, undead-buffers 5.7%->85.7%, and selection-framework / keyboard-compat / system-utils / system-defaults / ui-navigation / prog-go now ~100%). Re-measured against a clean make-coverage run and replaced the ~75 stale entries with current numbers for only the modules genuinely below ~60%. Modules in the 60-80% band are adequately covered and dropped. vc-config and quick-video-capture are flagged config/interactive-only (their pure logic is tested; the uncovered lines are use-package/interactive glue), so they're recorded as no-action rather than chased.
* test(elfeed): cover extract-stream-url and process-entries helpersCraig Jennings12 days1-0/+109
| | | | elfeed-config had only the youtube-feed-format helper under test; cj/extract-stream-url and cj/elfeed-process-entries were untested despite having clear error/boundary paths. Added characterization + Normal/Boundary/Error coverage: extract-stream-url returns the trimmed URL on success, nil on non-URL output or nonzero exit, and signals when yt-dlp is absent; process-entries applies the action per selected entry and marks read, errors when nothing is selected, skips entries with no link, catches per-entry action errors by default, and propagates them under skip-error-handling. yt-dlp (call-process) and the elfeed-search API are stubbed at the boundary.
* docs(todo): log the third solo-hardening batch (move-branch, keymaps, ↵Craig Jennings12 days1-64/+10
| | | | export, elfeed/eww, capture tests)
* test(org-capture): smoke-test template key uniqueness and file targetsCraig Jennings12 days1-0/+67
| | | | Org capture templates are assembled across org-capture-config, quick-video-capture, org-contacts-config and other modules, so a duplicate dispatch key or a file target pointing at an unset path variable would be easy to miss. Added a smoke test that loads the cleanly-loadable capture modules, applies their lazy additions, and asserts no two templates share a key and that every symbol-valued file target resolves to a non-empty string path. Literal-string targets (the video template's no-save (file "")) and lambda targets (the drill file pickers) are intentionally excluded; webclipper templates need org-web-tools and are covered by their own test.
* fix(elfeed): bound and clean up the synchronous YouTube fetchCraig Jennings12 days3-35/+171
| | | | | | | | cj/youtube-to-elfeed-feed-format called url-retrieve-synchronously with no timeout, so a hung YouTube request would block Emacs indefinitely, and it only killed the temporary URL buffer when an ID was successfully extracted — a page without the expected markers leaked the buffer. Passed cj/elfeed-url-fetch-timeout (10s) to the synchronous fetch, and moved the fetch+parse into an unwind-protect that always kills the temp buffer (live-p guarded), including the parse-failure path. Tests mock the network boundary and cover a normal channel parse, that a timeout is passed, and that the buffer is not leaked when parsing fails. Also added tests for the EWW user-agent advice (no code change): it already injects the desktop UA only from eww-mode buffers, so package.el and other non-EWW url callers pass through untouched — the tests pin that scoping and the replace-not-duplicate header behavior.
* fix(org-export): remove contradictory org-export-with-tasks defaultCraig Jennings12 days2-1/+22
| | | | | | org-export-config.el set org-export-with-tasks twice in a row — first to ("TODO"), then to nil. The final value won (export no tasks), but the stale first assignment and its "export with tasks by default" comment contradicted it, so the intended policy was ambiguous on a read. Removed the leftover ("TODO") line. nil is the deliberate default: it is the value that was already winning, its comment matches, and it sits with the adjacent "export without tags / section numbers by default" settings. Added a smoke test that fires the deferred ox :config and pins org-export-with-tasks to nil so a future flip is caught.
* refactor: declare cross-module commands bound in custom keymapsCraig Jennings12 days2-0/+10
| | | | | | custom-ordering.el binds cj/org-sort-by-todo-and-priority (owned by org-config) and custom-text-enclose.el binds change-inner/change-outer (the change-inner package). Both work at runtime — org-config loads eagerly and text-config autoloads change-inner via use-package :commands — but byte-compiling either module standalone warned "not known to be defined", and the dependency was implicit. Added declare-function for each so the compile is clean and the cross-module relationship is explicit at the top of the file. No autoload needed: the runtime autoload/eager-load already exists, so only the compiler needed telling. custom-buffer-file.el byte-compiles clean already, so it needed no change.
* fix(org-roam): guard move-branch-to-roam against data lossCraig Jennings12 days2-22/+63
| | | | | | cj/move-org-branch-to-roam cut the subtree from the source buffer before writing the new roam file, so a failure in the demote/format/write/db-sync steps left the subtree gone from the source and not persisted anywhere — a destructive operation with no rollback. Reordered so the node file is written and verified on disk before org-cut-subtree runs; a failed write now aborts with the source untouched. Added a no-clobber guard (refuse an existing target file) and a confirmation prompt for large subtrees (>= cj/move-org-branch-confirm-lines, 30) or buffers with unsaved changes. The source buffer is deliberately left modified and undoable rather than auto-saved, so the move stays reversible. New test drives the write-failure-preserves-source invariant via an unwritable roam dir; the existing creates-roam-file test gained the confirm mock.
* refactor(linear): point config at the renamed pearl packageCraig Jennings12 days4-94/+94
| | | | | | The linear-emacs package was renamed to pearl (~/code/pearl, feature pearl, all symbols pearl-*). Swapped every linear-emacs-* reference to pearl-* across linear-config.el (the use-package form, :load-path, the 26 :commands, the api-key/default-team-id/org-file-path vars, and the lazy-key advice targets pearl--graphql-request-async and pearl-check-setup), the dashboard launcher, and the two test files. Kept the Linear-domain naming intact, since pearl is just a client for the Linear service: the C-; L prefix, the cj/linear-* wrapper helpers, the "Linear" dashboard label, the api.linear.app authinfo host, and the data/linear.org synced file are unchanged. Verified the wiring in a live daemon — pearl loads, the team id and org-file path apply, and the key advice installs on both entry points.
* docs(todo): log the second solo-hardening batch (webclip, qvc timers, dir ↵Craig Jennings12 days1-50/+11
| | | | scans, vc cache, region scope)
* refactor(text-enclose): extract shared region-or-buffer bounds helperCraig Jennings12 days2-24/+64
| | | | | | The append/prepend/indent/dedent *-in-region-or-buffer commands each inlined the same (if (use-region-p) (region-beginning) (point-min)) / (region-end)/(point-max) block — four copies of the "operate on the region, else the whole buffer" contract. Extracted cj/--region-or-buffer-bounds as the single source of that decision and routed all four through it. Behavior is unchanged; the public-wrapper tests still pass. This was the "extract a shared helper that decides the target range" option from the reconcile task. The sibling custom-ordering.el helpers (cj/--arrayify, cj/--unarrayify) already document an explicit (start end) contract accurately and are region-required by design, so they needed no docstring change — each pair now has one clear, consistent contract. Tests cover the helper for the region case, the no-region whole-buffer case, and an empty buffer.
* fix(modeline): key VC cache on resolved truename for symlink movesCraig Jennings12 days2-2/+62
| | | | | | The VC modeline cache keyed on (list file cj/modeline-vc-show-remote). If file was a symlink whose target moved to a different VC tree (shared drives, CI workspaces), the key was unchanged and the cache kept serving the old branch/state. Added the resolved file-truename to the key, so a symlink re-pointed at a new target produces a different key and the cache refreshes. The extra file-truename is one stat per modeline refresh, cheap next to the VC calls the cache exists to avoid. Tests cover truename inclusion, key stability for an unchanged file, and a symlink whose target moves.
* fix(org): surface directory-scan failures instead of crashing or hiding themCraig Jennings12 days4-18/+124
| | | | | | | | The refile target scan caught permission-denied and silently dropped the directory, and would crash outright on a missing root (only permission-denied was caught, so a missing code-dir/projects-dir raised file-missing and aborted the whole build). The agenda build had the same crash: cj/add-files-to-org-agenda-files-list called directory-files on projects-dir with no existence check. Extracted cj/--org-refile-scan-dir, which warns (display-warning) and returns nil for a missing, unreadable, or permission-denied root so the rest of the scan continues. Guarded the agenda scan the same way. Both now log a concise warning naming the skipped directory rather than failing silently or fatally. Also fixed a latent bug surfaced here: org-refile-targets was never declared special, so under make compile cj/org-refile-in-file let-bound it lexically and the scoped targets never reached org-refile. Added (defvar org-refile-targets) so the binding stays dynamic when byte-compiled. Tests cover the helper (missing/permission-denied/normal) and the agenda missing-dir guard.
* refactor(video-capture): drop startup timers for lazy protocol initCraig Jennings12 days2-29/+80
| | | | | | quick-video-capture scheduled an after-init-hook idle timer plus a 2-second fallback run-with-timer to call cj/setup-video-download, which require-d org-protocol and org-capture and registered both the protocol handler and the capture template. That loaded Org protocol/capture plumbing at every startup even when the video workflow was never used. Split the two concerns the way org-webclipper already does. The org-protocol handler is registered in a with-eval-after-load (quote org-protocol) block — a lightweight add-to-list that needs no org-capture — so it is in place whenever org-protocol loads (org-config requires it at startup). cj/setup-video-download now registers only the capture template, lazily, on the first capture (org-capture-mode-hook) or the first protocol call (the handler ensures it). Both startup timers are gone. Tests pin that setup registers the template idempotently and no longer touches the protocol alist; verified in a live daemon that the protocol registers on load.
* refactor(webclipper): scope clip URL/title to dynamic bindingsCraig Jennings12 days2-45/+60
| | | | | | org-webclipper passed the org-protocol URL and title through globals cj/webclip-current-url / cj/webclip-current-title: the protocol handler setq them, and the "W" capture template plus its handler read them, with the handler clearing them afterward. An aborted or erroring capture left the stale values for the next clip. Renamed them to cj/--webclip-url / cj/--webclip-title and let-bind them around the org-capture call in the protocol entry point instead of mutating globals. The template %(identity ...) forms and the handler run within that dynamic extent, so they see the values while the capture runs, and an abort/error unwinds the binding automatically — no stale state, no manual clear. This mirrors the quick-video-capture fix. Tests updated to the new contract: URL/title visible during the capture, nothing left bound after, and an aborted capture leaves no stale state.
* docs(todo): log the org-drill, git-clone, and video-capture hardeningCraig Jennings13 days1-33/+7
|
* refactor(video-capture): scope capture URL to a dynamic bindingCraig Jennings13 days2-13/+90
| | | | | | quick-video-capture passed the org-protocol URL through a global cj/video-download-current-url: the protocol handler setq the global, the capture handler read and cleared it. If a capture was aborted or errored between those steps, the stale URL survived into the next manual capture. Renamed it to cj/--video-download-url and let-bind it around the org-capture call in the protocol handler instead of setq-ing a global. The binding lives only for the dynamic extent of the capture, so the handler still sees the URL while the capture runs, and an abort or error unwinds the binding automatically — no stale state, no manual clear. The handler still prompts when invoked manually with no URL bound. Tests cover the bound-URL download, the manual prompt, the empty-URL error, that the URL is visible during the capture, and that an aborted capture leaves nothing behind.
* fix(vc): harden clipboard git-clone process and path handlingCraig Jennings13 days2-13/+122
| | | | | | cj/git-clone-clipboard-url shelled out via shell-command and derived the clone directory with file-name-nondirectory, which mishandles scp-style SSH URLs with no slash (git@host:repo.git became git@host:repo). It also ran git in default-directory and only checked whether the clone dir appeared afterward, so a failed clone was silent. The clone now runs as a direct git process (call-process, no shell) with clone -- url dir so a URL beginning with - cannot be read as a flag. The destination path comes from cj/--git-clone-dir-name, which takes the last component splitting on / and :, handling HTTPS, scp-style and ssh:// SSH, and local paths. It validates the clipboard is non-empty and the target is a writable directory that does not already contain the destination, and surfaces a non-zero git exit as a user-error with the *git-clone* output. Tests cover the deriver across URL schemes plus the empty-clipboard and clone-failure paths.
* refactor(org-drill): share one validated drill-file selectorCraig Jennings13 days3-11/+73
| | | | org-capture-config.el and org-drill-config.el each scanned drill-dir with an inline directory-files call, so a missing, empty, or unreadable drill-dir surfaced as a low-level directory-files error or an empty completing-read, depending on which command ran. Added cj/--drill-files-or-error, the single validated entry point: it signals a clear user-error when the directory is missing, unreadable, or has no drill files, and otherwise returns the list. cj/--drill-pick-file and both drill capture templates now route through it. The pure cj/--drill-files-in primitive and its tests are unchanged. Tests cover missing dir, empty dir, a non-org-only dir, and a normal listing.
* docs(dwim-shell): record accepted 7z password-on-argv tradeoffCraig Jennings13 days2-15/+22
| | | | 7-Zip 26.01 reads the encryption password only from its controlling TTY, not stdin or a file — a piped password silently becomes an empty one — so it has to go on argv and is briefly visible in the process list. Rather than switch off the .7z format to gpg-wrapped tar, the exposure is accepted: single-user workstation, short-lived process, password already kept out of shell history by the mode-600 temp file. Documented the evaluated tradeoff in both encrypt/decrypt docstrings so it's visible at the call site.
* docs(todo): log the defensive restart/shutdown hardeningCraig Jennings13 days1-15/+3
|
* fix(system-commands): make Emacs restart and destructive confirms defensiveCraig Jennings13 days2-59/+150
| | | | | | Restart-Emacs scheduled an unconditional kill-emacs one second after firing the systemctl restart. If the service was missing or the restart failed, the session still got killed with nothing to replace it. Restart now guards on (daemonp) and a present emacs.service before doing anything, and drops the separate kill-emacs entirely — systemctl restart cycles the daemon itself, so a failed restart leaves the current Emacs alive. Added cj/system-cmd--emacs-service-available-p (systemctl --user cat) for the guard. Shutdown and reboot now use a strong yes-or-no-p confirm instead of the quick (Y/n) read-char, where RET or space counted as yes — a stray Enter at the prompt could power off the machine. Logout and suspend keep the quick confirm since they are recoverable. The confirm tier rides on a property set by cj/defsystem-command. Tests cover service detection, both restart guards, and the strong-confirm accept/decline paths with the system primitives stubbed.
* docs(todo): log the three video-audio-recording hardening fixesCraig Jennings13 days1-30/+6
| | | | Closed the X11/audio shell-quoting, the scoped wf-recorder stop signal, and the selected-directory creation as dated event-log entries under the recording hardening tree.
* fix(recording): create the selected recording directory, not its parentCraig Jennings13 days2-14/+66
| | | | | | The recording toggles took a directory from the prefix-arg prompt (or the default), then ran (file-name-directory location) before make-directory. For a path without a trailing slash that returns the parent, so make-directory created the parent and left the selected directory uncreated — ffmpeg then failed to write into it. Both toggles now route the destination through cj/recording--normalize-recording-dir, which expands and applies file-name-as-directory, then call make-directory on that normalized path. The selected directory itself is created (parents=t is a no-op when it already exists), including names with spaces. Tests cover trailing-slash normalization, idempotence, spaces, and relative-to-absolute expansion.
* fix(recording): scope wf-recorder stop signal to our own processCraig Jennings13 days2-4/+75
| | | | | | Stopping a Wayland recording ran pkill -INT wf-recorder, which signals every wf-recorder on the system — including an unrelated screen capture the user started outside Emacs. The stop path now scopes the producer-first interrupt to the wf-recorder child of our own recording shell via pkill -P <shell-pid>, in the new cj/recording--interrupt-child-wf-recorder helper. The producer-first ordering is unchanged: wf-recorder still gets SIGINT before the process-group signal so ffmpeg sees a clean EOF on pipe:0 and finalizes the MKV. The orphan-cleanup at recording start stays a broad by-name kill on purpose — those leftover recorders come from crashed sessions whose shells are already dead, so there is no live PID to scope to. Tests cover the scoped call, the nil-PID no-op, and that the bare system-wide form is never used.
* fix(recording): shell-quote device names and output paths in ffmpeg commandsCraig Jennings13 days3-19/+117
| | | | | | The X11 video path and the audio path interpolated the mic device, system device, and output filename straight into the shell command, so a device name or recording directory with a space (or other shell metacharacter) would break the command or mishandle the path. The Wayland video branch already quoted these; the other two did not. I wrapped all three in shell-quote-argument on both paths. To make the audio command testable, I extracted it into cj/recording--build-audio-command mirroring the existing cj/recording--build-video-command, then quoted there. Tests cover device names and filenames with spaces on both the X11 and audio builders.
* docs(todo): close C-s isearch task — verified non-bug on Emacs 30.2Craig Jennings13 days1-10/+3
| | | | While isearch is active, overriding-terminal-local-map is isearch-mode-map, so C-s resolves to isearch-repeat-forward and the global cj/consult-line-or-repeat binding can't shadow it. isearch-mode-map already binds C-s to that default, so I left selection-framework.el unchanged.
* docs(ai-kb): fold in review 6 and resolve the build-time decisionsCraig Jennings13 days2-39/+91
| | | | | | The latest design review was a UX and performance pass, and I folded its findings into the spec and the implementation tasks. The important one: human Emacs edits now use the same write path as agent writes. An ai-kb minor mode runs index, full lint, and commit under flock on after-save, so a hand edit can't quietly skip the safety gate. The rest: the generated index.org is now invisible to backlink and orphan logic (excluded from the scan, referenced as plain text rather than id-links), a required :SUMMARY: property feeds the index and query without inference, query gains lexical ranking with recency only as a tie-break, the switch installs a full org-roam profile rather than a two-variable swap, and the browsing surface (dashboard, find, search, show, backlinks, map) is named. I also answered the six build-time decisions: concrete raw and curation limits, performance budgets for the perf fixtures, the lexical scoring weights, org-roam-graph as the first map implementation, the after-save failure UX (the save always lands, the commit is gated, and a failure shows without trapping the buffer), and the after-save recursion guard. The numeric limits and budgets are starting points to calibrate. The rest are firm. Step 1 stays buildable.
* docs(todo): split Implement ai-kb tasks into 1a/1b and fold in review-5 ↵Craig Jennings13 days1-0/+43
| | | | | | hardening I reorganized the Implement ai-kb children into the Step 1a / 1b phases from the spec: 1a is the safe write path (store, contract, the index/lint/remember/doctor CLI, the adapter, provisioning), and 1b is query/curate/sync, the push timer, and the curation workflow. The remember task now gates the commit on the full ai-kb lint rather than node org-lint, the test tasks target the org-lint fatal-check list and the query --json contract, the push task carries the failure-observability surfaces, and the pointer convention is ID-first throughout.
* docs(design): incorporate ai-kb review 5Craig Jennings13 days1-21/+38
| | | | | | Review 5 was implementation-hardening, all of it sound, so I folded in all six findings. The important one: the commit gate now runs the full ai-kb lint over the change (index freshness, duplicate IDs, broken links, and a secret scan of nodes and raw/), not just org-lint on the edited node. If the write path is the safety boundary, gating only on single-node syntax would let a stale index or a leaked secret through. The rest, all adopted: an explicit org-lint fatal-check list so a future org-lint change can't silently move the gate, observable push failures surfaced through a state-file log and ai-kb doctor and a startup nudge so the KB can't go quietly local-only, a testable ai-kb query contract with text and --json output, and ID-first durable pointers since filenames change in curation but IDs don't. I also split the build plan into Step 1a (the safe write path) and 1b (query, curate, sync, push timer, workflow), since remember depends on index and lint and the adapter depends on remember.
* docs(design): resolve ai-kb open decisions and refresh provisioningCraig Jennings13 days1-10/+11
| | | | | | I resolved the four open decisions and baked the answers in: the store lives at ~/.local/share/ai-kb (XDG), the ai-kb CLI is a shell wrapper that calls emacs --batch for the org-lint and sync steps, the push runs off a background systemd --user timer rather than firing on every write (remember only commits locally), and curation is node-count-triggered with the workflow living in the rulesets .ai/workflows/ directory. I also refreshed the provisioning and Step 1 sections to match, since the push timer was a new piece: make ai-kb-init now installs and enables the ai-kb-push timer and service units, and doctor checks for them. Open decisions is empty now and the spec is fully decided.
* docs(design): fold ai-kb reviews 3-4 into the specCraig Jennings13 days1-115/+151
| | | | | | Reviews 3 (Codex, via Nexus/GraphRAG/Letta research) and 4 pushed on the write loop and the access layer rather than scope. I folded both in. The write path is now a real protocol: fetch and fast-forward before writing, org-lint the node, regenerate the index, commit locally always, and treat the push as best-effort and non-blocking so a failed push never errors or hangs the agent. That's the exact gpg-agent failure we hit earlier today. The index is regenerated from node properties by a script rather than hand-maintained, so it can't drift from the nodes. The access layer became an agent-neutral contract that lives in the repo, fronted by a minimal ai-kb CLI (doctor, query, remember, lint, curate, sync) with destructive operations human-only. That earns its place on Claude-only grounds: it's the clean home for the safe-write protocol and the lint and index steps. Cross-agent use is not a near-term goal, so Codex and Ollama adapters are deferred to vNext. The contract stays neutral in shape, so they're additive later. Added provenance fields, the T1/T2/T3 tier names, and the review dispositions. The spec is now Ready.
* docs(design): add ai-kb spec — global org-roam memory store for the agentCraig Jennings13 days1-0/+244
| | | | | | ai-kb is a global, durable, cross-project memory store for Claude Code: org-roam nodes holding lessons, principles, my preferences, and reusable procedures, distinct from the per-project memory files (which shrink to an index pointing into it). The spec covers the two-layer model (a git-versioned file store the agent reads/writes, and an Emacs switch command so I can browse it with backlinks), the sync model, the routing and proactive-write rules, the node format, and the startup retrieval contract. It folds in two reviews. The scope decision: v1 is the memory store, not a full Karpathy LLM Wiki. The heavy machinery (compiled wiki layer, source hashes, formal ingest pipeline, embedding search) is deferred to vNext, each with a reason. Storage is a dedicated private git repo at an XDG path rather than Syncthing or the public emacs-config repo, which would leak personal notes. Two Karpathy ideas earned their way into v1 because they pay off now: capturing the raw source when a node is compiled from external material, and an org-lint validity check on every write so malformed org never reaches the index. Review dispositions and the open decisions are recorded in the spec.
* feat(org): label the C-; O org prefix in which-keyCraig Jennings13 days1-0/+22
| | | | The C-; O prefix (cj/org-map) had no which-key labels, so the popup just showed raw command names, and nothing at all for the d (finalize-task) binding. I added labels for the whole prefix, including the r/c table sub-prefixes. The two org-show-all bindings are labeled "cancel sparse tree" (S) and "cancel todo tree" (T) so the popup shows what each one cancels rather than two identical "show all" entries.
* feat(org-tidy): mark collapsed property drawers with a middle dotCraig Jennings13 days1-1/+2
| | | | org-tidy's default inline marker is the music sharp (♯), which reads as a full-size # next to a heading. I set org-tidy-properties-inline-symbol to a middle dot (·) so the collapsed drawer is marked with something far less visually heavy.
* 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
|