In this post I’ll share my workflow and the Emacs functions I use to facilitate my workflow. One highlight is better understanding how to use
(file+function "filename" function-finding-location) target element.
One of my administrative tasks for my role at Software Services by Scientist.com is time tracking. It’s been over a decade since I last tracked my working hours. In my role I’m both coding and helping get team members unstuck on their tasks. This means on a given day, I jump between 5 to 7 projects. I find it invigorating helping get folks unstuck; either listening to their approach or digging in and pulling out a myriad of debugging and triage heuristics I’ve developed. To help me with keeping track of all of my hours and I work, I have begun leveraging even more of Emacs’s Org-Mode; a framework and toolkit for note taking and time tracking (and so much more).
My Current Workflow
At the start of my day, I review my existing todo items. This helps me remember where to start.
As I work on a todo item, I record time and take notes; which involves links and capturing further general documentation. Those notes will sometimes turn into blog posts or playbook articles. As I start a new project:
- I start tracking my time.
- Write a bit about what I’m working on.
- And start taking notes.
- For tasks that I don’t complete, I mark as todo.
As I wrap up a project’s task I go back to my todo items.
org-agenda function provides a menu of options to view my time and todo items. See the documentation
At the end of the month I then go through my projects and record that time. I do all of this in my
org-mode agenda file.
Code Supporting My Workflow
Before I started down this path I spent a month exploring, noting, and adjusting my workflow. As the month closed, I started to see the pattern I could use to extend my existing toolkit to better help my emerging workflow.
This section goes into the technical implementation.
org-capture-templates. There are two of them:
- The client’s project that I’m working on.
- The task within a project.
(setq org-capture-templates '(;; Needed for the first project of the day; to ensure the datetree is ;; properly generated. ("p" "Project" entry (file+olp+datetree jf/primary-agenda-filename-for-machine) "* %(jf/org-mode-project-prompt) :project:\n\n%?" :empty-lines-before 1 :empty-lines-after 1) ("t" "Task" ;; I tried this as a node, but that created headaches. Instead I'm ;; making the assumption about project/task depth. plain (file+function jf/primary-agenda-filename-for-machine jf/org-mode-find-project-node) ;; The five ***** is due to the assumptive depth of the projects and tasks. "***** TODO %? :task:\n\n" :empty-lines-before 1 :empty-lines-after 1) ))
Anywhere in Emacs I can call
C-c c in Emacs dialect).
Begin Capturing Notes for the Project
The capture for the project positions the content in the following headline tree:
- Year (e.g. 2022)
- Month (e.g. 2022-09 September)
- Day (e.g. 2022-09-03 Friday)
- Day (e.g. 2022-09-03 Friday)
- Month (e.g. 2022-09 September)
The capture template for the project is (e.g.
* %(jf/org-mode-project-prompt) :project:\n\n%?).
For the Project capture template, this:
- creates a headline
- prompts for the project
- tags the node as a
- positions the cursor to begin note taking.
The following function prompts me to select an existing project or allows me to enter a new one.
(defun jf/org-mode-project-prompt () "Prompt for project based on existing projects in agenda file. Note: I tried this as interactive, but the capture templates insist that it should not be interactive." (completing-read "Project: " (sort (-distinct (org-map-entries (lambda () ;; Get the entry's title (org-element-property :title (org-element-at-point))) ;; By convention project’s are: ;; - a level 4 headline ;; - tagged with :project: "+LEVEL=4+project" ;; Look within all agenda files 'agenda)) #'string<)))
When I started I thought I would need to create a local variable for projects. But I use
org-map-entries to dynamically query the document for existing projects.
I also spent some time on the prompting function; in part because I thought it needed to be
interactive. It does not.
Begin “Capturing” Notes for the Task
The “Task” capture template uses the
file+function directive to find where in the document to insert the task.
The first parameter (e.g.
jf/primary-agenda-filename-for-machine) specifies the agenda file for my machine.
Those machines are work and personal; each with their own todo lists.
The second parameter (e.g.
jf/org-mode-find-project-node) is defined below; it finds and positions the cursor at the end of the given project within the give date.
;; Inspiration from https://gist.github.com/webbj74/0ab881ed0ce61153a82e (cl-defun jf/org-mode-find-project-node (&key (project (jf/org-mode-project-prompt)) ;; The `file+olp+datetree` directive creates ;; a headline like “2022-09-03 Saturday”. (within_headline (format-time-string "%Y-%m-%d %A"))) "Find and position the cursor at the end of the given PROJECT WITHIN_HEADLINE." ;; Ensure we’re using the right agenda file. (with-current-buffer (find-file-noselect jf/primary-agenda-filename-for-machine) (let ((existing-position (org-element-map (org-element-parse-buffer) 'headline ;; Finds the end position of: ;; - a level 4 headline ;; - that is tagged as a :project: ;; - is titled as the given project ;; - and is within the given headline (lambda (hl) (and (=(org-element-property :level hl) 4) ;; I can't use the :title attribute as it is a ;; more complicated structure; this gets me ;; the raw string. (string= project (plist-get (cadr hl) :raw-value)) (member "project" (org-element-property :tags hl)) ;; The element must have an ancestor with a headline of today (string= within_headline (plist-get ;; I want the raw title, no styling nor tags (cadr (car (org-element-lineage hl))) :raw-value)) (org-element-property :end hl))) nil t))) (if existing-position ;; Go to the existing position for this project (goto-char existing-position) (progn ;; Go to the end of the file and append the project to the end (end-of-buffer) (insert (concat "\n**** " project " :project:\n\n")))))))
Current Implementation Constraint
My workflow does not need the “Project” capture. However the “Task” capture needs the headline structure that the “Project” capture creates. Future work that I could do would be for the “Task” capture to create the correct headline(s). But that’s a once a day inconvenience.
My Daily Task Sheet
org-clock-report function provides a plain text tabular breakdown of my work days. Below is an anonymized example:
#+BEGIN: clocktable :scope subtree :maxlevel 5 :tcolumns 4 #+CAPTION: Clock summary at [2022-09-03 Sat 10:12] | Headline | Time | | | | |----------------------------------------------------+---------+-------+------+------| | *Total time* | *14:30* | | | | |----------------------------------------------------+---------+-------+------+------| | \_ 2022-09 September | | 14:30 | | | | \_ 2022-09-01 Thursday | | | 7:45 | | | \_ Client 1 | | | | 0:30 | | \_ Merge and Backport... | | | | 0:30 | | \_ Client 2 | | | | 2:15 | | \_ Get Bitnami SOLR Blocking Done | | | | 2:15 | | \_ Learning Time | | | | 1:30 | | \_ Adjusting Time Tracking Automation... | | | | 0:30 | | \_ Submit Proposal for Responsible... | | | | 0:30 | | \_ Show and Tell | | | | 0:30 | | \_ Client 3 | | | | 1:45 | | \_ Pairing with A regarding Workflows | | | | 1:45 | | \_ Client 4 | | | | 1:15 | | \_ Pairing on #138 | | | | 1:00 | | \_ Reviewing... | | | | 0:15 | | \_ Client 5 | | | | 0:30 | | \_ Pairing with B on Collection Slugs | | | | 0:30 | | \_ 2022-09-02 Friday | | | 6:45 | | | \_ Client 6 | | | | 0:15 | | \_ Pairing with C regarding rebase... | | | | 0:15 | | \_ Learning Time | | | | 0:15 | | \_ Writing About Emacs and Org-Mode Time... | | | | 0:15 | | \_ Client 1 | | | | 2:15 | | \_ Working on troubleshooting upstream... | | | | 0:30 | | \_ Work on Documenting Hyrax’s IIIF... | | | | 1:45 | | \_ Samvera | | | | 0:15 | | \_ Reviewing PR for a Hyrax app without... | | | | 0:15 | | \_ Client 2 | | | | 1:30 | | \_ Working on getting SOLR up and... | | | | 1:30 | | \_ Client 7 | | | | 1:15 | | \_ Client 7 Upgrade Estimate | | | | 1:15 | | \_ Client 5 | | | | 1:00 | | \_ Universal Viewer Overview | | | | 0:45 | | \_ Working with D on Collections | | | | 0:15 | #+END:
In the actual time sheet each of those lines link to the corresponding headline. The provides another way to navigate.
I never quite realized that I would appreciate time tracking. It helps me ensure that I’m not working more hours than I should. At other places, I’d work more hours. Here the time sheet helps set clear boundaries.
This workflow also helps me recover from context shifting. I want to help people get unstuck, but jumping in and out of that context does come with a cognitive cost. The underlying technical workflow provides the ritual/habit for re-orienting to what comes next.
As I mentioned earlier, my agenda file becomes a source for knowledge sharing; either with my future self or as a blog post. This article began as a quick note in my agenda file. And in that agenda file I’ve linked to this article.
Now to write a function to generate my daily stand-up “what did I do”; it should be rather straightforward based on my well structured time sheet and notes.
And as always, you can look to my dotemacs repository for even more regarding my Emacs configuration.