From f6fc37d74f0b027ea7cfb1c6ff5c9e362d8af465 Mon Sep 17 00:00:00 2001 From: Szymon Szukalski Date: Fri, 10 Apr 2026 13:44:48 +1000 Subject: Add Org refile configuration --- README.md | 5 + docs/plans/2026-04-10-org-refile-design.md | 70 +++++++++++++ docs/plans/2026-04-10-org-refile-implementation.md | 108 +++++++++++++++++++++ lisp/ss-org.el | 14 +++ tests/ss-org-tests.el | 88 +++++++++++++++++ 5 files changed, 285 insertions(+) create mode 100644 docs/plans/2026-04-10-org-refile-design.md create mode 100644 docs/plans/2026-04-10-org-refile-implementation.md create mode 100644 tests/ss-org-tests.el diff --git a/README.md b/README.md index 408ec5f..da53922 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,11 @@ and `WAIT` once the question has been asked and the answer is pending. Completing an item prompts for a note so the answer can be recorded in the task log, and those state logs are stored in a `LOGBOOK` drawer. +Org refile uses the current `org-agenda-files` set as its target space and can +move entries to any heading within those files. Refile selection uses full +outline paths, including the file name, so the existing Vertico, Orderless, and +Marginalia stack can present a clearer path-based destination prompt. + The configured capture templates cover: - journal tasks diff --git a/docs/plans/2026-04-10-org-refile-design.md b/docs/plans/2026-04-10-org-refile-design.md new file mode 100644 index 0000000..ae7e881 --- /dev/null +++ b/docs/plans/2026-04-10-org-refile-design.md @@ -0,0 +1,70 @@ +# Org Refile Design + +## Context + +The configuration already has the core minibuffer completion stack in place: + +- `lisp/ss-ui.el` enables Vertico, Orderless, and Marginalia globally. +- `lisp/ss-org.el` owns base Org configuration. +- `lisp/ss-agenda.el` already discovers `org-agenda-files` from the journal and + PARA directories. + +The missing piece is Org refile configuration. The requested behavior is to +refile Org entries to any heading in the current `org-agenda-files`, using a +single, path-oriented prompt that works well with the existing Vertico-based +completion UI. + +## Options Considered + +### 1. Recommended: configure built-in Org refile against `org-agenda-files` + +- Use Org's standard `org-refile` command and target machinery. +- Refresh `org-agenda-files` before refile so the target set stays aligned with + the existing agenda discovery rules. +- Configure path-based completion so Vertico and Orderless present the target + list cleanly. + +This keeps the workflow conventional, reuses existing repository structure, and +avoids maintaining a parallel refile implementation. + +### 2. Add a custom wrapper command with richer target formatting + +- Build a custom candidate list for headings across agenda files. +- Pass the chosen destination back into Org's refile internals. + +This could show more custom metadata, but it duplicates behavior Org already +provides and increases maintenance cost for little practical gain. + +### 3. Add more completion packages just for refile + +- Introduce a Vertico extension or a separate package to alter refile prompts. + +This adds package surface area without first exhausting the built-in Org and +completion capabilities already present in the config. + +## Chosen Design + +Use option 1. + +- Configure `org-refile-targets` to use `org-agenda-files` with unrestricted + heading depth. +- Enable outline-path completion so identically named headings are + distinguishable by their parent path. +- Use the direct, path-based completion flow rather than an additional outline + navigation step. +- Refresh `org-agenda-files` before refile by reusing the existing agenda file + discovery helper instead of copying the directory rules. +- Keep the change inside the existing module boundaries: Org behavior in + `lisp/ss-org.el`, with a UI tweak in `lisp/ss-ui.el` only if the current + completion categories need one. + +## Verification + +- Add focused ERT coverage for the refile setup helper and variable values. +- Run `emacs --batch -Q --load ./init.el` from the repository root. + +## Documentation Impact + +`README.md` should be updated to describe that Org refile targets any heading in +`org-agenda-files` and uses the configured minibuffer completion stack for +path-based target selection. \ No newline at end of file diff --git a/docs/plans/2026-04-10-org-refile-implementation.md b/docs/plans/2026-04-10-org-refile-implementation.md new file mode 100644 index 0000000..687e86d --- /dev/null +++ b/docs/plans/2026-04-10-org-refile-implementation.md @@ -0,0 +1,108 @@ +# Org Refile Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Configure Org refile so entries can be moved to any heading in `org-agenda-files` with a clearer Vertico-friendly path-based prompt. + +**Architecture:** Reuse the existing agenda discovery helper to keep `org-agenda-files` current before refile, then configure built-in Org refile variables in `lisp/ss-org.el`. Add narrow ERT coverage for the helper and settings, and update `README.md` so the documented workflow matches the configuration. + +**Tech Stack:** Emacs Lisp, Org mode, ERT, Vertico, Orderless, Marginalia + +--- + +## Chunk 1: Refile Configuration + +### Task 1: Add refile refresh helper and Org settings + +**Files:** +- Modify: `lisp/ss-org.el` +- Reference: `lisp/ss-agenda.el` + +- [ ] **Step 1: Write the failing test** + +Add ERT coverage that loads `ss-org`, stubs agenda refresh behavior, runs the +new setup helper, and asserts: + +- `org-refile-targets` points at `org-agenda-files` +- unlimited heading depth is enabled +- outline-path completion is enabled +- the direct path completion flow is selected + +- [ ] **Step 2: Run test to verify it fails** + +Run: `emacs --batch -Q -L lisp -l tests/ss-org-tests.el -f ert-run-tests-batch-and-exit` +Expected: FAIL because the refile helper and settings do not exist yet. + +- [ ] **Step 3: Write minimal implementation** + +- Add a small helper in `lisp/ss-org.el` that refreshes `org-agenda-files` + before refile, reusing `ss-refresh-org-agenda-files` when available. +- Configure Org refile variables during `ss-org-setup`. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `emacs --batch -Q -L lisp -l tests/ss-org-tests.el -f ert-run-tests-batch-and-exit` +Expected: PASS. + +- [ ] **Step 5: Commit** + +Wait for verification and user approval before creating a commit. + +## Chunk 2: Documentation and Startup Verification + +### Task 2: Document the refile workflow + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Write the failing test** + +The failing condition is documentation drift: the README currently does not +describe refile behavior. + +- [ ] **Step 2: Run test to verify it fails** + +Review `README.md` and confirm it lacks refile documentation. + +- [ ] **Step 3: Write minimal implementation** + +- Add a short note describing that Org refile targets any heading in + `org-agenda-files` and uses path-based minibuffer completion. + +- [ ] **Step 4: Run test to verify it passes** + +Review the updated text against the code for accuracy. + +- [ ] **Step 5: Commit** + +Wait for verification and user approval before creating a commit. + +### Task 3: Verify the full startup path + +**Files:** +- Verify only + +- [ ] **Step 1: Write the failing test** + +Use the repository's normal startup verification path in addition to the new +targeted ERT coverage. + +- [ ] **Step 2: Run test to verify it fails** + +Not applicable before implementation. + +- [ ] **Step 3: Write minimal implementation** + +No code changes in this task. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `emacs --batch -Q --load ./init.el` +Expected: exits successfully. + +Run: `emacs --batch -Q -L lisp -l tests/ss-org-tests.el -f ert-run-tests-batch-and-exit` +Expected: PASS. + +- [ ] **Step 5: Commit** + +Wait for verification and user approval before creating a commit. \ No newline at end of file diff --git a/lisp/ss-org.el b/lisp/ss-org.el index 6f1ae1b..88590fa 100644 --- a/lisp/ss-org.el +++ b/lisp/ss-org.el @@ -243,6 +243,19 @@ This is for significant navigation points only and clears forward history." (interactive) (find-file (ss-require-existing-file ss-moc-file))) +(defun ss-org-refresh-agenda-files-for-refile (&rest _args) + "Refresh `org-agenda-files' before refile when agenda setup is available." + (when (fboundp 'ss-refresh-org-agenda-files) + (ss-refresh-org-agenda-files))) + +(defun ss-org-configure-refile () + "Configure Org refile to target any heading in `org-agenda-files'." + (setq org-refile-targets '((org-agenda-files :regexp . "^\\*+ ")) + org-refile-use-outline-path 'file + org-outline-path-complete-in-steps nil) + (unless (advice-member-p #'ss-org-refresh-agenda-files-for-refile #'org-refile) + (advice-add 'org-refile :before #'ss-org-refresh-agenda-files-for-refile))) + (defun ss-org-setup () "Initialize base Org configuration." (use-package org @@ -259,6 +272,7 @@ This is for significant navigation points only and clears forward history." "DONE(d)" "CANCELLED(x@)")) org-log-done 'note org-log-into-drawer t) + (ss-org-configure-refile) (add-hook 'org-mode-hook (lambda () (setq-local org-hide-emphasis-markers t) diff --git a/tests/ss-org-tests.el b/tests/ss-org-tests.el new file mode 100644 index 0000000..b8e1eb5 --- /dev/null +++ b/tests/ss-org-tests.el @@ -0,0 +1,88 @@ +;;; ss-org-tests.el --- Tests for ss-org -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Focused ERT coverage for Org refile configuration. + +;;; Code: + +(add-to-list 'load-path (expand-file-name "../lisp" (file-name-directory load-file-name))) + +(require 'ert) +(require 'cl-lib) +(require 'org) +(require 'ss-org) + +(ert-deftest ss-org-configure-refile-sets-org-variables () + (let ((old-refile-targets (and (boundp 'org-refile-targets) + org-refile-targets)) + (old-refile-use-outline-path (and (boundp 'org-refile-use-outline-path) + org-refile-use-outline-path)) + (old-outline-path-complete-in-steps + (and (boundp 'org-outline-path-complete-in-steps) + org-outline-path-complete-in-steps))) + (unwind-protect + (progn + (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile) + (setq org-refile-targets nil + org-refile-use-outline-path nil + org-outline-path-complete-in-steps t) + (ss-org-configure-refile) + (should (equal org-refile-targets + '((org-agenda-files :regexp . "^\\*+ ")))) + (should (eq org-refile-use-outline-path 'file)) + (should-not org-outline-path-complete-in-steps)) + (setq org-refile-targets old-refile-targets + org-refile-use-outline-path old-refile-use-outline-path + org-outline-path-complete-in-steps + old-outline-path-complete-in-steps) + (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile)))) + +(ert-deftest ss-org-configure-refile-discovers-headings-in-agenda-files () + (let* ((file (make-temp-file "ss-org-refile-" nil ".org" + "* Alpha\n** Beta\n")) + (file-name (file-name-nondirectory file)) + (org-agenda-files (list file)) + (old-refile-targets (and (boundp 'org-refile-targets) + org-refile-targets)) + (old-refile-use-outline-path (and (boundp 'org-refile-use-outline-path) + org-refile-use-outline-path)) + (old-outline-path-complete-in-steps + (and (boundp 'org-outline-path-complete-in-steps) + org-outline-path-complete-in-steps)) + targets) + (unwind-protect + (progn + (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile) + (ss-org-configure-refile) + (with-current-buffer (find-file-noselect file) + (setq targets (org-refile-get-targets))) + (should (assoc (format "%s/Alpha" file-name) targets)) + (should (assoc (format "%s/Alpha/Beta" file-name) targets))) + (setq org-refile-targets old-refile-targets + org-refile-use-outline-path old-refile-use-outline-path + org-outline-path-complete-in-steps + old-outline-path-complete-in-steps) + (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile) + (when-let ((buffer (get-file-buffer file))) + (kill-buffer buffer)) + (delete-file file)))) + +(ert-deftest ss-org-configure-refile-adds-refresh-advice () + (unwind-protect + (progn + (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile) + (ss-org-configure-refile) + (should (advice-member-p #'ss-org-refresh-agenda-files-for-refile + 'org-refile))) + (advice-remove 'org-refile #'ss-org-refresh-agenda-files-for-refile))) + +(ert-deftest ss-org-refresh-agenda-files-for-refile-reuses-agenda-helper () + (let (called) + (cl-letf (((symbol-function 'ss-refresh-org-agenda-files) + (lambda (&rest _args) + (setq called t)))) + (ss-org-refresh-agenda-files-for-refile) + (should called)))) + +;;; ss-org-tests.el ends here \ No newline at end of file -- cgit v1.2.3