Skip to content

Latest commit

 

History

History
476 lines (380 loc) · 17.1 KB

Workflow.org

File metadata and controls

476 lines (380 loc) · 17.1 KB

Org Mode Workflow

This file contains the description and configuration of the productivity workflow I use with Org Mode.

Table of Contents

Principles

Don’t look at Org files to find the next task, use the agenda view

Ensure sufficient metadata on tasks for effective filtering

Keep a daily log of work being done

Tasks

Here are some things I need to do to make this workflow even more effective:

Figure out how to refile tasks to an org-capture target

I want to put them in the “Completed Tasks” entry of that day

File Paths

The location where my Notes folder lives changes in some cases

;; -*- lexical-binding: t; -*-

(setq org-directory
      (if dw/is-termux
          "~/storage/shared/Notes"
        "~/Notes"))

;; (setq org-agenda-files `(,org-directory))
(defun dw/org-path (path)
  (expand-file-name path org-directory))

(setq org-default-notes-file (dw/org-path "Inbox.org"))

(with-eval-after-load 'org-roam
  (defun my/org-roam-project-finalize-hook ()
    "Adds the captured project file to `org-agenda-files' if the
capture was not aborted."
    ;; Remove the hook since it was added temporarily
    (remove-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

    ;; Add project file to the agenda list if the capture was confirmed
    (unless org-note-abort
      (with-current-buffer (org-capture-get :buffer)
        (add-to-list 'org-agenda-files (buffer-file-name)))))

  (defun my/org-roam-find-project ()
    (interactive)
    ;; Add the project file to the agenda after capture is finished
    (add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

    ;; Select a project file to open, creating it if necessary
    (org-roam-node-find
     nil
     nil
     (my/org-roam-filter-by-tag "Project")
     :templates
     '(("p" "project" plain "* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
        :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+category: ${title}\n#+filetags: Project")
        :unnarrowed t))))

  (defun my/org-roam-capture-inbox ()
    (interactive)
    (org-roam-capture- :node (org-roam-node-create)
                       :templates '(("i" "inbox" plain "* %?"
                                     :if-new (file+head "Inbox.org" "#+title: Inbox\n")))))

  (defun my/org-roam-copy-todo-to-today ()
    (interactive)
    (let ((org-refile-keep t) ;; Set this to nil to delete the original!
          (org-roam-dailies-capture-templates
           '(("t" "tasks" entry "%?"
              :if-new (file+head+olp "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n" ("Tasks")))))
          (org-after-refile-insert-hook #'save-buffer)
          today-file
          pos)
      (save-window-excursion
        (org-roam-dailies--capture (current-time) t)
        (setq today-file (buffer-file-name))
        (setq pos (point)))

      ;; Only refile if the target file is different than the current file
      (unless (equal (file-truename today-file)
                     (file-truename (buffer-file-name)))
        (org-refile nil nil (list "Tasks" today-file nil pos)))))

  ;; (add-to-list 'org-after-todo-state-change-hook
  ;;              (lambda ()
  ;;                (when (equal org-state "DONE")
  ;;                  (my/org-roam-copy-todo-to-today))))
  )

Workflow States

I try to keep my set of workflow states simple so that I can filter on them easily:

  • TODO - A task that should be done at some point
  • NEXT - This task should be done next (in the Getting Things Done sense)
  • BACK - A task in the backlog to be done some day but not now
  • WAIT - Waiting for someone else to be actionable again
  • DONE - It’s done!

I also make sure to set them up in the order I want org-sort-entries to sort them in when I use the o option upon invoking that command.

(setq org-todo-keywords
  '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!)")
    (sequence "|" "WAIT(w)" "BACK(b)")))

;; TODO: org-todo-keyword-faces
(setq org-todo-keyword-faces
  '(("NEXT" . (:foreground "orange red" :weight bold))
    ("WAIT" . (:foreground "HotPink2" :weight bold))
    ("BACK" . (:foreground "MediumPurple3" :weight bold))))

Tags

Tags are used to filter all tasks to find anything actionable in a particular context. I use the @home and @work tags for explicit contexts and then the rest are used for filtering tasks for agenda views.

  • followup - Someone is waiting on me to follow up on this task, it should be prioritized above others
  • batch - The task can be batched with others (low effort)
;; Configure common tags
(setq org-tag-alist
  '((:startgroup)
     ; Put mutually exclusive tags here
     (:endgroup)
     ("@home" . ?H)
     ("@work" . ?W)
     ("batch" . ?b)
     ("followup" . ?f)))

Agendas

(setq org-agenda-window-setup 'current-window)
(setq org-agenda-span 'day)
(setq org-agenda-start-with-log-mode t)

;; Make done tasks show up in the agenda log
(setq org-log-done 'time)
(setq org-log-into-drawer t)

(setq org-columns-default-format "%20CATEGORY(Category) %65ITEM(Task) %TODO %6Effort(Estim){:}  %6CLOCKSUM(Clock) %TAGS")

(setq org-agenda-custom-commands
      `(("d" "Dashboard"
         ((agenda "" ((org-deadline-warning-days 7)))
          (tags-todo "+PRIORITY=\"A\""
                     ((org-agenda-overriding-header "High Priority")))
          (tags-todo "+followup" ((org-agenda-overriding-header "Needs Follow Up")))
          (todo "NEXT"
                ((org-agenda-overriding-header "Next Actions")
                 (org-agenda-max-todos nil)))
          (todo "TODO"
                ((org-agenda-overriding-header "Unprocessed Inbox Tasks")
                 (org-agenda-files '(,(dw/org-path "Inbox.org")))
                 (org-agenda-text-search-extra-files nil)))))

        ("n" "Next Tasks"
         ((agenda "" ((org-deadline-warning-days 7)))
          (todo "NEXT"
                ((org-agenda-overriding-header "Next Tasks")))))

        ;; Low-effort next actions
        ("e" tags-todo "+TODO=\"NEXT\"+Effort<15&+Effort>0"
         ((org-agenda-overriding-header "Low Effort Tasks")
          (org-agenda-max-todos 20)
          (org-agenda-files org-agenda-files)))))

Clocking

I like to use org-timer-set-timer to set a countdown timer for a task based on the “effort” value I set on it. Since Org doesn’t seem to automatically clock the task when I set a timer, this hook will take care of that.

(add-hook 'org-timer-set-hook #'org-clock-in)

Capture Templates

(defun dw/get-todays-journal-file-name ()
  "Gets the journal file name for today's date"
  (interactive)
  (let* ((journal-file-name
           (expand-file-name
             (format-time-string "%Y/%Y-%2m-%B.org")
             (dw/org-path "Journal/")))
         (journal-year-dir (file-name-directory journal-file-name)))
    (if (not (file-directory-p journal-year-dir))
      (make-directory journal-year-dir))
    journal-file-name))


(defun dw/on-org-capture ()
  ;; Don't show the confirmation header text
  (setq header-line-format nil)

  ;; Control how some buffers are handled
  (let ((template (org-capture-get :key t)))
    (pcase template
      ("jj" (delete-other-windows)))))

(add-hook 'org-capture-mode-hook 'dw/on-org-capture)

(setq org-capture-templates
  `(("t" "Tasks")
    ("tt" "Task" entry (file ,(dw/org-path "Inbox.org"))
         "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1)
    ("ts" "Clocked Entry Subtask" entry (clock)
         "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1)

    ("j" "Journal Entries")
    ("je" "General Entry" entry
         (file+olp+datetree ,(dw/org-path "Journal.org"))
         "\n* %<%I:%M %p> - %^{Title} \n\n%?\n\n"
         :tree-type week
         :clock-in :clock-resume
         :empty-lines 1)
    ("jt" "Task Entry" entry
         (file+olp+datetree ,(dw/org-path "Journal.org"))
         "\n* %<%I:%M %p> - Task Notes: %a\n\n%?\n\n"
         :tree-type week
         :clock-in :clock-resume
         :empty-lines 1)
    ("jj" "Journal" entry
         (file+olp+datetree ,(dw/org-path "Journal.org"))
         "\n* %<%I:%M %p> - Journal :journal:\n\n%?\n\n"
         :tree-type week
         :clock-in :clock-resume
         :empty-lines 1)))

Old Configuration

The following blocks are being migrated over from my Org Mode configuration in Emacs.org. I’ve disabled tangling for them so that they don’t get written out to workflow.el in favor of my new configuration above.

Agenda

;; Configure custom agenda views
(setq org-agenda-custom-commands
      `(("d" "Dashboard"
         ((agenda "" ((org-deadline-warning-days 7)))
          (todo "FLOW" ((org-agenda-overriding-header "Workflow Tasks")))
          (tags-todo "+PRIORITY=\"A\""
                     ((org-agenda-overriding-header "High Priority")))
          (todo "NEXT"
                ((org-agenda-overriding-header "Next Tasks")))
          (tags-todo "agenda/ACTIVE" ((org-agenda-overriding-header "Active Projects")))
          (todo "TODO"
                ((org-agenda-overriding-header "Unprocessed Inbox Tasks")
                 (org-agenda-files '(,(dw/org-path "Inbox.org")))
                 (org-agenda-text-search-extra-files nil)))))

        ("n" "Next Tasks"
         ((todo "NEXT"
                ((org-agenda-overriding-header "Next Tasks")))))

        ("p" "Active Projects"
         ((agenda "")
          (todo "ACTIVE"
                ((org-agenda-overriding-header "Active Projects")
                 (org-agenda-max-todos 5)
                 (org-agenda-files org-agenda-files)))))

        ("w" "Workflow Status"
         ((todo "WAIT"
                ((org-agenda-overriding-header "Waiting on External")
                 (org-agenda-files org-agenda-files)))
          (todo "REVIEW"
                ((org-agenda-overriding-header "In Review")
                 (org-agenda-files org-agenda-files)))
          (todo "PLAN"
                ((org-agenda-overriding-header "In Planning")
                 (org-agenda-todo-list-sublevels nil)
                 (org-agenda-files org-agenda-files)))
          (todo "BACKLOG"
                ((org-agenda-overriding-header "Project Backlog")
                 (org-agenda-todo-list-sublevels nil)
                 (org-agenda-files org-agenda-files)))
          (todo "READY"
                ((org-agenda-overriding-header "Ready for Work")
                 (org-agenda-files org-agenda-files)))
          (todo "ACTIVE"
                ((org-agenda-overriding-header "Active Projects")
                 (org-agenda-files org-agenda-files)))
          (todo "COMPLETED"
                ((org-agenda-overriding-header "Completed Projects")
                 (org-agenda-files org-agenda-files)))
          (todo "CANC"
                ((org-agenda-overriding-header "Cancelled Projects")
                 (org-agenda-files org-agenda-files)))))

        ;; Projects on hold
        ("h" tags-todo "+LEVEL=2/+HOLD"
         ((org-agenda-overriding-header "On-hold Projects")
          (org-agenda-files org-agenda-files)))

        ;; Low-effort next actions
        ("e" tags-todo "+TODO=\"NEXT\"+Effort<15&+Effort>0"
         ((org-agenda-overriding-header "Low Effort Tasks")
          (org-agenda-max-todos 20)
          (org-agenda-files org-agenda-files)))))

(use-package org-super-agenda
  :after org
  :config
  (org-super-agenda-mode 1)
  (setq org-super-agenda-groups
        '(;; Each group has an implicit boolean OR operator between its selectors.
          (:name "Today"  ; Optionally specify section name
                 :time-grid t  ; Items that appear on the time grid
                 :todo "NEXT")  ; Items that have this TODO keyword
          (:name "Important"
                 ;; Single arguments given alone
                 :priority "A")
          )))

Tags

;; Configure common tags
(setq org-tag-alist
  '((:startgroup)
     ; Put mutually exclusive tags here
     (:endgroup)
     ("@home" . ?H)
     ("@work" . ?W)
     ("batch" . ?b)
     ("next" . ?n)
     ("followup" . ?f)
     ("recurring" . ?r)))

;; Configure task state change tag triggers
;; (setq org-todo-state-tags-triggers
;;   (quote (("CANC" ("cancelled" . t))
;;           ("WAIT" ("waiting" . t))
;;           ("HOLD" ("waiting") ("onhold" . t))
;;           (done ("waiting") ("onhold"))
;;           ("TODO" ("waiting") ("cancelled") ("onhold"))
;;           ("DONE" ("waiting") ("cancelled") ("onhold")))))

Tasks

;; Configure TODO settings
(setq org-datetree-add-timestamp 'inactive)
(setq org-habit-graph-column 60)
(setq org-fontify-whole-heading-line t)
(setq org-todo-keywords
  '((sequence "TODO(t)" "NEXT(n)" "PROC" "FLOW(f)" "|" "DONE(d!)")
    (sequence "BACKLOG(b)" "PLAN(p)" "READY(r)" "ACTIVE(a)" "REVIEW(v)" "WAIT(w@/!)" "HOLD(h)" "|" "COMPLETED(c)" "CANC(k@)")
    (sequence "GOAL(g)" "|" "ACHIEVED(v)" "MAINTAIN(m)")))

Capture Templates

Information on template expansion can be found in the Org manual.

(defun dw/read-file-as-string (path)
  (with-temp-buffer
    (insert-file-contents path)
    (buffer-string)))

(setq org-capture-templates
  `(("t" "Tasks / Projects")
    ("tt" "Task" entry (file+olp ,(dw/org-path "Projects.org") "Projects" "Inbox")
         "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1)
    ("ts" "Clocked Entry Subtask" entry (clock)
         "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1)
    ("tp" "New Project" entry (file+olp ,(dw/org-path "Projects.org") "Projects" "Inbox")
         "* PLAN %?\n  %U\n  %a\n  %i" :empty-lines 1)

    ("j" "Journal Entries")
    ("jj" "Journal" entry
         (file+olp+datetree ,(dw/get-todays-journal-file-name))
         ;"\n* %<%I:%M %p> - Journal :journal:\n\n%?\n\n"
         ,(dw/read-file-as-string "~/Notes/Templates/Daily.org")
         :clock-in :clock-resume
         :empty-lines 1)
    ("jm" "Meeting" entry
         (file+olp+datetree ,(dw/get-todays-journal-file-name))
         "* %<%I:%M %p> - %a :meetings:\n\n%?\n\n"
         :clock-in :clock-resume
         :empty-lines 1)
    ("jt" "Thinking" entry
         (file+olp+datetree ,(dw/get-todays-journal-file-name))
         "\n* %<%I:%M %p> - %^{Topic} :thoughts:\n\n%?\n\n"
         :clock-in :clock-resume
         :empty-lines 1)
    ("jc" "Clocked Entry Notes" entry
         (file+olp+datetree ,(dw/get-todays-journal-file-name))
         "* %<%I:%M %p> - %K :notes:\n\n%?"
         :empty-lines 1)
    ("jg" "Clocked General Task" entry
         (file+olp+datetree ,(dw/get-todays-journal-file-name))
         "* %<%I:%M %p> - %^{Task description} %^g\n\n%?"
         :clock-in :clock-resume
         :empty-lines 1)

    ("w" "Workflows")
    ("we" "Checking Email" entry (file+olp+datetree ,(dw/get-todays-journal-file-name))
         "* Checking Email :email:\n\n%?" :clock-in :clock-resume :empty-lines 1)

    ("m" "Metrics Capture")
    ("mw" "Weight" table-line (file+headline "~/Notes/Metrics.org" "Weight")
     "| %U | %^{Weight} | %^{Notes} |" :kill-buffer)
    ("mp" "Blood Pressure" table-line (file+headline "~/Notes/Metrics.org" "Blood Pressure")
     "| %U | %^{Systolic} | %^{Diastolic} | %^{Notes}" :kill-buffer)))

Module Setup

This Org file produces a file called dw-workflow.el which gets loaded up in init.el; export it as a feature so that it can be loaded with require.

(provide 'dw-workflow)