This file contains the description and configuration of the productivity workflow I use with Org Mode.
- Principles
- Tasks
- File Paths
- Workflow States
- Tags
- Agendas
- Clocking
- Capture Templates
- Old Configuration
- Module Setup
Here are some things I need to do to make this workflow even more effective:
I want to put them in the “Completed Tasks” entry of that day
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))))
)
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 pointNEXT
- 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 nowWAIT
- Waiting for someone else to be actionable againDONE
- 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 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 othersbatch
- 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)))
(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)))))
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)
(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)))
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.
;; 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")
)))
;; 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")))))
;; 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)")))
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)))
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)