<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
     xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
     xmlns:georss="http://www.georss.org/georss"
     xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
     xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>cyan.sh</title>
    <atom:link
      href="https://www.cyan.sh/blog/feed.xml"
      rel="self" type="application/rss+xml" />
    <link></link>
    <description><![CDATA[]]></description>
    <language>en</language>
    <pubDate>Sat, 29 Nov 2025 00:00:00 +0000</pubDate>
    <lastBuildDate>Sat, 29 Nov 2025 10:03:16 +0000</lastBuildDate>
    <generator>weblorg 0.1.0 (https://emacs.love/weblorg)</generator>
    <webMaster>birdt_@cyan.sh (BirDt_)</webMaster>

    
    <item>
      <title>Emacs Weather for Australia</title>
      <link>https://www.cyan.sh/blog/posts/emacs-weather-for-australia.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/emacs-weather-for-australia.html</guid>
      <pubDate>Sat, 29 Nov 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
There are a lot of good Emacs weather plugins, but most of the services they use are not as accurate as the <a href="https://www.bom.gov.au/">BOM</a> in Australia.
</p>

<p>
I've created a small weather forecast tool in Elisp, using <a href="https://github.com/tkf/emacs-request">request.el</a> and the BOM's HTTP API (unofficial documentation here <a href="https://trickypr.github.io/bom-weather-docs/">https://trickypr.github.io/bom-weather-docs/</a>).
</p>

<p>
The full module <a href="https://git.cyan.sh/BirDt/theurgy/src/branch/master/userland/weather.el">can be found here</a> in my own configuration, though you might want to change the function names if you intend to copy this into your own config.
</p>

<p>
<code>theurgy-weather-find-geohash</code> provides an interface for finding your geohash, which is the geographic identifier that BOM uses in it's API. You can search by suburb name or postcode, and it will provide you a list of possible results. The geohash string is stored in <code>theurgy-geohash</code>.
</p>

<p>
<code>theurgy-weather-fetch-forecast</code> downloads the forecast data for the next few days to a file named <code>forecast</code> in the Emacs user directory. <code>theurgy-start-forecast-timer</code> starts a timer to refetch this forecast every 30 minutes (this request is non-blocking thanks to request.el), and the script itself runs this function on Emacs startup.
</p>

<p>
<code>theurgy-quick-forecast</code> will prompt for a date, then display the minimum and maximum temperature, as well as a short description ("Sunny", "Cloudy", etc) in the echo area. <code>theurgy-weather</code> will instead display a more detailed weather forecast in a read-only org-mode buffer, for example:
</p>
<div class="org-src-container">
<pre class="src src-org"><span class="org-org-level-1">* 29/11/2025 - Sunny.</span>
Sunny. Winds southeasterly 20 to 30 km/h turning easterly 25 to 40 km/h before dawn then tending northeast to southeasterly 15 to 25 km/h in the middle of the day.
- 16-35&#176;C
- 0% Chance of Rain (0-0mm)
- extreme UV
- High Fire Danger

<span class="org-org-level-1">* 30/11/2025 - Sunny.</span>
Sunny. Winds east to northeasterly 25 to 35 km/h tending northwest to northeasterly 20 to 30 km/h in the morning then tending south to southwesterly 15 to 25 km/h in the early afternoon.
- 21-38&#176;C
- 0% Chance of Rain (0-0mm)
- extreme UV
- High Fire Danger

<span class="org-org-level-1">* 1/12/2025 - Partly cloudy.</span>
Partly cloudy. Light winds becoming southwesterly 20 to 30 km/h during the morning then tending southerly 15 to 20 km/h during the evening.
- 17-27&#176;C
- 10% Chance of Rain (0-0mm)
- extreme UV
- High Fire Danger

</pre>
</div>

<p>
Patches welcome. If there is enough interest I will move this out of my own config and into it's own package.
</p>
]]></description>
    </item>
    
    <item>
      <title>Org Babel for TTRPG Automation</title>
      <link>https://www.cyan.sh/blog/posts/org-babel-for-ttrpg-automation.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/org-babel-for-ttrpg-automation.html</guid>
      <pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
I play a lot of solo tabletop RPGs, mostly for worldbuilding and writing purposes because thinking in the shoes of a person in the world helps you pinpoint what is and isn't ultimately important. Typically I use either pen and paper or <a href="https://jeansenvaars.itch.io/pum-companion">PUM Companion</a> to play, but I would like to play more complex games (particularly of my own design) and that requires some automation to be ergonomic for solo play. I've done this with spreadsheet scripting in the past, but that also has the problem of being very "numbers" focused, and it's difficult to structure it like I want to (it's still a narrative, after all).
</p>

<p>
So, for this <a href="https://donaldh.wtf/2025/10/emacs-carnival-2025-11-an-ode-to-org-babel/">Emacs Carnival</a>, I wanted to solve this problem with Org Babel, because not only can it be used for literate programming in the traditional sense (as I've already talked about in <a href="https://www.cyan.sh/blog/posts/mixing-code-styles-with-org-babel.html">this blog</a>), it can also be used to interact with and manipulate the document the code itself is in. This allows the document to "come alive".
</p>

<p>
As a proof of concept of how I want this to work, I've mocked up a <a href="https://fate-srd.com/fate-accelerated/get-started">Fate Accelerated</a> character sheet which does some very simple automations to make rolling dice easier. I've spent only a few hours mocking this up, and no doubt I will expand it, especially to include an oracle system of some sort for solo play as well. Let's have a look at how it works now.
</p>

<p>
The full sheet can be found <a href="https://git.cyan.sh/BirDt/org-character-sheet/src/branch/master/Solo/fae+opse.org?display=source">here</a> but I'll copy code blocks here as relevant. I won't duplicate the structure, but essentially all the "code" of this sheet is inside it's own code header so that it can be hidden. In that header there is a startup block, which looks like this
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-meta-line">#+name: startup</span>
<span class="org-org-block-begin-line">#+begin_src elisp :results silent
</span><span class="org-org-block">  (</span><span class="org-org-block"><span class="org-keyword">org-sbe</span></span><span class="org-org-block"> </span><span class="org-org-block"><span class="org-string">"fae"</span></span><span class="org-org-block">)

  (</span><span class="org-org-block"><span class="org-keyword">define-minor-mode</span></span><span class="org-org-block"> </span><span class="org-org-block"><span class="org-function-name">srpg-mode</span></span><span class="org-org-block">
    </span><span class="org-org-block"><span class="org-doc">"Minor mode for solo RPG gameplay."</span></span><span class="org-org-block">
    </span><span class="org-org-block"><span class="org-builtin">:init-value</span></span><span class="org-org-block"> nil
    </span><span class="org-org-block"><span class="org-builtin">:keymap</span></span><span class="org-org-block"> (make-keymap))

  (define-key srpg-mode-map (kbd </span><span class="org-org-block"><span class="org-string">"M-r"</span></span><span class="org-org-block">) 'fae/take-action)
</span><span class="org-org-block-end-line">#+end_src</span>
</pre>
</div>

<p>
The <code>org-sbe</code> macro evaluates another elisp code block that contains all the Fate system logic that I need. The minor mode is also defined here because I want key mapping, but I don't want to override any keys in the org major mode in other buffers. It's also defined here because I want the whole file to be redistributable.
</p>

<p>
There is another code block before the Fate code that looks like this:
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-meta-line">#+name: prop-tbl-lookup</span>
<span class="org-org-block-begin-line">#+begin_src elisp :var lookup="" target="" :results silent
</span><span class="org-org-block">  (alist-get target
         (mapcar #'(lambda (x) (cons (replace-regexp-in-string </span><span class="org-org-block"><span class="org-string">"</span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span></span><span class="org-org-block"><span class="org-string">\\[\\[</span></span><span class="org-org-block"><span class="org-string"><span class="org-constant">.*\\</span></span></span><span class="org-org-block"><span class="org-string">]\\[</span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span></span><span class="org-org-block"><span class="org-string">.*</span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span></span><span class="org-org-block"><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span></span><span class="org-org-block"><span class="org-string">\\]\\]"</span></span><span class="org-org-block"> </span><span class="org-org-block"><span class="org-string">"\\2"</span></span><span class="org-org-block"> (cadr x)) (car x)))
                 (cl-remove-if (</span><span class="org-org-block"><span class="org-keyword">lambda</span></span><span class="org-org-block"> (x) (not (listp x))) lookup))
         nil
         nil
         (</span><span class="org-org-block"><span class="org-keyword">lambda</span></span><span class="org-org-block"> (x y) (</span><span class="org-org-block"><span class="org-keyword">let</span></span><span class="org-org-block"> ((z (string-search x y)))
                         (</span><span class="org-org-block"><span class="org-keyword">and</span></span><span class="org-org-block"> z (= 0 z)))))
</span><span class="org-org-block-end-line">#+end_src</span>
</pre>
</div>

<p>
This is a bit disgusting, but essentially what it does is it searches a table (<code>lookup</code>) for a value based on the right column contents (<code>target</code>). In this block, <code>lookup</code> and <code>target</code> are empty strings - the code blocks in the Fate code will set those values when they call this block. This is the least annoying way of fetching values from an org table that I could figure out.
</p>

<p>
You may be wondering about that regex replace. It converts links to their description, which is useful because the Fate approaches (skills) table for each character looks like this:
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-meta-line">#+name: pc1</span>
<span class="org-org-table">| +x | Approach |</span>
<span class="org-org-table">|----+----------|</span>
<span class="org-org-table">| +2 | </span><span class="org-org-link"><a href="elisp:(fae/roll-approach &quot;pc1&quot; &quot;Careful&quot;)">Careful</a></span><span class="org-org-table">  |</span>
<span class="org-org-table">| +0 | </span><span class="org-org-link"><a href="elisp:(fae/roll-approach &quot;pc1&quot; &quot;Clever&quot;)">Clever</a></span><span class="org-org-table">   |</span>
<span class="org-org-table">| +8 | </span><span class="org-org-link"><a href="elisp:(fae/roll-approach &quot;pc1&quot; &quot;Flashy&quot;)">Flashy</a></span><span class="org-org-table">   |</span>
<span class="org-org-table">| +3 | </span><span class="org-org-link"><a href="elisp:(fae/roll-approach &quot;pc1&quot; &quot;Forecful&quot;)">Forceful</a></span><span class="org-org-table"> |</span>
<span class="org-org-table">| +0 | </span><span class="org-org-link"><a href="elisp:(fae/roll-approach &quot;pc1&quot; &quot;Quick&quot;)">Quick</a></span><span class="org-org-table">    |</span>
<span class="org-org-table">| +0 | </span><span class="org-org-link"><a href="elisp:(fae/roll-approach &quot;pc1&quot; &quot;Sneaky&quot;)">Sneaky</a></span><span class="org-org-table">   |</span>
</pre>
</div>

<p>
If you've never seen <code>elisp:</code> links, I wouldn't blame you, but they are perfect here because they turn an otherwise static table into a bunch of clickable buttons. This is 1 way to roll checks in this sheet, which uses this snippet of code:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">fae/roll-approach</span> (table approach)
  <span class="org-doc">"Get careful approach value."</span>
  (+ (fae/roll-ndF 4)
     (<span class="org-keyword">or</span> (org-babel-execute-src-block nil (org-babel-lob-get-info `(babel-call (<span class="org-builtin">:call</span> <span class="org-string">"prop-tbl-lookup"</span> <span class="org-builtin">:arguments</span> ,(format <span class="org-string">"%s, \"%s\""</span> table approach))))) 0)
     (<span class="org-keyword">if</span> (y-or-n-p <span class="org-string">"Any other modifiers?"</span>)
       (read-minibuffer <span class="org-string">"+? "</span>)
       0)))
</pre>
</div>

<p>
<code>fae/roll-ndF</code> is just a dice rolling function and not that interesting. The <code>org-babel-execute-src-block</code> may draw your attention instead. What this is doing is constructing a <code>#+call:</code> block in code, which allows us to pass a dynamic table and approach as parameters. We can't use <code>org-sbe</code> here because any arguments passed to it are treated as strings, <code>(org-sbe table)</code> will treat <code>table</code> as a string literal <code>"table"</code> and not as it's actual value.
</p>

<p>
We have one more way of rolling dice, which is bound the <code>M-r</code> in the minor mode:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">fae/get-approach-modifier</span> (approach)
  <span class="org-doc">"Get approach value."</span>
  (<span class="org-keyword">let</span> ((table (read-string <span class="org-string">"For which approach table? "</span>)))
    (<span class="org-keyword">or</span> (org-babel-execute-src-block nil (org-babel-lob-get-info `(babel-call (<span class="org-builtin">:call</span> <span class="org-string">"prop-tbl-lookup"</span> <span class="org-builtin">:arguments</span> ,(format <span class="org-string">"%s, \"%s\""</span> table approach))))) 0)))

(<span class="org-keyword">defun</span> <span class="org-function-name">fae/take-action</span> ()
  <span class="org-doc">"Say what you're trying to do and insert a roll outcome."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">let*</span> ((action (read-string <span class="org-string">"What are you trying to do? "</span>))
         (dice-roll (fae/roll-ndF 4))
         (approach (completing-read
                  <span class="org-string">"Select approach: "</span>
                  '(<span class="org-string">"Careful"</span> <span class="org-string">"Clever"</span> <span class="org-string">"Flashy"</span> <span class="org-string">"Forceful"</span> <span class="org-string">"Quick"</span> <span class="org-string">"Sneaky"</span>)))
         (app-mod (fae/get-approach-modifier approach))
         (add-mod (<span class="org-keyword">if</span> (y-or-n-p <span class="org-string">"Any other modifiers?"</span>)
                    (read-minibuffer <span class="org-string">"+? "</span>)
                  0)))
    (insert (format <span class="org-string">"%s (Approach %s, Rolled %s+%s+%s=%s)\n"</span> action approach dice-roll app-mod add-mod (+ dice-roll app-mod add-mod)))))
</pre>
</div>

<p>
This is used for logging the action at the current point instead of just displaying the result in the minibuffer, which is good because I like to keep track of my rolls and what I was trying to do with them.
</p>

<p>
And finally, I want this to all set itself up automatically when I open the file, which can be done by putting this at the very bottom to run the startup block when the file opens.
</p>

<div class="org-src-container">
<pre class="src src-fundamental"># Local Variables&#58;
# org-confirm-babel-evaluate: nil
# eval: (progn (org-sbe <span class="org-string">"startup"</span>))
# End:
</pre>
</div>

<p>
And that's all for now, until I extend this to include other features from Fate like fate point tracking and aspects/stunts.
</p>
]]></description>
    </item>
    
    <item>
      <title>Obscure Package (?): tmr</title>
      <link>https://www.cyan.sh/blog/posts/obscure-package-tmr.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/obscure-package-tmr.html</guid>
      <pubDate>Wed, 24 Sep 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
One day in the last few weeks, I had a need to set a couple of timers of different lengths. My phone was out of battery, so I pulled out ol' reliable (typing "5 minute timer" into DuckDuckGo). Just as soon as it had started running, I thought "wow, it seems dumb to open a whole new browser tab for a timer". Then I thought that surely I'm not the first Emacs user to need a timer, and that something must exist here already.
</p>

<p>
Obscured by search results of Emacs Lisp and <code>org-mode</code> timers, I came upon <a href="https://github.com/protesilaos/tmr">tmr</a>, from Prot. You might be surprised that it exceeded my expectations, purely just because of the tabulated view. It fits cleanly into my setup with the below snippet. I recommend you give it a try.
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">use-package</span> tmr)

(add-to-list 'display-buffer-alist
           '(<span class="org-string">"\\\\*tmr-tabulated-view\\\\*"</span>
             (display-buffer-in-side-window)
             (side . top)
             (slot . 4)
             (window-height . 0.1)))
</pre>
</div>
]]></description>
    </item>
    
    <item>
      <title>Controlling Godot with Emacs</title>
      <link>https://www.cyan.sh/blog/posts/controlling-godot-with-emacs.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/controlling-godot-with-emacs.html</guid>
      <pubDate>Wed, 27 Aug 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
Godot already has some support for Emacs with <a href="https://github.com/godotengine/emacs-gdscript-mode">gdscript-mode</a>, but I really dream of being able to control the whole editor from within Emacs directly - never leaving it except maybe for more precise level design.
</p>

<p>
So, that's what I've been working on. Using copious <code>magit-section</code> and a bunch of extremely crappy, poorly formatted Elisp, I'm starting to make my dream a reality.
</p>

<p>
You can see a video demo of the current functionality here: <a href="https://www.youtube.com/watch?v=BimnzXjA0DA">video demo</a>.
</p>

<p>
It's quite bare-bones at the moment, and doesn't support a lot of things that are probably necessary for this to be of any serious use. My plan is to "eat my own dogfood" with this and add things as I want them. Contributions are always welcome, and the code is available <a href="https://git.cyan.sh/BirDt/godot-remote-control">here</a>.
</p>
]]></description>
    </item>
    
    <item>
      <title>Make Neotree &#34;Tab-Local&#34;</title>
      <link>https://www.cyan.sh/blog/posts/make-neotree-tab-local.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/make-neotree-tab-local.html</guid>
      <pubDate>Thu, 21 Aug 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
Not entirely sure if this is already a thing, but here goes.
</p>

<p>
I'm using <a href="https://github.com/SalOrak/whaler.el">whaler.el</a> instead of project.el, because it's important to me to be able to treat arbitrary, non-git repositories as projects. I also like that it's minimal and easy to understand. I've also swapped to using <a href="https://github.com/jaypei/emacs-neotree">neotree</a> instead of <a href="https://github.com/Alexander-Miller/treemacs">treemacs</a>, and generally I'm trying to build a more tab-oriented workflow for myself.
</p>

<p>
It would be nice, then, if neotree automatically updated to show the right project directory when I change tabs, but this isn't default functionality with whaler. Here's my solution, copied from my <a href="https://git.cyan.sh/BirDt/theurgy">config</a>:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defvar</span> <span class="org-variable-name">tab-bar-select-tab-hook</span> nil <span class="org-doc">"Hook for `</span><span class="org-doc"><span class="org-constant">tab-bar-select-tab</span></span><span class="org-doc">'"</span>)
(advice-add 'tab-bar-select-tab <span class="org-builtin">:after</span> (<span class="org-keyword">lambda</span> (x) (run-hooks 'tab-bar-select-tab-hook)))
</pre>
</div>

<p>
As far as I can tell, <code>tab-bar-mode</code> doesn't expose any hooks for selecting tabs natively, so this advice does that for me.
</p>

<p>
To save the working directory as a "tab-local variable", I'm just setting it as the tab bar group name (while keeping it somewhat readable):
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">theurgy-open-project</span> ()
  <span class="org-doc">"Select a project and update neotree to use it as root."</span>
  (<span class="org-keyword">interactive</span>)
  (whaler <span class="org-builtin">:action</span> (<span class="org-keyword">lambda</span> (dir)
                  (find-file (get-project-default-file dir))
                  (tab-rename (file-name-nondirectory (string-remove-suffix <span class="org-string">"/"</span> dir)))
                  (tab-bar-change-tab-group (concat <span class="org-string">"project: "</span> dir))
                  (neotree-dir dir))))
</pre>
</div>

<p>
Finally I can use the previous hook to swap back to the right directory after tab change:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">theurgy-swap-to-tab-project</span> ()
  <span class="org-doc">"Go to the project dir of the tab group."</span>
  (<span class="org-keyword">let</span> ((tab-group-name (tab-bar-tab-group-default (tab-bar--current-tab))))
    (<span class="org-keyword">when</span> (string-match-p <span class="org-string">"^project:"</span> tab-group-name)
      (neotree-dir (string-remove-prefix <span class="org-string">"project: "</span> tab-group-name)))))

(add-hook 'tab-bar-select-tab-hook #'theurgy-swap-to-tab-project)
</pre>
</div>
]]></description>
    </item>
    
    <item>
      <title>Consistency</title>
      <link>https://www.cyan.sh/blog/posts/consistency.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/consistency.html</guid>
      <pubDate>Fri, 08 Aug 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
Along the lines of the <a href="https://takeonrules.com/2025/08/01/emacs-carnival-2025-08-your-elevator-pitch-for-emacs/">August Emacs Carnival</a>, I thought I'd get my notes down on why I personally decided to put effort into learning Emacs. Probably unsurprisingly, I didn't start with this editor. My first text editor was Visual Studio (not Code), followed by a sequence (in a now forgotten order) of Notepad++, Visual Studio Code, Kate, and Neovim. I picked up Emacs around my first year of University (it may have been a bit before that actually), but my usage for a long time was limited just to simple text editing. I didn't start from any distribution, and I rarely touched <code>.emacs</code> unless I wanted some syntax highlighting. I also swapped between other editors when I needed to - I would use Code at work, the built-in editor in Godot, even Notepad for taking notes when something else wasn't available.
</p>

<p>
There was a point where I realised that swapping between tools was introducing some unnecessary mental overhead (I think it was being forced to use some web text editor within AWS, I don't remember). Unbeknown to me, Emacs was the perfect solution. Not just for text, but for everything I could want. I came to value the consistency of my user interface, and not just for text which was my immediate issue, but also for other applications like Git and note-taking (via org). I don't really evangelise tools, because people will use what they're used to, but if I had to pitch Emacs to someone it would really just be "a consistent interface for almost everything, on any platform".
</p>

<p>
It's difficult to overstate how useful this one trait of the application is. Being able to keep the same interface and functionality (more or less) whether I'm on my home Linux systems, or my work machines which have been a mix of Windows and Mac is such a boon. I can have an entire development environment functioning on a client device usually with just 1 software approval, as opposed 30 different approvals for an editor, a git interface, etc.
</p>

<p>
Emacs is a piece of software with a "killer feature" around every corner, but consistency and portability is without question the one that I believe stands before the rest. Even <code>org</code>.
</p>
]]></description>
    </item>
    
    <item>
      <title>Exporting Wikis with Orgmode</title>
      <link>https://www.cyan.sh/blog/posts/exporting-wikis-with-orgmode.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/exporting-wikis-with-orgmode.html</guid>
      <pubDate>Tue, 29 Jul 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
One of the major boons I've found of using <code>org-roam</code> since my rework of my note taking environment is that I've been finding more ways to get use out of backlinks. I figured this would actually be a pretty useless feature, and I never spent so much time with it previously, but now that I'm more actively taking notes (and especially noting down random thoughts) I've found it very helpful. Being able to explore backwards through index cards has really been great for ideation, and particularly for worldbuilding.
</p>

<p>
Unfortunately, while it's great for <i>notes</i>, I still prefer to have a proper project structure when I'm building up the world. Generally, I've done this via <a href="https://kanka.io/">Kanka</a> because it's free, but as I've started a new worldbuilding project recently I figured I should give it a shot in Org directly. I already had some use with <code>org-novelist</code> (which I have stopped using), and while there isn't a direct equivalent for worldbuilding, I figured that if I structured my articles as if I was using Zettelkasten, then it may work out.
</p>

<p>
The main reason that I was using Kanka previously is because the end result is easily shareable. I won't go into the project structure of my worldbuilding setup in depth, but I want to show off the "sharing" part - publishing it as a wiki with <code>ox-publish</code>.
</p>

<p>
As for why I chose to use basic <code>ox-publish</code> over <code>weblorg</code>, which I use for this blog: I want to do some hacky stuff to get some wiki functionality working here, and I want a more free-form directory structure.
</p>

<p>
One other thing: I'm not using <code>org-roam</code> here, because I don't want to mess with having multiple DBs and I don't want to accidentally "cross-contaminate" from my personal notes to my public facing projects. That's a bad idea security-wise.
</p>

<p>
Anyhow, <a href="https://systemcrafters.net/publishing-websites-with-org-mode/building-the-site/">System Crafters</a> has a good page on setting up a basic web project, and that's the basis of my build script. The only addition is this list to the <code>org-publish-project-list</code>:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(list <span class="org-string">"org-static"</span>
      <span class="org-builtin">:base-directory</span> <span class="org-string">"."</span>
      <span class="org-builtin">:base-extension</span> <span class="org-string">"css</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">js</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">json</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">png</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">jpg</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">gif</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">pdf</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">mp3</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">ogg</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">swf"</span>
      <span class="org-builtin">:publishing-directory</span> <span class="org-string">"./export"</span>
      <span class="org-builtin">:recursive</span> t
      <span class="org-builtin">:publishing-function</span> 'org-publish-attachment
      )
</pre>
</div>

<p>
This capture's attachments, and crucially it also captures CSS files, JavaScript files, and JSON files. The CSS is less important here, it's just for styling which can be set like this in the HTML head (<code>base-path</code> here is the base URL of the website):
</p>

<div class="org-src-container">
<pre class="src src-elisp"><span class="org-comment-delimiter">;; </span><span class="org-comment">Customize the HTML output
</span>(<span class="org-keyword">setq</span> org-html-head (concat <span class="org-string">"&lt;link rel=\"stylesheet\" href=\""</span> base-path <span class="org-string">"/styles.css\" type=\"text/css\"&gt;"</span>))
</pre>
</div>

<p>
Just this on it's own already gives pretty exports (well, with a good stylesheet anyway). But there are two things which any good wiki needs: searching, and backlinks.
</p>

<p>
I implemented searching with <a href="https://www.fusejs.io/">Fuse</a>: it's one JavaScript file that sits at the root of the project next to the build script. And now we get to the most disgusting Elisp code you'll ever see. <b>I welcome any attempts to clean this up, I know it is terrible.</b>
Fuse needs a JSON file containing all the contents it should search, and I'm going to construct one by recursively going down through each org file in the project, fetching the title, filetags, and contents, then writing that to a JSON file.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">with-temp-file</span>
  <span class="org-string">"search.json"</span>
(insert <span class="org-string">"searchData = "</span>)
(json-insert (vconcat (seq-filter
                       (<span class="org-keyword">lambda</span> (x) x)
                       (mapcar
                        (<span class="org-keyword">lambda</span> (f)
                          (<span class="org-keyword">when</span> (org-publish-find-property f <span class="org-builtin">:title</span> nil)
                            (list
                             <span class="org-builtin">:path</span> (concat base-path (string-trim-left f <span class="org-string">"."</span>))
                             <span class="org-builtin">:title</span> (car (org-publish-find-property f <span class="org-builtin">:title</span> nil))
                             <span class="org-builtin">:tags</span> (vconcat (org-publish-find-property f <span class="org-builtin">:filetags</span> nil))
                             <span class="org-builtin">:contents</span> (<span class="org-keyword">with-temp-buffer</span>
                                         (insert-file-contents f)
                                         (goto-char (point-min))
                                         (flush-lines <span class="org-string">"^#+"</span>)
                                         (org-replace-all-links-by-description)
                                         (goto-char (point-min))
                                         (flush-lines <span class="org-string">"^$"</span>)
                                         (flush-lines <span class="org-string">"^- $"</span>)
                                         (buffer-string))
                             )))
                        (directory-files-recursively <span class="org-string">"."</span> <span class="org-string">"\\.org$"</span>))))))
</pre>
</div>

<p>
I'm prepending <code>searchData =</code> so that the file can be included in a script tag in the postamble, we'll see this in a bit.
The <code>:contents</code> property is a bit confusing, so step by step:
</p>
<ol class="org-ol">
<li>In a temporary buffer, insert the current org file, then go to the start of it.</li>
<li>Get rid of all property lines.</li>
<li>Replace all links with their descriptions, so getting rid of the plaintext syntax.</li>
<li>Go back to the start of the buffer, and get rid of all empty lines.</li>
<li>Get rid of all lines that are just a single dash - this happens in index cards where I have incomplete bullet lists.</li>
<li>Return the whole buffer as a string.</li>
</ol>

<p>
<code>org-replace-all-links-by-description</code> looks like this:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">org-replace-all-links-by-description</span> (<span class="org-type">&amp;optional</span> start end)
  <span class="org-doc">"Find all org links and replace by their descriptions."</span>
  (goto-char (point-min))
  (<span class="org-keyword">while</span> (re-search-forward org-link-bracket-re nil t)
    (replace-match (match-string-no-properties 
                    (<span class="org-keyword">if</span> (match-end 2) 2 1)))))
</pre>
</div>

<p>
And I got that from <a href="https://emacs.stackexchange.com/questions/10707/in-org-mode-how-to-remove-a-link">here</a>.
Also, in case it seems a bit weird to be using <code>vconcat</code> to turn the list into an array, <code>json-insert</code> doesn't work on lists. And the <code>seq-filter</code> - it's for getting rid of any random null values.
</p>

<p>
Ok, all told that gives me a nice JSON file containing all the metadata I want to search. Now to search it.
</p>

<p>
In the <code>org-html-preamble</code> (I'll come back to this in a second), I have a search box and an empty unordered list.
</p>

<div class="org-src-container">
<pre class="src src-html">&lt;<span class="org-function-name">input</span> <span class="org-variable-name">id</span>=<span class="org-string">"search_input"</span> <span class="org-variable-name">type</span>=<span class="org-string">"text"</span> <span class="org-variable-name">placeholder</span>=<span class="org-string">"Search for..."</span>&gt;
&lt;<span class="org-function-name">ul</span> <span class="org-variable-name">id</span>=<span class="org-string">"search_results"</span>&gt;
</pre>
</div>

<p>
A simple JavaScript script can read when the search input is submitted, and then searches with Fuse. It will push results to the search results list:
</p>

<div class="org-src-container">
<pre class="src src-javascript">document.getElementById(<span class="org-string">"search_input"</span>).addEventListener(<span class="org-string">"keypress"</span>, search);

<span class="org-keyword">const</span> <span class="org-variable-name">fuse</span> = <span class="org-keyword">new</span> <span class="org-type">Fuse</span>(searchData, {includeScore: <span class="org-constant">true</span>, ignoreDiacritics: <span class="org-constant">true</span>, includeMatches: <span class="org-constant">true</span>, ignoreLocation: <span class="org-constant">true</span>, useExtendedSearch: <span class="org-constant">true</span>, threshold: 0.3, keys: [{name: <span class="org-string">'title'</span>, weight: 2},{name: <span class="org-string">'tags'</span>, weight: 3},{name: <span class="org-string">'contents'</span>, weight: 1}]});

<span class="org-keyword">function</span> <span class="org-function-name">search</span>(<span class="org-variable-name">event</span>) {
    <span class="org-keyword">if</span> (event.key == <span class="org-string">"Enter"</span>) {
      <span class="org-keyword">var</span> <span class="org-variable-name">x</span> = document.getElementById(<span class="org-string">"search_input"</span>);
      <span class="org-keyword">var</span> <span class="org-variable-name">r</span> = document.getElementById(<span class="org-string">"search_results"</span>);
      r.innerHTML = <span class="org-string">""</span>;
      <span class="org-keyword">var</span> <span class="org-variable-name">sr</span> = fuse.search(x.value);
      console.log(fuse.search(x.value));
      sr.forEach((result) =&gt; {
          <span class="org-keyword">var</span> <span class="org-variable-name">c</span> = document.createElement(<span class="org-string">'li'</span>);
          <span class="org-keyword">var</span> <span class="org-variable-name">a</span> = document.createElement(<span class="org-string">'a'</span>);
          <span class="org-keyword">var</span> <span class="org-variable-name">b</span> = document.createElement(<span class="org-string">'p'</span>);
          <span class="org-keyword">var</span> <span class="org-variable-name">h</span> = document.createTextNode(result.item.title);

          a.appendChild(h);
          a.title = result.item.title;
          a.href = result.item.path.replace(<span class="org-string">/\.[^/.]+$/</span>, <span class="org-string">".html"</span>);

          <span class="org-keyword">const</span> <span class="org-variable-name">truncate</span> = (input) =&gt; input.length &gt; 125 ? <span class="org-string">`${input.substring(0, 125)}...`</span> : input;

          b.appendChild(document.createTextNode(truncate(result.item.contents)));

          c.appendChild(a);
          c.appendChild(b);
          r.appendChild(c);
      });
    }
}
</pre>
</div>

<p>
Again I am aware that this is terrible. But, when I add this, Fuse, and the search data JSON to the post-amble like so:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">setq</span> org-html-postamble (concat
                        (concat <span class="org-string">"&lt;script type=\"text/javascript\" src=\""</span> base-path <span class="org-string">"/search.json\"&gt;&lt;/script&gt;"</span>)
                        (concat <span class="org-string">"&lt;script src=\""</span> base-path <span class="org-string">"/fuse.js\"&gt;&lt;/script&gt;"</span>)
                        (concat <span class="org-string">"&lt;script src=\""</span> base-path <span class="org-string">"/search.js\"&gt;&lt;/script&gt;"</span>)))
</pre>
</div>

<p>
It all works! Search results get dredged up from the depths and shown underneath the search input bar.
</p>

<p>
Now backlinks.
</p>

<p>
This is actually conceptually simple: iterate through each org file, get a list of all the links inside it, then transpose it so that <code>((source-1 (target-1 target-2)))</code> becomes <code>((target-1 (source-1)) (target-2 (source-1)))</code>, and write that to the page somewhere.
</p>

<div class="org-src-container">
<pre class="src src-elisp"><span class="org-comment-delimiter">;; </span><span class="org-comment">Construct backlink tracker
</span>(<span class="org-keyword">defvar</span> <span class="org-variable-name">backlink-hashmap</span> (make-hash-table <span class="org-builtin">:test</span> #'equal))

(<span class="org-keyword">dolist</span> (i (mapcar
          (<span class="org-keyword">lambda</span> (f)
            (list
             (list <span class="org-builtin">:file</span> (string-trim-left f <span class="org-string">"\\."</span>))
             (list <span class="org-builtin">:links</span> (mapcar
                           (<span class="org-keyword">lambda</span> (l)
                             (string-trim-left (concat <span class="org-string">"/"</span> (file-relative-name (expand-file-name l (file-name-parent-directory f)) <span class="org-string">"."</span>)) <span class="org-string">"\\."</span>))
                           (<span class="org-keyword">with-temp-buffer</span>
                             (insert-file-contents f)
                             (org-element-map (org-element-parse-buffer) 'link
                               (<span class="org-keyword">lambda</span> (link)
                                 (<span class="org-keyword">when</span> (string= (org-element-property <span class="org-builtin">:type</span> link) <span class="org-string">"file"</span>)
                                   (org-element-property <span class="org-builtin">:path</span> link)))))))))
          (directory-files-recursively <span class="org-string">"."</span> <span class="org-string">"\\.org$"</span>)))
  (mapcar
   (<span class="org-keyword">lambda</span> (b)
     (<span class="org-keyword">let</span> ((new-lst (gethash b backlink-hashmap '())))
       (remhash b backlink-hashmap)
       (puthash b
              (append new-lst (list (cadr (assoc <span class="org-builtin">:file</span> i))))
              backlink-hashmap)))
   (cadr (assoc <span class="org-builtin">:links</span> i))))

<span class="org-comment-delimiter">;; </span><span class="org-comment">Remove duplicates
</span>(maphash
 (<span class="org-keyword">lambda</span> (x y)
   (delq nil (delete-dups y)))
 backlink-hashmap)
</pre>
</div>

<p>
For the nth time, this is terrible code. You'll particularly cringe at the file name transformation crap. Regardless, it does work, and <code>backlink-hashmap</code> gets populated in the way we want. One thing of note here: I'm using the relative file name as the key for the hash map, which means that setting the test to <code>equal</code> is necessary for queries to work.
</p>

<p>
Now we can get the backlinks for anyfile with <code>gethash</code> and the name of our file, so we can fetch all of this data in a function for <code>org-html-preamble</code>:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">build-navigation</span> (info)
  <span class="org-doc">"Constructs navigation HTML. INFO contain all export options."</span>
  (concat <span class="org-string">"&lt;nav&gt;
  &lt;ul&gt;
  &lt;li&gt;Project Name&lt;/li&gt;
  &lt;li class=\"float-right sticky\"&gt;&lt;input id=\"search_input\" type=\"text\" placeholder=\"Search for...\"&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=\""</span>
        base-path
        <span class="org-string">"/home.html\"&gt;Home &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=\"#\"&gt;Backlinks &#9662;&lt;/a&gt;
      &lt;ul&gt;"</span>
        (mapconcat
         (<span class="org-keyword">lambda</span> (x)
           (concat <span class="org-string">"&lt;li&gt;&lt;a href=\""</span> (concat base-path (string-trim-right x <span class="org-string">"\\.org"</span>) <span class="org-string">".html"</span>) <span class="org-string">"\"&gt;"</span> (car (org-publish-find-property (expand-file-name (concat <span class="org-string">"."</span> x) (file-name-directory load-file-name)) <span class="org-builtin">:title</span> nil)) <span class="org-string">"&lt;/a&gt;&lt;/li&gt;"</span>))
         (gethash
          (concat <span class="org-string">"/"</span> (file-relative-name (plist-get info <span class="org-builtin">:input-file</span>) (file-name-directory load-file-name)))
          backlink-hashmap))
        <span class="org-string">"&lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;/nav&gt;
&lt;div&gt;
&lt;ul id=\"search_results\"&gt;
&lt;/ul&gt;
&lt;/div&gt;"</span>))

(<span class="org-keyword">setq</span> org-html-preamble 'build-navigation)
</pre>
</div>

<p>
And that's it. Each page now has a drop down in the navigation bar showing all backlinks. Hopefully you learned something in this process, even if that's that I'm terrible at writing Elisp.
</p>
]]></description>
    </item>
    
    <item>
      <title>Architecting a Better Org Workflow</title>
      <link>https://www.cyan.sh/blog/posts/architecting-a-better-org-workflow.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/architecting-a-better-org-workflow.html</guid>
      <pubDate>Wed, 09 Jul 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
There's a new <a href="https://gregnewman.io/blog/emacs-carnival-2025-07-writing-experience/">carnival</a> in town, and I'm joining in. The theme for this month is "Writing Experience", and boy what an experience it is. About 80% of my daily usage of Emacs is writing, but some parts of my workflow are tedious, or worse yet completely unused, and I've had some quality of life stuff that I've been itching to implement for a while. My configuration is not particularly well formatted either. So in all, I think it's a good time to do a bit of Winter cleaning (Southern Hemisphere rules) and build a better configuration for writing specifically.
</p>

<p>
Before looking at what my config looks like currently, let's look at what I'm actually using Emacs to write, since a use case is a good place to start from. When I write in Emacs, 99.9% of the time I'm writing in <code>org-mode</code> (the other 0.01% of the time is in <code>markdown-mode</code> with Emacs Everywhere, but I won't cover that here). By my own self-reflection, this is what I mostly use Emacs for in terms of writing (and in no particular order):
</p>
<ul class="org-ul">
<li>Fiction Writing:
<ul class="org-ul">
<li>Novels or Short Stories</li>
<li>Worldbuilding</li>
</ul></li>
<li>Technical Writing:
<ul class="org-ul">
<li>Essays and Mini-essays</li>
<li>Notes, and To-Dos</li>
<li>Knowledge Management</li>
<li>Literate Programming</li>
</ul></li>
<li>Blog Posts</li>
<li>Tabletop RPG Rules</li>
</ul>

<p>
The requirements for these are all relatively distinct, and my flow for some is more streamlined than others. For example, I'm quite happy with my RPG rules writing setup, at least for my current project; and I'm also pretty happy with Weblorg and blog writing/publishing. I feel like my main points of contention are essay writing and personal knowledge management. So let's have a look at what I'm doing now before I start thinking how to do it better.
</p>

<div class="note">
<p>
I initially started writing this with the intention to do up my fiction writing and TTRPG flows, but it really turned into a complete overhaul of my second brain/PKDB setup. Sorry about that!
</p>

</div>

<div class="outline-2">
<h2 id="the-now-and-reflection">The Now and Reflection</h2>
<div class="outline-text-2">
<p>
This is a list of every external package I'm currently using when writing in <code>org</code>:
</p>
<ul class="org-ul">
<li><code>wc-mode</code></li>
<li><code>org-modern</code></li>
<li><code>powerthesaurus</code></li>
<li><code>eglot</code> with <a href="https://www.cyan.sh/blog/posts/goodbye-languagetool-hello-harper.html">Harper</a></li>
<li><code>org-novelist</code></li>
<li><code>org-roam</code></li>
<li><code>company</code> for use with Harper and Ispell</li>
<li><code>org-transclusion</code></li>
<li><code>yasnippet</code></li>
<li><code>weblorg</code> and <code>templatel</code></li>
</ul>

<p>
I'm not including any packages like <code>ob-racket</code> that enable more language support for babel.
</p>

<p>
<code>wc-mode</code> is invaluable to me, however I wouldn't mind getting rid of the dependency and trying to implement it myself. It would be good practice with Elisp. For the sake of not reinventing the wheel, let's not do that here.
</p>

<p>
<code>org-modern</code> is excellent, but my use of it could probably be better. The incompatibility with <code>org-indent</code> bugs me in particular, which probably means I need to have a look at <code>org-modern-indent</code> instead.
</p>

<p>
<code>powerthesaurus</code> is also invaluable, more-so than <code>wc-mode</code>. However, it would be good to have at least some of this functionality available offline, since I don't always write with an internet connection.
</p>

<p>
<code>eglot</code> and Harper are excellent, but I want to tweak some of the rules. Unfortunately, Harper doesn't have native support for <code>org</code> so there will always be some incompatibility until then, but I'm willing to put up with that until it gets implemented (if it does).
</p>

<p>
<code>org-novelist</code> is&#x2026; good, but I don't make enough use of it to justify it's inclusion. I don't do enough story writing anymore, and the structure is not particularly helpful for worldbuilding or other document types.
</p>

<p>
<code>org-roam</code> is the most contentious thing here. I feel like my "organisation system", meaning my to-do files, commonplace notebook, and PKDB are where I see the biggest friction currently. Sure my current setup works, but it feels disorganised, and I don't use <code>org-roam</code> as much as I should - probably because the main killer feature, backlinks, just doesn't jive well with how my head works. It also doesn't have any good spaced repetition implementation, which I would like to have.
</p>

<p>
<code>company</code>. I'm not going to touch this too much; I already did that in my previous blog about Harper, and I'm quite happy in the state that it's in.
</p>

<p>
<code>org-transclusion</code>. I installed this on a whim because I thought it might be helpful in making <code>org-roam</code> fit better in my head, but I basically haven't touched it since. It might be time for this to go.
</p>

<p>
<code>yasnippet</code> is great, I use it all the time for things like date insertion, but I wouldn't mind adding some more templates to skeleton some document types.
</p>

<p>
I don't have much to say about <code>weblorg</code>, you're reading this right now thanks to it. I have two issues here: firstly, <a href="https://github.com/emacs-love/weblorg/issues/85">citations don't work</a>, and secondly the table of contents links don't work. I will try to fix these, but I'm doubtful that I can. We will see.
</p>
</div>
</div>

<div class="outline-2">
<h2 id="stuff-i-want-to-have">Stuff I Want To Have</h2>
<div class="outline-text-2">
<p>
Big one: I'm crap at writing Bibtex and I use citation generators. The issue is that these are outside Emacs, which causes friction. I think <code>biblio</code> should solve this.
</p>

<p>
The biggest thing I would absolutely love, is for my PKDB to be easily accessible and editable on my phone. Yes, I know Emacs can run on Android, but it's not exactly a pleasant experience - nor should it be, it's a touch interface not a keyboard interface. I've kinda tried to work around this with a <a href="https://www.clockworkpi.com/uconsole">uConsole</a>, but, eh, honestly that workflow has it's own problems. That whole device has it's own usability problems for me that I want to address separately. Back to the main desire here: ideally, I don't want to program my own app in Flutter (although that would be pretty neat, imagine an interactive <code>org-roam-ui</code>) because it's too time consuming currently. I use Orgzly Revived on mobile and I would like my note-taking to be compatible with it; or, in lieu of that, have it be compatible with Logseq (which actually seems like it may be the better solution here).
</p>

<p>
While we're on the topic of mobile, being able to share some files with my girlfriend would be useful - for example to track a shared grocery list. She is using an iPhone, which might make this more complicated.
</p>

<p>
I want to start journaling proper. I've been having fun with solo journaling TTRPGs lately and I think it's not a bad idea to integrate that into my daily life, even if it's just a paragraph or so.
</p>

<p>
If you're not familiar with mini-essays, I recommend watching <a href="https://www.youtube.com/watch?v=N4YjXJVzoZY">this video.</a> The takeaway is that I already sort of do this in <code>org-roam</code>, but that's not exactly the most frictionless setup. Zettelkasten is not made for this type of thing in my opinion, but that may just be because of my organisation.
</p>

<p>
While I'm loving Harper for picking up errors, I also want to be able to inspect the text style of my writing. Currently, I'm doing this with <a href="https://www.expresso-app.org/">Expresso</a> but having this available within Emacs would be greatly beneficial.
</p>

<p>
I want some cleaner text selection commands. Particularly, I want <code>mark-word</code> to select the whole word at the cursor and not just to the end of it. I also want something like <code>mark-sentence</code> (<code>forward-sentence</code> is also a bit buggy, so fixing that might be a good idea). Also along these lines, I want to implement some functionality from <a href="https://essay.app/">essay.app</a>, specifically sentence/paragraph reordering and "focused rewriting".
</p>

<p>
In general I want to change some key-binds to use custom multi-key selection menus through <code>transient</code>, because I find that having such a single entry-point will make it less likely I forget things.
</p>

<p>
More to the TTRPG side, I want to be able to do dice rolls via the standard syntax (<code>xDy+z</code>) and create roll tables that I can reference remotely easily. Card decks would also be very helpful.
</p>

<p>
Finally, I'm currently synchronising org files across devices with git. This sucks, and I want to swap to Syncthing as part of this.
</p>

<hr />

<p>
OK, that was a lot of words. I'm kind of using this document as a brain dump, so apologies for that in advance. I think though, I have a good idea of what I want to do to improve my experience in Org now. So, it's time to get to work.
</p>
</div>
</div>

<div class="outline-2">
<h2 id="structuring">Structuring</h2>
<div class="outline-text-2">
<p>
We need a cool project name for this workflow. Because why not. I'm in a phase of naming things after alchemical/hermetic terms currently (see <a href="https://git.cyan.sh/BirDt/animus">animus</a> and <a href="https://git.cyan.sh/BirDt/ouroboros-emacs-themes">ouroboros-emacs-themes</a>) so let's stick with that theme - I'll call it "org-logos".
</p>

<p>
My Emacs configuration depends on <code>straight</code>, and I will be using it here along with <code>use-package</code>. Besides that, I like to keep everything as self-contained as possible. If I'm referencing a package I have installed globally and not as part of this specific workflow, I'll bring this up here.
</p>

<p>
The best place to start is with configuring <code>org-mode</code> itself, before getting into any additional packages. This comes in 2 parts; one is changing a bunch of variables, and the other is adding an initialisation function which calls a bunch of other functions as a hook. Let's start with the latter.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">org-mode-init</span> ()
  <span class="org-doc">"This is called through org-mode-hook, so that I don't need to add-hook 20 million times."</span>
  <span class="org-comment-delimiter">;; </span><span class="org-comment">I want org to display pictures inline.
</span>  (org-display-inline-images)
  <span class="org-comment-delimiter">;; </span><span class="org-comment">I don't want monospace text by default.
</span>(variable-pitch-mode))

(add-hook 'org-mode-hook #'org-mode-init)
</pre>
</div>

<p>
Regarding <code>variable-pitch-mode</code>, <a href="https://git.cyan.sh/BirDt/ouroboros-emacs-themes">ouroboros-emacs-themes</a> already supports this across all themes, including monospace formatting for code blocks and verbatims, so I won't go into my configuration for that here.
</p>

<p>
We'll add more lines to this as we go along. For now this is a good place to start. Onto the variable changes next.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">setq</span> org-log-done 'time <span class="org-comment-delimiter">;; </span><span class="org-comment">I want to keep track of the time tasks are completed.
</span>    org-return-follows-link t <span class="org-comment-delimiter">;; </span><span class="org-comment">I want to be able to follow links easily.
</span>    org-hide-emphasis-markers t <span class="org-comment-delimiter">;; </span><span class="org-comment">This is a big one: hide markup syntax like ** and //.
</span>    org-pretty-entities t <span class="org-comment-delimiter">;; </span><span class="org-comment">These two variables together render latex entities as UTF8, which means that Greek letters, subscript, and superscript are all rendered correctly.
</span>    org-pretty-entities-include-sub-superscripts t
    org-enforce-todo-dependencies t <span class="org-comment-delimiter">;; </span><span class="org-comment">Prevent changing a parent to DONE if the children aren't
</span>    org-enforce-todo-checkbox-dependencies t
    org-agenda-skip-scheduled-if-done t <span class="org-comment-delimiter">;; </span><span class="org-comment">Don't show done stuff in the agenda view
</span>    org-agenda-skip-deadline-if-done t
    org-agenda-skip-timestamp-if-done t
    org-todo-keywords '((sequence <span class="org-string">"TODO"</span> <span class="org-string">"WIP"</span> <span class="org-string">"DELEGATED"</span> <span class="org-string">"WAITING"</span> <span class="org-string">"|"</span> <span class="org-string">"CANCELLED"</span> <span class="org-string">"DONE"</span>)) <span class="org-comment-delimiter">;; </span><span class="org-comment">Some extra keywords in addition to TODO and DONE
</span>    org-refile-targets '((org-agenda-files <span class="org-builtin">:maxlevel</span> . 2)) <span class="org-comment-delimiter">;; </span><span class="org-comment">Allow any agenda files to be refile targets
</span>    )
</pre>
</div>

<p>
I want to keep all my org agenda files in <code>~/.org</code>, so I'll be hard-coding that value for this next variable:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">setq</span> org-cite-global-bibliography '(<span class="org-string">"~/.org/global.bib"</span>) <span class="org-comment-delimiter">;; </span><span class="org-comment">Global bibliography location)</span>
</pre>
</div>
</div>

<div class="outline-3">
<h3 id="agenda-and-the-exobrain-organisation">Agenda and The Exobrain - Organisation</h3>
<div class="outline-text-3">
<p>
Since I'm already looking at the org-agenda-files variable, this might be a good time to think about the structure of my agenda files and PKDB. Let's abstract a little.
</p>

<p>
There is an <a href="https://www.worthe-it.co.za/blog/2025-05-04-my-exobrain.html">excellent article by Justin Wernick</a> about his exobrain, and another good workshop on <a href="https://guildoftherose.org/workshops/creating-an-exobrain">Guild of the Rose</a> about the same topic. There are essentially three components that I want:
</p>
<ol class="org-ol">
<li>Task tracking and management: this is what org agenda is currently useful for.</li>
<li>Note taking: this is what most people use <code>org-roam</code> for.</li>
<li>Knowledge refinement: this is what I've been using <code>org-roam</code> for, and where things like mini-essays come in. Flashcards can also fall into this category.</li>
</ol>

<p>
Here's the issue that I've realised while writing this post: Zettelkasten is a great methodology for taking notes, specifically short-hand notes, but it's a terrible methodology for long-form knowledge storage <i>in my opinion</i>. I really want to stress that this is just my personal view - everyone's brains work different - but for example having I've been treating <code>org-roam</code> like a wiki with 1 node for each topic (1 for Python, 1 for SQL, etc). I think that having 1 node for an individual snippet of information, and linking everything with index cards, will be much more useful for synthesis at the refinement stage. That way I can also see backlinks being helpful for me.
</p>

<p>
OK, so what can I use to cover the knowledge refinement step? Well, I don't think it needs anything complex: in fact I think splitting the <code>org-roam</code> directory into multiple folders is the solution here.
</p>
</div>
</div>

<div class="outline-3">
<h3 id="prettifying">Prettifying</h3>
<div class="outline-text-3">
<p>
Before we get too stuck in on exobrain optimisation, let's first add some basic user experience enhancements. I like using <code>org-modern</code>, but I also want to fix some minor code block formatting issues that I have with <code>org-indent-mode</code>.
</p>

<div class="org-src-container">
<pre class="src src-elisp">  (<span class="org-keyword">use-package</span> org-modern
  <span class="org-builtin">:hook</span> ((org-mode . org-modern-mode)
         (org-agenda-finalize . org-modern-agenda)))

(<span class="org-keyword">use-package</span> org-modern-indent
  <span class="org-builtin">:straight</span> (org-modern-indent <span class="org-builtin">:type</span> git <span class="org-builtin">:host</span> github <span class="org-builtin">:repo</span> <span class="org-string">"jdtsmith/org-modern-indent"</span>)
  <span class="org-builtin">:hook</span> ((org-mode . org-modern-indent-mode)))

</pre>
</div>

<p>
Also need to set <code>org-startup-indented</code> to <code>t</code> in the big <code>setq</code> block. I also want <code>wc-mode</code>, as stated before.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">use-package</span> wc-mode
  <span class="org-builtin">:hook</span> ((org-mode . wc-mode))
  <span class="org-builtin">:config</span> (<span class="org-keyword">setq</span> wc-modeline-format <span class="org-string">"WC[%tw]"</span>))
</pre>
</div>

<p>
That about covers everything I need or want as far as beautification is concerned.
</p>
</div>
</div>

<div class="outline-3">
<h3 id="generally-useful-stuff">Generally Useful Stuff</h3>
<div class="outline-text-3">
<p>
I want to work a bit on the stuff that's going to be generally useful no matter the form of writing.
</p>

<p>
Let's start with what's been bothering me the most: sentence-based navigation and word/sentence selection. Emacs already has a <code>backward-</code> and <code>forward-sentence</code> function but they don't work for me - they jump to the start and end of the paragraph. The reason for this is <a href="https://emacs.stackexchange.com/questions/71972/why-is-forward-sentence-moving-the-cursor-to-the-end-of-the-paragraph-and-not-wo">very simple</a> and very (in my opinion) stupid: <code>sentence-end-double-space</code> is <code>t</code> by default, which means that a sentence needs to end with two spaces following a period for Emacs to recognise it. Easy enough fix, just <code>setq</code> to <code>nil</code>.
</p>

<p>
With that fixed, we can have some fun with creating custom marking functions for sentences and words, since those don't behave properly either (by design).
</p>

<div class="org-src-container">
<pre class="src src-elisp">  (<span class="org-keyword">defun</span> <span class="org-function-name">mark-whole-word</span> ()
  <span class="org-doc">"Marks the whole word underneath the cursor."</span>
  (<span class="org-keyword">interactive</span>)
  (forward-word)
  (set-mark-command nil)
  (backward-word))

(<span class="org-keyword">defun</span> <span class="org-function-name">mark-sentence</span> ()
  <span class="org-doc">"Marks the entire sentence underneath the cursor."</span>
  (<span class="org-keyword">interactive</span>)
  (forward-sentence)
  (set-mark-command nil)
  (backward-sentence))
</pre>
</div>

<p>
Let's also have some fun with re-ordering sentences and paragraphs. These aren't perfect (for example, paragraph moving can be messy with code blocks) but they're "good enough". I've even made them compatible with <code>C-u</code>.
</p>

<div class="org-src-container">
<pre class="src src-elisp">  (<span class="org-keyword">defun</span> <span class="org-function-name">move-sentence-right</span> (<span class="org-type">&amp;optional</span> arg)
  <span class="org-doc">"Moves the whole sentence to the right of the next sentence."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"^p"</span>)
  (<span class="org-keyword">or</span> arg (<span class="org-keyword">setq</span> arg 1))
  (<span class="org-keyword">while</span> (&gt; arg 0)
    (forward-sentence)
    (backward-sentence)
    (left-char) <span class="org-comment-delimiter">;; </span><span class="org-comment">Capture previous space
</span>    (kill-sentence)
    (forward-sentence)
    (yank)
    (backward-sentence)
    (<span class="org-keyword">setq</span> arg (1- arg))))

(<span class="org-keyword">defun</span> <span class="org-function-name">move-sentence-left</span> (<span class="org-type">&amp;optional</span> arg)
  <span class="org-doc">"Moves the whole sentence to the left of the previous sentence."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"^p"</span>)
  (<span class="org-keyword">or</span> arg (<span class="org-keyword">setq</span> arg 1))
  (<span class="org-keyword">while</span> (&gt; arg 0)
    (forward-sentence)
    (backward-sentence)
    (left-char) <span class="org-comment-delimiter">;; </span><span class="org-comment">Capture previous space
</span>    (kill-sentence)
    (backward-sentence)
    (left-char) <span class="org-comment-delimiter">;; </span><span class="org-comment">Capture previous space
</span>    (yank)
    (backward-sentence)
    (<span class="org-keyword">setq</span> arg (1- arg))))

(<span class="org-keyword">defun</span> <span class="org-function-name">move-paragraph-up</span> (<span class="org-type">&amp;optional</span> arg)
  <span class="org-doc">"Moves the whole paragraph above the previous paragraph."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"^p"</span>)
  (<span class="org-keyword">or</span> arg (<span class="org-keyword">setq</span> arg 1))
  (<span class="org-keyword">while</span> (&gt; arg 0)
    (forward-paragraph)
    (backward-paragraph)
    (kill-paragraph nil)
    (backward-paragraph)
    (yank)
    (backward-paragraph)
    (<span class="org-keyword">setq</span> arg (1- arg))))

(<span class="org-keyword">defun</span> <span class="org-function-name">move-paragraph-down</span> (<span class="org-type">&amp;optional</span> arg)
  <span class="org-doc">"Moves the whole paragraph above the previous paragraph."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"^p"</span>)
  (<span class="org-keyword">or</span> arg (<span class="org-keyword">setq</span> arg 1))
  (<span class="org-keyword">while</span> (&gt; arg 0)
    (forward-paragraph)
    (backward-paragraph)
    (kill-paragraph nil)
    (forward-paragraph)
    (yank)
    (backward-paragraph)
    (<span class="org-keyword">setq</span> arg (1- arg))))
</pre>
</div>

<p>
If you're not aware, you can already narrow the buffer to a paragraph with <code>narrow-to-defun</code>. That's not enough to give me "focused rewriting" by itself though. Let's implement that next, by copying from <a href="https://www.youtube.com/watch?v=E-yk_V5TnNU">this video</a>.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">break-out-sentence</span> ()
  <span class="org-doc">"Breaks a sentence out into it's own buffer for editing."</span>
  (<span class="org-keyword">interactive</span>)
  (backward-sentence)
  (kill-sentence)
  (<span class="org-keyword">let</span> ((buf (generate-new-buffer <span class="org-string">"*break-out*"</span>))
      (window (split-window-below -10)))
    (set-window-buffer window buf)
    (select-window window))
  (erase-buffer)
  (yank)
  (org-mode))

(<span class="org-keyword">defun</span> <span class="org-function-name">break-out-choose-sentence</span> ()
  <span class="org-doc">"Chooses a sentence from the break-out buffer."</span>
  (<span class="org-keyword">interactive</span>)
  (backward-sentence)
  (kill-sentence)
  (other-window -1)
  (yank)
  (select-window (get-buffer-window <span class="org-string">"*break-out*"</span>))
  (kill-buffer-and-window))
</pre>
</div>

<p>
Powerthesaurus is next. I don't need such a complex configuration for this.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">use-package</span> powerthesaurus
  <span class="org-builtin">:bind</span> (<span class="org-string">"C-x t"</span> . powerthesaurus-transient))
</pre>
</div>

<p>
I'm going to use <code>C-c o</code> as my personal command prefix in Org, but Powerthesaurus is generally useful everywhere so I want to keep it on an easily accessible binding. On that note:
</p>

<div class="org-src-container">
<pre class="src src-elisp">  (<span class="org-keyword">defun</span> <span class="org-function-name">break-out-dwim</span> ()
  <span class="org-doc">"Either break-out or choose sentency depending on buffer name."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">if</span> (equal (buffer-name) <span class="org-string">"*break-out*"</span>)
      (break-out-choose-sentence)
    (break-out-sentence)))

(define-key org-mode-map (kbd <span class="org-string">"C-c o b"</span>) #'break-out-dwim)
</pre>
</div>

<p>
Let's also bind some keys for the paragraph and sentence manipulation from before, and marking.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(define-key org-mode-map (kbd <span class="org-string">"C-M-&lt;left&gt;"</span>) #'move-sentence-left)
(define-key org-mode-map (kbd <span class="org-string">"C-M-&lt;right&gt;"</span>) #'move-sentence-right)
(define-key org-mode-map (kbd <span class="org-string">"C-M-&lt;up&gt;"</span>) #'move-paragraph-up)
(define-key org-mode-map (kbd <span class="org-string">"C-M-&lt;down&gt;"</span>) #'move-paragraph-down)

(define-key org-mode-map (kbd <span class="org-string">"C-@"</span>) #'mark-whole-word)
(define-key org-mode-map (kbd <span class="org-string">"M-@"</span>) #'mark-sentence)
</pre>
</div>

<p>
I also want to put all of these manipulation keys into a transient menu.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">require</span> '<span class="org-constant">transient</span>)

(transient-define-prefix reorder-transient ()
  <span class="org-string">"Transient menu for text re-ordering commands."</span>
  [<span class="org-string">"Move sentence..."</span> (<span class="org-string">"l"</span> <span class="org-string">"Left"</span> move-sentence-left)
   (<span class="org-string">"r"</span> <span class="org-string">"Right"</span> move-sentence-right)]
  [<span class="org-string">"Move paragraph..."</span> (<span class="org-string">"u"</span> <span class="org-string">"Up"</span> move-paragraph-up)
   (<span class="org-string">"d"</span> <span class="org-string">"Down"</span> move-paragraph-down)])

(transient-define-prefix mark-menu-transient ()
  <span class="org-string">"Transient menu for marking units of text."</span>
  [<span class="org-string">"Mark"</span> (<span class="org-string">"w"</span> <span class="org-string">"Word"</span> mark-whole-word)
   (<span class="org-string">"s"</span> <span class="org-string">"Sentence"</span> mark-sentence)
   (<span class="org-string">"p"</span> <span class="org-string">"Paragraph"</span> mark-paragraph)
   (<span class="org-string">"b"</span> <span class="org-string">"Buffer"</span> mark-whole-buffer)])

(define-key org-mode-map (kbd <span class="org-string">"C-c o SPC"</span>) #'mark-menu-transient)
(define-key org-mode-map (kbd <span class="org-string">"C-c o r"</span>) #'reorder-transient)
</pre>
</div>

<p>
Apparently this wouldn't be a post on my blog without glazing Harper, so let's add that in here quickly.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">when</span> (<span class="org-keyword">and</span> (not (equal system-type 'windows-nt)) (locate-file <span class="org-string">"harper-ls"</span> exec-path))
  (<span class="org-keyword">with-eval-after-load</span> 'eglot
    (add-to-list 'eglot-server-programs
               '(org-mode . (<span class="org-string">"harper-ls"</span> <span class="org-string">"--stdio"</span>))))

  (<span class="org-keyword">setq-default</span> eglot-workspace-configuration
              '(<span class="org-builtin">:harper-ls</span> (<span class="org-builtin">:dialect</span> <span class="org-string">"Australian"</span>)))

  (add-hook 'org-mode-hook 'eglot-ensure))
</pre>
</div>

<p>
If you're curious, these are my bindings for using Eglot with Flymake to fix up errors. These are globally useful bindings, so I won't add them into the document here.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(global-set-key (kbd <span class="org-string">"M-g &lt;left&gt;"</span>) 'flymake-goto-prev-error)
(global-set-key (kbd <span class="org-string">"M-g &lt;right&gt;"</span>) 'flymake-goto-next-error)
(global-set-key (kbd <span class="org-string">"C-x M-f"</span>) 'eglot-code-actions)
</pre>
</div>

<p>
Also a good idea to set up <code>company</code> with all the right backends.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">org-mode-company-hook</span> ()
    <span class="org-doc">"Set company backends for `</span><span class="org-doc"><span class="org-constant">org-mode</span></span><span class="org-doc">' usage."</span>
    (<span class="org-keyword">setq-local</span> company-backends
            '((company-capf company-dabbrev company-files company-ispell))))
(add-hook 'org-mode-hook #'org-mode-company-hook)
</pre>
</div>

<p>
That seems to be all the generally applicable stuff, except for text style metrics which I'm putting off indefinitely because I'm too stupid/lazy to implement it. Now it's time for the other thing I've been too lazy to touch.
</p>
</div>
</div>

<div class="outline-3">
<h3 id="exobrain-1-task-management">Exobrain 1: Task Management</h3>
<div class="outline-text-3">
<p>
Task management needs to be logically organised, and after a lot of thinking I've arrived on something that's a mix of GTD, PARA, and <a href="https://www.worthe-it.co.za/blog/2025-05-04-my-exobrain.html">Justin Wernick's system</a>.
</p>

<p>
First we need an inbox/capture step. In my <code>~/.org</code> directory, I'm going to have a directory structure like so:
</p>

<pre class="example">
.
├── lore
│   ├── raw
│   └── refined
│       ├── journal
│       └── wiki
└── tasks
    ├── maintenance
    └── projects
	└── career
</pre>

<p>
I'll touch on <code>lore</code> in the next two Exobrain sections, <code>tasks</code> is what's important here. Outside of this structure, I have <code>~/.org/inbox.org</code>, which is where I will dump absolutely everything from new tasks to quick notes. All of this will be refiled to the right place later.
</p>

<p>
Each file in maintenance is for tracking related tasks, such as house related stuff, and has a short preface to detail the area it refers to, followed by sections for scheduled tasks ("Calendar"), one-off tasks ("TO-DO"), recurring tasks ("Routines"), and finished tasks ("Archive"). I never really want to delete any tasks that build up (although I may modify recurring tasks instead of creating a new task) because you never truly know if you'll need that context in the future.
</p>

<p>
Projects are similar to maintenance tasks, except each of the above sections is a level 2 heading under a level 1 "Work" section (which also has a TO-DO state). There are also level 1 sections for acceptance criteria and ideas relevant to the project. Career projects are just tasks for my current job - I want to keep these separate from other tasks.
</p>

<p>
It seems like a pain to manually specify each file here as an agenda file, so let's do it <a href="https://www.reddit.com/r/orgmode/comments/6q6cdk/comment/dkvokt1/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button">automatically</a>.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">setq</span> org-agenda-files (apply 'append (mapcar
                                 (<span class="org-keyword">lambda</span> (dir)
                                   (directory-files-recursively dir org-agenda-file-regexp))
                                 '(<span class="org-string">"~/.org/tasks"</span>))))
</pre>
</div>

<p>
I also want to make some functions for creating new maintenance areas and projects.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">new-maintenance-file</span> (maintenance-area)
  <span class="org-doc">"Create a new maintenance file for a given `</span><span class="org-doc"><span class="org-constant">MAINTENANCE-AREA</span></span><span class="org-doc">'."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"sMaintenance area is: "</span>)
  (find-file (concat <span class="org-string">"~/.org/tasks/maintenance/"</span> maintenance-area <span class="org-string">".org"</span>))
  (insert <span class="org-string">"\n\n* Calendar\n\n* TO-DO\n\n* Routines\n\n* Archive\n"</span>)
  (goto-char (point-min)))

(<span class="org-keyword">defun</span> <span class="org-function-name">new-project-file</span> (project-name acceptance-criteria)
  <span class="org-doc">"Create a new project file for a given `</span><span class="org-doc"><span class="org-constant">PROJECT-NAME</span></span><span class="org-doc">', with some `</span><span class="org-doc"><span class="org-constant">ACCEPTANCE-CRITERIA</span></span><span class="org-doc">'."</span>
  (find-file (concat <span class="org-string">"~/.org/tasks/projects/"</span> project-name <span class="org-string">".org"</span>))
  (insert (concat <span class="org-string">"\n\n* Acceptance Criteria\n\n"</span> acceptance-criteria <span class="org-string">"\n\n* Ideas\n\n* TODO Work\n\n** Calendar\n\n** TO-DO\n\n** Routines\n\n** Archive\n"</span>))
  (goto-char (point-min)))

(<span class="org-keyword">defun</span> <span class="org-function-name">new-generic-project</span> (project-name acceptance-criteria)
  <span class="org-doc">"Interactive wrapper for `</span><span class="org-doc"><span class="org-constant">new-project-file</span></span><span class="org-doc">', for generic projects."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"sProject name is: \nsAcceptance criteria is: "</span>)
  (new-project-file project-name acceptance-criteria))

(<span class="org-keyword">defun</span> <span class="org-function-name">new-career-project</span> (project-name acceptance-criteria)
  <span class="org-doc">"Interactive wrapper for `</span><span class="org-doc"><span class="org-constant">new-project-file</span></span><span class="org-doc">', for career projects."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"sProject name is: \nsAcceptance criteria is: "</span>)
  (new-project-file (concat <span class="org-string">"career/"</span> project-name) acceptance-criteria))
</pre>
</div>

<p>
Now while we do have an inbox file, sometimes I know where something needs to go already and when I capture I would like to be able to specify the target location. This function can do that:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">require</span> '<span class="org-constant">subr-x</span>)

(<span class="org-keyword">defun</span> <span class="org-function-name">select-task-area</span> (location headline)
  <span class="org-doc">"Prompt for a file in ~/.org/tasks/maintenance/ to insert the capture."</span>
  (<span class="org-keyword">let*</span> ((files (directory-files (concat <span class="org-string">"~/.org/tasks/"</span> location) t <span class="org-string">"\\.org$"</span>))
       (choices (mapcar
                 (<span class="org-keyword">lambda</span> (x) (string-remove-suffix <span class="org-string">".org"</span> x))
                 (mapcar #'file-name-nondirectory files)))
       (selected (completing-read <span class="org-string">"Choose a file: "</span> choices nil t)))
  (set-buffer (org-capture-target-buffer (concat <span class="org-string">"~/.org/tasks/"</span> location selected <span class="org-string">".org"</span>)))
  (org-capture-put-target-region-and-position)
  (widen)
  (goto-char (point-min))
  (<span class="org-keyword">setq</span> headline (org-capture-expand-headline headline))
  (re-search-forward (format org-complex-heading-regexp-format
                             (regexp-quote headline))
                     nil t)
  (forward-line 0)))


(<span class="org-keyword">defun</span> <span class="org-function-name">select-maintenance-area</span> (headline)
  (select-task-area <span class="org-string">"maintenance/"</span> headline))

(<span class="org-keyword">defun</span> <span class="org-function-name">select-project</span> (headline)
  (select-task-area <span class="org-string">"projects/"</span> headline))

(<span class="org-keyword">defun</span> <span class="org-function-name">select-career-project</span> (headline)
  (select-task-area <span class="org-string">"projects/career/"</span> headline))
</pre>
</div>

<p>
The astute Emacs guru may realise this is absolutely disgusting. It also doesn't work properly, and gives a bunch of <code>cl-assertion-failed</code> errors on <code>C-c C-c</code>. It also widens to the entire org file after insertion. So it's not exactly smooth, but it does the job.
</p>

<p>
Anyway, we can use this to make capture templates now.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">setq</span> org-capture-templates
      '((<span class="org-string">"i"</span> <span class="org-string">"Idea"</span>)
      (<span class="org-string">"ii"</span> <span class="org-string">"Generic"</span> entry
       (file <span class="org-string">"~/.org/inbox.org"</span>)
       <span class="org-string">"* %^{TITLE} :idea: \n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)
      (<span class="org-string">"ip"</span> <span class="org-string">"Project"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-project <span class="org-string">"Ideas"</span>)))
       <span class="org-string">"* %^{TITLE} \n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)
      (<span class="org-string">"ic"</span> <span class="org-string">"Career"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-career-project <span class="org-string">"Ideas"</span>)))
       <span class="org-string">"* %^{TITLE} \n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)

      (<span class="org-string">"t"</span> <span class="org-string">"Task"</span>)
      (<span class="org-string">"tt"</span> <span class="org-string">"Generic"</span>)
      (<span class="org-string">"ttt"</span> <span class="org-string">"Raw"</span> entry
       (file <span class="org-string">"~/.org/inbox.org"</span>)
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :task:\n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)
      (<span class="org-string">"ttm"</span> <span class="org-string">"Maintenance"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-maintenance-area <span class="org-string">"TO-DO"</span>)))
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)
      (<span class="org-string">"ttp"</span> <span class="org-string">"Project"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-project <span class="org-string">"TO-DO"</span>)))
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)
      (<span class="org-string">"ttc"</span> <span class="org-string">"Career"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-career-project <span class="org-string">"TO-DO"</span>)))
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)
      (<span class="org-string">"ts"</span> <span class="org-string">"Scheduled"</span>)
      (<span class="org-string">"tss"</span> <span class="org-string">"Raw"</span> entry
       (file <span class="org-string">"~/.org/inbox.org"</span>)
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :scheduled:\n:SCHEDULED: %^T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1
       <span class="org-builtin">:time-prompt</span> t)
      (<span class="org-string">"tsm"</span> <span class="org-string">"Maintenance"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-maintenance-area <span class="org-string">"Calendar"</span>)))
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1
       <span class="org-builtin">:time-prompt</span> t)
      (<span class="org-string">"tsp"</span> <span class="org-string">"Project"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-project <span class="org-string">"Calendar"</span>)))
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1
       <span class="org-builtin">:time-prompt</span> t)
      (<span class="org-string">"tsc"</span> <span class="org-string">"Career"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-career-project <span class="org-string">"Calendar"</span>)))
       <span class="org-string">"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1
       <span class="org-builtin">:time-prompt</span> t)

      (<span class="org-string">"n"</span> <span class="org-string">"Note"</span>)
      (<span class="org-string">"nn"</span> <span class="org-string">"Raw"</span> entry
       (file <span class="org-string">"~/.org/inbox.org"</span>)
       <span class="org-string">"* %^{TITLE} :note: \n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1)
      (<span class="org-string">"nm"</span> <span class="org-string">"Meeting"</span>)
      (<span class="org-string">"nmm"</span> <span class="org-string">"Raw"</span> entry
       (file <span class="org-string">"~/.org/inbox.org"</span>)
       <span class="org-string">"* %^{TITLE} :meeting: \n:Created: %T\n%?"</span>
       <span class="org-builtin">:clock-in</span> t
       <span class="org-builtin">:clock-resume</span> t)
      (<span class="org-string">"nmc"</span> <span class="org-string">"Career"</span> entry
       (<span class="org-keyword">function</span> (<span class="org-keyword">lambda</span> () (select-career-project <span class="org-string">"Calendar"</span>)))
       <span class="org-string">"* %^{TITLE} \n:Created: %T\n%?"</span>
       <span class="org-builtin">:empty-lines</span> 1
       <span class="org-builtin">:clock-in</span> t
       <span class="org-builtin">:clock-resume</span> t)))
</pre>
</div>

<p>
Wow that's a lot of words! It's a bit messy (it could definitely be better by putting all the magic strings into variables) but it gets the job done. One final thing, I want to expose all of these commands under a transient menu, and make some bindings for <code>org-capture</code> and <code>org-agenda</code>.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">open-inbox</span> ()
  <span class="org-doc">"Opens `</span><span class="org-doc"><span class="org-constant">~/.org/inbox.org</span></span><span class="org-doc">'"</span>
  (<span class="org-keyword">interactive</span>)
  (find-file <span class="org-string">"~/.org/inbox.org"</span>))

(<span class="org-keyword">defun</span> <span class="org-function-name">open-maintenance</span> ()
  <span class="org-doc">"Opens `</span><span class="org-doc"><span class="org-constant">~/.org/tasks/maintenance/</span></span><span class="org-doc">'"</span>
  (<span class="org-keyword">interactive</span>)
  (find-file <span class="org-string">"~/.org/tasks/maintenance/"</span>))

(<span class="org-keyword">defun</span> <span class="org-function-name">open-projects</span> ()
  <span class="org-doc">"Opens `</span><span class="org-doc"><span class="org-constant">~/.org/tasks/projects/</span></span><span class="org-doc">'"</span>
  (<span class="org-keyword">interactive</span>)
  (find-file <span class="org-string">"~/.org/tasks/projects/"</span>))

(<span class="org-keyword">defun</span> <span class="org-function-name">open-career</span> ()
  <span class="org-doc">"Opens `</span><span class="org-doc"><span class="org-constant">~/.org/tasks/projects/career/</span></span><span class="org-doc">'"</span>
  (<span class="org-keyword">interactive</span>)
  (find-file <span class="org-string">"~/.org/tasks/projects/career/"</span>))

(transient-define-prefix task-management-menu ()
  <span class="org-string">"Transient menu for task management shortcuts."</span>
  [<span class="org-string">"Go To"</span> (<span class="org-string">"i"</span> <span class="org-string">"Inbox"</span> open-inbox)
   (<span class="org-string">"m"</span> <span class="org-string">"Maintenance Directory"</span> open-maintenance)
   (<span class="org-string">"p"</span> <span class="org-string">"Projects Directory"</span> open-projects)
   (<span class="org-string">"c"</span> <span class="org-string">"Career Directory"</span> open-career)]
  [<span class="org-string">"Create New"</span> (<span class="org-string">"M"</span> <span class="org-string">"Maintenance File"</span> new-maintenance-file)
   (<span class="org-string">"P"</span> <span class="org-string">"Project"</span> new-generic-project)
   (<span class="org-string">"C"</span> <span class="org-string">"Career Project"</span> new-career-project)])

(define-key global-map (kbd <span class="org-string">"C-c o t"</span>) #'task-management-menu)  
(define-key global-map (kbd <span class="org-string">"C-c a"</span>) #'org-agenda)
(define-key global-map (kbd <span class="org-string">"C-c a"</span>) #'org-capture)
</pre>
</div>

<p>
And in terms of task management, that's pretty much it. I'm going to ignore Outlook and Jira integration for now because it's a pain (especially if I want it to be portable), and Orgzly Revived will give me an interface to this on mobile (though without the capture templates obviously).
</p>
</div>
</div>

<div class="outline-3">
<h3 id="exobrain-2-note-taking">Exobrain 2: Note Taking</h3>
<div class="outline-text-3">
<p>
As I said earlier, my goal is to use <code>org-roam</code> for capturing notes and snippets, instead of using it as a wiki (which should be reserved for refinement), chained together by index cards. Both this "web" of raw notes and the journals from the next section should work with Logseq, so we need to do some configuration on that end. I'm following closely <a href="https://sbgrl.me/posts/logseq-org-roam-1/">this</a> blog, as well as <a href="https://coredumped.dev/2021/05/26/taking-org-roam-everywhere-with-logseq/">this</a> one. You may remember the previously mentioned structure of <code>~/.org</code> - all the raw notes I'm capturing with <code>org-roam</code> will be in <code>lore/raw</code>.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">use-package</span> org-roam
  <span class="org-builtin">:straight</span> nil
  <span class="org-builtin">:ensure</span> t
  <span class="org-builtin">:init</span>
  (<span class="org-keyword">setq</span> org-roam-v2-ack t)
  <span class="org-builtin">:custom</span>
  (org-roam-directory (file-truename <span class="org-string">"~/.org/lore/raw/"</span>))
  (org-dailies-directory (file-truename <span class="org-string">"~/.org/refined/journal/"</span>))
  (org-roam-file-exclude-regexp <span class="org-string">"\\.git/.*</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">logseq/.*$"</span>)
  (org-roam-completion-everywhere)
  (org-roam-capture-templates
    '((<span class="org-string">"d"</span> <span class="org-string">"default"</span> plain
       <span class="org-string">"%?"</span>
       <span class="org-builtin">:target</span> (file+head <span class="org-string">"${slug}.org"</span> <span class="org-string">"#+title: ${title}\n"</span>)
       <span class="org-builtin">:unnarrowed</span> t)))
  (org-roam-dailies-capture-templates
    '((<span class="org-string">"d"</span> <span class="org-string">"default"</span> entry
       <span class="org-string">"* %?"</span>
       <span class="org-builtin">:target</span> (file+head <span class="org-string">"%&lt;%Y-%m-%d&gt;.org"</span> <span class="org-comment-delimiter">;; </span><span class="org-comment">format matches Logseq
</span>                          <span class="org-string">"#+title: %&lt;%Y-%m-%d&gt;\n"</span>))))
  <span class="org-builtin">:bind</span> ((<span class="org-string">"C-c n f"</span> . org-roam-node-find)
       (<span class="org-string">"C-c n i"</span> . org-roam-node-insert)
       <span class="org-builtin">:map</span> org-roam-dailies-map
       (<span class="org-string">"Y"</span> . org-roam-dailies-capture-yesterday)
       (<span class="org-string">"T"</span> . org-roam-dailies-capture-tomorrow))
  <span class="org-builtin">:bind-keymap</span> (<span class="org-string">"C-c n d"</span> . org-roam-dailies-map)
  <span class="org-builtin">:config</span>
  (<span class="org-keyword">require</span> '<span class="org-constant">org-roam-dailies</span>) <span class="org-comment-delimiter">;; </span><span class="org-comment">Ensure the keymap is available
</span>  (org-roam-db-autosync-mode))
</pre>
</div>

<p>
This configuration sets up all the binds for dailies and nodes. It also sets the filenames for <code>org-roam</code> notes and dailies to be the same as what Logseq is using. I should not that Logseq cannot have multiple pages directories, so it will never be usable for mini-essays, but I don't really care since long writing on a phone is terrible anyway.
</p>

<p>
I'm going to add a <code>(setq org-attach-id-dir "~/.org/lore/assets")</code> so that Logseq can store and see attachments. Logseq also has some interoperability kinks regarding links, but thankfully this package should sort everything out:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">use-package</span> logseq-org-roam
  <span class="org-builtin">:straight</span> (<span class="org-builtin">:host</span> github
          <span class="org-builtin">:repo</span> <span class="org-string">"sbougerel/logseq-org-roam"</span>
          <span class="org-builtin">:files</span> (<span class="org-string">"*.el"</span>)))
</pre>
</div>

<p>
As some notes might go to the inbox, I want a function to refile them to <code>org-roam</code>. There's a <a href="https://org-roam.discourse.group/t/creating-an-org-roam-note-from-an-existing-headline/978">good forum thread</a> that has a great example for this functionality.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">org-roam-create-note-from-headline</span> ()
  <span class="org-doc">"Create an Org-roam note from the current headline and jump to it.

Normally, insert the headline&#8217;s title using the &#8217;#title:&#8217; file-level property
and delete the Org-mode headline. However, if the current headline has a
Org-mode properties drawer already, keep the headline and don&#8217;t insert
&#8216;#+title:'. Org-roam can extract the title from both kinds of notes, but using
&#8216;#+title:&#8217; is a bit cleaner for a short note, which Org-roam encourages."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">let</span> ((title (nth 4 (org-heading-components)))
        (has-properties (org-get-property-block)))
    (org-cut-subtree)
    (org-roam-find-file title nil nil 'no-confirm)
    (org-paste-subtree)
    (<span class="org-keyword">unless</span> has-properties
      (kill-line)
      (<span class="org-keyword">while</span> (outline-next-heading)
        (org-promote)))
    (goto-char (point-min))
    (<span class="org-keyword">when</span> has-properties
      (kill-line)
      (kill-line))))
</pre>
</div>

<p>
For now, I'm going to leave this as it is. I really need to live in this workflow to see how it feels and what changes need to be made. I will make a transient menu for all the <code>org-roam</code> functions however.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(transient-define-prefix org-roam-menu ()
  <span class="org-string">"Transient menu for task management shortcuts."</span>
  [<span class="org-string">"Node"</span> (<span class="org-string">"f"</span> <span class="org-string">"Capture"</span> org-roam-node-find)
   (<span class="org-string">"i"</span> <span class="org-string">"Insert"</span> org-roam-node-insert)
   (<span class="org-string">"r"</span> <span class="org-string">"Refile"</span> org-roam-create-node-from-headline)
   (<span class="org-string">"v"</span> <span class="org-string">"Visit"</span> org-roam-node-visit)   ]
  [<span class="org-string">"Dailies"</span> (<span class="org-string">"t"</span> <span class="org-string">"Today"</span> org-roam-dailies-capture-today)
   (<span class="org-string">"y"</span> <span class="org-string">"Yesterday"</span> org-roam-dailies-capture-yesterday)
   (<span class="org-string">"n"</span> <span class="org-string">"Tomorrow"</span> org-roam-dailies-capture-tomorrow)]
  [<span class="org-string">"Logseq"</span> (<span class="org-string">"R"</span> <span class="org-string">"Fix links"</span> logseq-org-roam)])

(define-key global-map (kbd <span class="org-string">"C-c o n"</span>) #'org-roam-menu)
</pre>
</div>
</div>
</div>

<div class="outline-3">
<h3 id="exobrain-3-refinement">Exobrain 3: Refinement</h3>
<div class="outline-text-3">
<p>
Refinement covers three things in particular:
</p>
<ol class="org-ol">
<li>Journaling</li>
<li>Mini-essay writing and information "aggregation"</li>
<li>"Output", meaning anything that makes use of the information in a broader sense (such as proper essays, or even using my notes for work).</li>
</ol>

<p>
Journaling is already covered by <code>org-roam-dailies</code> and Logseq, so we'll skip it over. Mini essays, and proper essays, are where I can make some improvements. Firstly, I want to update my referencing workflow.
</p>

<p>
I use BibTeX to store all my sources when I'm working on a bigger writing project. Most of my references will sit in <code>global.bib</code>, unless I'm working on a self-contained project. There's two parts to making my citation workflow a bit nicer: first, I want to have some way of pulling source formatting automatically for resources like arXiv and doi.org, and second I want a transient menu for inserting references in BibTeX.
</p>

<p>
The first can be solved just by installing <code>biblio</code>, since it already allows pulling sources from doi and arXiv. Simple enough.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">use-package</span> biblio)
</pre>
</div>

<p>
And done. Now for the transient menu. I want quick access to doi and arXiv - as well as quick access to some existing entry templates (mainly I'll use this for online resources).
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">defun</span> <span class="org-function-name">bibtex-online-entry</span> ()
  (<span class="org-keyword">interactive</span>)
  (bibtex-entry <span class="org-string">"Online"</span>))

(<span class="org-keyword">defun</span> <span class="org-function-name">bibtex-misc-entry</span> ()
  (<span class="org-keyword">interactive</span>)
  (bibtex-entry <span class="org-string">"Misc"</span>))

(<span class="org-keyword">defun</span> <span class="org-function-name">bibtex-software-entry</span> ()
  (<span class="org-keyword">interactive</span>)
  (bibtex-entry <span class="org-string">"Software"</span>))

(transient-define-prefix bibtex-transient-menu ()
  <span class="org-string">"Transient menu for task management shortcuts."</span>
  [<span class="org-string">"Biblio"</span> (<span class="org-string">"d"</span> <span class="org-string">"doi.org"</span> doi-insert-bibtex)
   (<span class="org-string">"x"</span> <span class="org-string">"arXiv"</span> arxiv-lookup)]
  [<span class="org-string">"Templates"</span> (<span class="org-string">"o"</span> <span class="org-string">"Online Resource"</span> bibtex-online-entry)
   (<span class="org-string">"m"</span> <span class="org-string">"Misc"</span> bibtex-misc-entry)
   (<span class="org-string">"s"</span> <span class="org-string">"Software"</span> bibtex-software-entry)])

(define-key bibtex-mode-map (kbd <span class="org-string">"C-c r"</span>) #'bibtex-transient-menu)
</pre>
</div>

<p>
Now I'm quite happy with my essay writing workflow already, but I wouldn't mind some extra helper functions for mini-essays. I actually want to make use of org-roam for this, so I'm going to slightly modify the configuration. First, the <code>org-roam-directory</code> will be changed to just <code>~/.org/lore</code>, then I'll update the capture template to push to raw. I'll create another capture template specifically for mini-essays - the goal here is to keep them discoverable within <code>org-roam</code>.
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">use-package</span> org-roam
<span class="org-builtin">:straight</span> nil
<span class="org-builtin">:ensure</span> t
<span class="org-builtin">:init</span>
(<span class="org-keyword">setq</span> org-roam-v2-ack t)
<span class="org-builtin">:custom</span>
(org-roam-directory (file-truename <span class="org-string">"~/.org/lore/"</span>))
(org-dailies-directory (file-truename <span class="org-string">"~/.org/lore/refined/journal/"</span>))
(org-roam-file-exclude-regexp <span class="org-string">"\\.git/.*</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">logseq/.*$"</span>)
(org-roam-completion-everywhere)
(org-roam-capture-templates
 '((<span class="org-string">"c"</span> <span class="org-string">"Raw"</span> plain
    <span class="org-string">"%?"</span>
    <span class="org-builtin">:target</span> (file+head <span class="org-string">"raw/${slug}.org"</span> <span class="org-string">"#+title: ${title}\n"</span>)
    <span class="org-builtin">:unnarrowed</span> t)
   (<span class="org-string">"r"</span> <span class="org-string">"Refined"</span>)
   (<span class="org-string">"rm"</span> <span class="org-string">"Mini-essay"</span> plain
    <span class="org-string">"%?"</span>
    <span class="org-builtin">:target</span> (file+head <span class="org-string">"refined/wiki/${slug}.org"</span> <span class="org-string">"#+title: ${title}\n#+author: Jakub Nowak"</span>)
    <span class="org-builtin">:unnarrowed</span> t)))
(org-roam-dailies-capture-templates
 '((<span class="org-string">"d"</span> <span class="org-string">"default"</span> entry
    <span class="org-string">"* %?"</span>
    <span class="org-builtin">:target</span> (file+head <span class="org-string">"%&lt;%Y-%m-%d&gt;.org"</span> <span class="org-comment-delimiter">;; </span><span class="org-comment">format matches Logseq
</span>                       <span class="org-string">"#+title: %&lt;%Y-%m-%d&gt;\n"</span>))))
<span class="org-builtin">:bind</span> ((<span class="org-string">"C-c n l"</span> . org-roam-buffer-toggle)
       (<span class="org-string">"C-c n f"</span> . org-roam-node-find)
       (<span class="org-string">"C-c n g"</span> . org-roam-graph)
       (<span class="org-string">"C-c n i"</span> . org-roam-node-insert)
       (<span class="org-string">"C-c n c"</span> . org-roam-capture)
       <span class="org-builtin">:map</span> org-roam-dailies-map
       (<span class="org-string">"Y"</span> . org-roam-dailies-capture-yesterday)
       (<span class="org-string">"T"</span> . org-roam-dailies-capture-tomorrow))
<span class="org-builtin">:bind-keymap</span> (<span class="org-string">"C-c n d"</span> . org-roam-dailies-map)
<span class="org-builtin">:config</span>
(<span class="org-keyword">require</span> '<span class="org-constant">org-roam-dailies</span>) <span class="org-comment-delimiter">;; </span><span class="org-comment">Ensure the keymap is available
</span>(org-roam-db-autosync-mode))
</pre>
</div>

<p>
Now that is essentially done. I may add extra templates for additional refinement stuff - index cards come to mind - but for now this is good enough.
</p>
</div>
</div>

<div class="outline-3">
<h3 id="stuff-i-didn-t-do">Stuff I Didn't Do</h3>
<div class="outline-text-3">
<p>
This was rather a lot of work, but I think I'm done with messing with this configuration for now. I did also set up Syncthing over the course of writing this post. There are some things that I left out as they're too annoying to implement for me currently, namely:
</p>
<ol class="org-ol">
<li>Something similar to Expresso</li>
<li>Dice and cards</li>
<li>Jira and Outlook integration with Agenda</li>
<li>A better system for worldbuilding</li>
</ol>

<p>
However I do feel like I addressed my main points of friction with <code>org-roam</code> - I feel comfortable getting more use out of it now that I have it available on mobile. In general it feels nicer to have everything be more organised.
</p>

<p>
So yeah, that's it. This was a bit of a messy journey that turned into a complete reorganisation of my life, but I hope you enjoyed it and got some useful things out of it as well. You can find the full configuration file <a href="https://git.cyan.sh/BirDt/org-logos/src/branch/master/logos.el">here</a>.
</p>
</div>
</div>
</div>
]]></description>
    </item>
    
    <item>
      <title>Mixing Code Styles with org-babel</title>
      <link>https://www.cyan.sh/blog/posts/mixing-code-styles-with-org-babel.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/mixing-code-styles-with-org-babel.html</guid>
      <pubDate>Wed, 02 Jul 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
I've really started making good use of <code>org-babel</code> recently as part of my job, and <code>org-babel-tangle</code> has been particularly invaluable. I never honestly expected to get any use out of <code>org-babel</code> outside of taking lecture notes or working through textbooks. Don't get me wrong, it's a very useful tool - and I want to explain how I'm using it now that I've found a really solid use-case for it.
</p>

<p>
If you're unfamiliar, <code>org-babel</code> is a literate programming extension for <code>org-mode</code>. On a surface level, it works similar to a Python notebook. It allows you to specify what major mode to treat a code block as and execute code directly from within your <code>org-mode</code> file. It also allows you to rip code out of code-blocks and place it into another file. So, for example, I can have two code-blocks at different points in my <code>org</code> file, both with a <code>:tangle file.cpp</code> header, and have them both be inserted (in order) into <code>file.cpp</code>. This feature is often used for literate configs within Emacs - where one <code>org</code> file contains all the configuration code, with suitable annotations.
</p>

<p>
I don't use a literate config (currently). Actually, when it comes to Lisp I'm generally against being too literate in your code, either in code comments or literal literate programming. There's a quote out of <a href="https://mumble.net/~campbell/scheme/style.txt">Riastradh's Lisp Style Rules</a> that I generally adhere to:
</p>

<blockquote>
<p>
Write comments only where the code is incapable of explaining itself.
Prefer self-explanatory code over explanatory comments.  Avoid
`literate programming' like the plague.
</p>

<p>
Rationale:  If the code is often incapable of explaining itself, then
perhaps it should be written in a more expressive language.  This may
mean using a different programming language altogether, or, since we
are talking about Lisp, it may mean simply building a combinator
language or a macro language for the purpose.  `Literate programming'
is the logical conclusion of languages incapable of explaining
themselves; it is a direct concession of the inexpressiveness of the
computer language implementing the program, to the extent that the
only way a human can understand the program is by having it rewritten
in a human language.
</p>
</blockquote>

<p>
In Lisp, via macros, we usually have the luxury of completely changing the "language" (not literally) of the code we write, which means that code can, in my opinion, actually be self-documenting. Whenever I feel the impulse to annotate something with a comment, I try to take time to reflect on whether that line could instead be written in a better way that requires less external explanation. There are exceptions, like marking where a section of some logic begins and ends, but that's my general philosophy. It's also why I like Racket so much - being able to switch as required between effectively any syntax you want for a particular problem is very useful for maintaining readability in my opinion. Or just because I want to write something using APL syntax.
</p>

<p>
However, at work, I don't have the luxury of writing in Lisp. Currently I'm doing data engineering with <code>dbt</code>, and I'm mostly writing in SQL (with Jinja macros sprinkled on top). There's also been a lot of hubbub around code style recently, for a number of reasons I won't get into, and now we are recommended to use <code>sqlfluff</code> to fix up formatting on SQL files. So, my two issues here are:
</p>
<ol class="org-ol">
<li>SQL can be <i>very</i> opaque without sufficient commentary (something that is very true for a lot of older <code>dbt</code> models in this codebase).</li>
<li>I do not like the standard <code>sqlfluff</code> formatting rules. This is really just a personal grip, but having functions and keywords be CAPITALISED has always seemed a bit pointless to me, as though it's a relic from a time before syntax highlighting.</li>
<li>Bonus: <code>dbt</code> tests are often undocumented and (this is a problem with a lot of test suites, not just <code>dbt</code>) are abstracted away from the context of the code they're testing.</li>
</ol>

<p>
<code>org-babel</code> helps me fix all of these issues in one fell swoop. Better yet, it lets me do so without ever bothering other people working in the repository with <code>org-mode</code> or Emacs.
</p>

<p>
Let's tackle the first issue: opaqueness and literate programming. There is more to this than just explaining that "Oh, line X does Y and Z". The business context of a particular decision can also be very helpful to keep track of, and so is the story or task in Jira that a model or code block is a part of. While the former of these two could be done with just code comments, the latter benefits greatly from <code>org</code>'s structural editing. For example, if a bunch of user stories are related and should be (per the system design) in one folder in the tree, we can structure the file like so (<code>DE-</code> is the user story prefix in Jira):
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-level-1">* DE-1: Building some pipeline</span>
<span class="org-org-level-2">** Model 1</span>
<span class="org-org-level-2">** Model 2</span>
</pre>
</div>

<p>
Even better, some models are made up of a massive chain of SQL CTEs. We can add those to our structure too:
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-level-1">* DE-1: Building some pipeline</span>
<span class="org-org-level-2">** Model 1</span>
<span class="org-org-level-3">*** CTE 1</span>
<span class="org-org-level-3">*** CTE 2</span>
</pre>
</div>

<p>
For each model, we can specify the file we want the model to untangle to via header properties for each code-block.
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-level-1">* DE-1: Building some pipeline</span>
<span class="org-org-level-2">** Model 1</span>
<span class="org-org-drawer">:PROPERTIES:
</span><span class="org-org-special-keyword">:header-args:sql:</span> <span class="org-org-property-value">:tangle model_1.sql :comments no :padline no</span>
<span class="org-org-special-keyword">:header-args:yaml:</span> <span class="org-org-property-value">:tangle _model_1.yml :comments no :padline no</span>
<span class="org-org-drawer">:END:
</span>
<span class="org-org-level-3">*** CTE 1</span>
<span class="org-org-level-3">*** CTE 2</span>
</pre>
</div>

<p>
This way, every SQL code-block within Model 1 automatically gets tangled into the right file, and the YAML (which is used for test specification in <code>dbt</code>) goes to its own file. I can write as much as I want around a code block, and it will also be ignored during the tangle. But, if I want a specific piece of text to be inserted as a comment in the output file (which I might want to do, since the output is going into version control and no one but me will ever see the 'master file'), I can do that by adding <code>:comments org</code> to the header of the code-block.
</p>

<p>
OK, so literate programming is solved. Let's look at the bonus issue next since that's still more in the realm of literate programming as a whole. One benefit of this setup is that I can write my YAML tests directly next to the actual SQL source code. For example:
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-block-begin-line">#+begin_src sql
</span><span class="org-org-block">  </span><span class="org-org-block"><span class="org-keyword">select</span></span><span class="org-org-block"> something </span><span class="org-org-block"><span class="org-keyword">from</span></span><span class="org-org-block"> somewhere
</span><span class="org-org-block-end-line">#+end_src
</span>
<span class="org-org-drawer">:testing:
</span>This goes to the yaml.
<span class="org-org-block-begin-line">#+begin_src yaml
</span>  - name: some_column
    data_Type: INTEGER
    tests:
      - not_null
<span class="org-org-block-end-line">#+end_src
</span><span class="org-org-drawer">:end:</span>
</pre>
</div>

<p>
This of course requires setting up the boilerplate for the YAML file, which I do in a separate code-block before the first CTE section. I also use this preface to specify the <code>dbt</code> materialization and table alias, as well as add a general comment as to the purpose of the model.
</p>

<p>
If you're familiar with YAML, you will know that indentation is used to determine scope, and tangling <code>org</code> files tends to mess up the whitespace rules. The solution here is to set the <code>org-src-preserve-indentation</code> variable to <code>t</code> - but we want this to be on a per-file basis. And we have some other Elisp that we want to run, so that we can fix the second issue of code style. I have an Elisp code block that looks like this at the start of the <code>org</code> file:
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-meta-line">#+name: startup</span>
<span class="org-org-block-begin-line">#+begin_src elisp :results silent
</span><span class="org-org-block">  (</span><span class="org-org-block"><span class="org-keyword">setq</span></span><span class="org-org-block"> org-src-preserve-identation t)

  (</span><span class="org-org-block"><span class="org-keyword">defun</span></span><span class="org-org-block"> </span><span class="org-org-block"><span class="org-function-name">sql-format</span></span><span class="org-org-block"> ()
    </span><span class="org-org-block"><span class="org-doc">"Run sqlfluff on the current file."</span></span><span class="org-org-block">
    (call-process-shell-command (concat </span><span class="org-org-block"><span class="org-string">"sqlfluff fix --dialect snowflake"</span></span><span class="org-org-block"> buffer-file-name)
                            nil
                            nil))

  (add-hook 'org-babel-post-tangle-hook (</span><span class="org-org-block"><span class="org-keyword">lambda</span></span><span class="org-org-block"> () (</span><span class="org-org-block"><span class="org-keyword">when</span></span><span class="org-org-block"> (string-match-p </span><span class="org-org-block"><span class="org-string">".sql\\"</span></span><span class="org-org-block"> (buffer-file-name))
                                                   (beginning-of-buffer)
                                                   (flush-lines </span><span class="org-org-block"><span class="org-string">"-- :end:"</span></span><span class="org-org-block">)
                                           (flush-lines </span><span class="org-org-block"><span class="org-string">"^$"</span></span><span class="org-org-block">)
                                           (save-buffer)
                                           (sql-format))))
</span><span class="org-org-block-end-line">#+end_src</span>
</pre>
</div>

<p>
The <code>sql-format</code> function just runs <code>sqlfluff</code> over the file of the current buffer, our team is using Snowflake so that's the dialect I've selected. I want a hook to run this function on the output whenever I tangle an SQL file, so that's what the <code>add-hook</code> block is doing. The reason for the two <code>flush-lines</code> calls is to get rid of any <code>:end:</code> comments (which come up when I want a comment after a <code>:testing:</code> section) and to remove any empty lines (since <code>sqlfluff</code> will handle padding on it's own). This means that I can write the SQL however I like, and then not care about formatting the output. In fact, until I send the PR for review, I never even have to read the actual output of the master file. I can just work in it the whole time.
</p>

<p>
We can run this code-block from within <code>org-mode</code> with <code>C-c C-c</code>, but it's a bit of a chore to do this each time I enter the file. There is a better way: with local variables I can execute this code-block when I enter the file. This is also the reason for <code>#+name: startup</code> at the top:
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-comment"># Local Variables&#58;</span>
<span class="org-comment"># org-confirm-babel-evaluate: nil</span>
<span class="org-comment"># eval: (progn (org-babel-goto-named-src-block "startup") (org-babel-execute-src-block))</span>
<span class="org-comment"># End:</span>
</pre>
</div>

<p>
Chuck this at the end of the <code>org</code> file, and now every time you open it that startup block will run.
</p>

<p>
That's basically it. There's a few caveats: obviously, once this gets merged into the version control then any changes won't synchronise with the <code>org</code> file. This could be fixed with link comments and <code>org-babel-detangle</code>, but that goes against the making sure no one else in the team ever needs to interact with <code>org</code>. This workflow is supposed to be for myself only, and not interrupt anyone else in their reading or writing of the code. This does mean that once it starts going through review, and especially once any code is actually merged, the master file is essentially useless as it won't stay up to date. But that's OK, because usually any changes that are made after merge are minor, and I can still keep the <code>org</code> file updated with any information that shouldn't be a file comment. Or, I can export the whole thing as a word document to describe the implementation in plain English to the reviewer. Not sure if that will ever come up, but it's nice to have the option.
</p>
]]></description>
    </item>
    
    <item>
      <title>Goodbye LanguageTool, Hello Harper</title>
      <link>https://www.cyan.sh/blog/posts/goodbye-languagetool-hello-harper.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/goodbye-languagetool-hello-harper.html</guid>
      <pubDate>Sun, 29 Jun 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
I found out about <a href="https://writewithharper.com/">Harper</a> the other day. Ever since I heard about it, I've been itching to set it up in my config and get rid of the horrible LanguageTool setup I had going. Now that I've got it working, I've gotta say I'm quite impressed. My previous LanguageTool setup was not smooth at all, and definitely not as fast as Harper is. I'm not sure how anyone else is doing spellchecking in Emacs (and particularly in <code>org-mode</code>) at the moment, but I recommend anyone using Emacs for most of their writing to give it a shot.
</p>

<p>
I won't embarrass myself with a comparison of my LanguageTool config. Just know that it was slow and terrible. Copied from the Harper docs, this is what my config looks like:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">when</span> (<span class="org-keyword">and</span> (not (equal system-type 'windows-nt)) (locate-file <span class="org-string">"harper-ls"</span> exec-path))
  (<span class="org-keyword">with-eval-after-load</span> 'eglot
    (add-to-list 'eglot-server-programs
               '(org-mode . (<span class="org-string">"harper-ls"</span> <span class="org-string">"--stdio"</span>))))

  (<span class="org-keyword">setq-default</span> eglot-workspace-configuration
              '(<span class="org-builtin">:harper-ls</span> (<span class="org-builtin">:dialect</span> <span class="org-string">"Australian"</span>)))

  (add-hook 'org-mode-hook 'eglot-ensure))
</pre>
</div>

<p>
Some caveats: it doesn't look like Harper has <a href="https://github.com/Automattic/harper/issues/79#issuecomment-2638110954">support for <code>org-mode</code> specifically yet</a>, but it seems to generally work OK despite that. I have however noticed that it doesn't lint property tags, so for example your <code>#+title:</code> won't be spellchecked. Another minorly annoying little niggle is that straight seems to <a href="https://github.com/joaotavora/eglot/discussions/1487">misbehave with eglot</a>, or eldoc more specifically, printing <code>eldoc error: (invalid-function incf)</code> in the minibuffer instead of an actual error message. I was using <code>lsp-mode</code> before this - although I'm beginning to like the eglot interface more and more as I write this, so I think I may swap over to it where possible - so I never realised this issue. Per that GitHub issue link, <code>(use-package eldoc :straight (:type built-in))</code> seems to be the solution. 
</p>

<p>
The only thing that's missing, and to be fair LanguageTool doesn't implement this either (in fact to my knowledge nothing implements this, I don't even think there's a Word plugin), is <a href="https://www.expresso-app.org/">text style metrics analysis</a>. Every so often I get the impulse to try to untangle what the source code for that website is doing, and then write a simple elisp wrapper for it, but I always get sidetracked before I start. Maybe now is the time.
</p>

<div class="outline-2">
<h2 id="addendum">Addendum</h2>
<div class="outline-text-2">
<p>
I've discovered another few quirks in setting this up (mostly to do with eglot and company), so I figured I'd go back to update this post and catalogue what I've had to do for future reference.
</p>

<p>
eglot <a href="https://github.com/joaotavora/eglot/issues/324">overwrites <code>company-backends</code> by default</a>. This can be disabled with <code>(setq eglot-stay-out-of '(company))</code>. Figuring this out also convinced me to finally fix <code>company-ispell</code>, which I could not get working last time I tried.
To get it working with both Ispell and flyspell it seems to need to following code-block:
</p>

<div class="org-src-container">
<pre class="src src-elisp">(<span class="org-keyword">if</span> (file-exists-p <span class="org-string">"/usr/bin/hunspell"</span>)
    (<span class="org-keyword">progn</span>
      (eval-after-load <span class="org-string">"ispell"</span>
        '(<span class="org-keyword">progn</span>
         (<span class="org-keyword">setq</span> ispell-program-name <span class="org-string">"hunspell"</span>
               ispell-dictionary   <span class="org-string">"en_AU"</span>
               ispell-alternate-dictionary (file-truename (concat user-emacs-directory <span class="org-string">"en_AU.dict"</span>))
               ispell-local-dictionary-alist '((<span class="org-string">"en_AU"</span> <span class="org-string">"[[:alpha:]]"</span> <span class="org-string">"[</span><span class="org-string"><span class="org-negation-char">^</span></span><span class="org-string">[:alpha:]]"</span> <span class="org-string">"[']"</span> nil (<span class="org-string">"-d"</span> <span class="org-string">"en_AU,en_AU-med"</span>) nil utf-8)))
         (<span class="org-keyword">defun</span> <span class="org-function-name">ispell-get-coding-system</span> () 'utf-8)))
      (eval-after-load <span class="org-string">"flyspell"</span>
        '(<span class="org-keyword">progn</span>
         (<span class="org-keyword">setq</span> ispell-program-name <span class="org-string">"hunspell"</span>
               ispell-dictionary   <span class="org-string">"en_AU"</span>
               ispell-alternate-dictionary (file-truename (concat user-emacs-directory <span class="org-string">"en_AU.dict"</span>))
               ispell-local-dictionary-alist '((<span class="org-string">"en_AU"</span> <span class="org-string">"[[:alpha:]]"</span> <span class="org-string">"[</span><span class="org-string"><span class="org-negation-char">^</span></span><span class="org-string">[:alpha:]]"</span> <span class="org-string">"[']"</span> nil (<span class="org-string">"-d"</span> <span class="org-string">"en_AU,en_AU-med"</span>) nil utf-8)))
         (<span class="org-keyword">defun</span> <span class="org-function-name">ispell-get-coding-system</span> () 'utf-8)))))
</pre>
</div>

<p>
I'm using Hunspell here - if you don't also <code>eval-after-load</code> for flyspell, then <code>ispell-program-name</code> gets overwritten (in my case, to <code>enchant-2</code>, which wasn't working). The alternate dictionary needs to be created, Hunspell doesn't ship a plaintext dictionary. You can make it by going to <code>/usr/share/hunspell/</code> and running <code>unmunch en_AU.dic en_AU.aff &gt;&gt; /~//.emacs.d/en_AU.dict</code>. You'll notice that I'm using <code>file-truename</code> to turn that user Emacs directory into an absolute path - for some reason, it wasn't working with just the relative path.
</p>
</div>
</div>
]]></description>
    </item>
    
    <item>
      <title>Making Emacs Dashboard Show Treemacs Workspaces</title>
      <link>https://www.cyan.sh/blog/posts/making-emacs-dashboard-show-treemacs-workspaces.html</link>
      <author>Jakub Nowak</author>
      <guid isPermaLink="false">https://www.cyan.sh/blog/posts/making-emacs-dashboard-show-treemacs-workspaces.html</guid>
      <pubDate>Fri, 20 Jun 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>
I don't use projectile, or project.el, or any other project management system in Emacs. Maybe I should, but I "grew up" without having these systems and in my opinion they add complexity onto something that your filesystem (and git) already does fine on its own. So, to that end, I use <a href="https://github.com/Alexander-Miller/treemacs">Treemacs</a> extensively for project management.
</p>

<p>
I generally follow a Workspace -&gt; Project hierarchy. For example, here's a small snippet of my Treemacs persist file, with two workspaces and some varying projects underneath them.
</p>

<div class="org-src-container">
<pre class="src src-org"><span class="org-org-level-1">* Website</span>
<span class="org-org-level-2">** cyan.sh</span>
 - <span class="org-org-list-dt">path ::</span> ~/.../cyan.sh
<span class="org-org-level-1">* Extending Emacs</span>
<span class="org-org-level-2">** dotfiles</span>
- <span class="org-org-list-dt">path ::</span> ~/.emacs.d
<span class="org-org-level-2">** ouroboros themes</span>
- <span class="org-org-list-dt">path ::</span> ~/..../ouroboros-emacs-themes
</pre>
</div>

<p>
One thing that I wanted for a while was the ability to show workspaces in <a href="https://github.com/emacs-dashboard/emacs-dashboard">Dashboard</a>, because I usually want to go back to my most recent workspace when launching a new client instance. Same reason that people want thier projectile projects to show up. Unfortunately, Dashboard doesn't have any such integration already, so I had to hack some together. If I was smarter, I would do this in a way that's more portable. Currently it requires both Dashboard and Treemacs to be installed via Straight, as per the first two lines.
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp">(load-file (concat user-emacs-directory <span class="org-string">"straight/build/dashboard/dashboard-widgets.el"</span>))
(load-file (concat user-emacs-directory <span class="org-string">"straight/build/treemacs/treemacs-workspaces.el"</span>))


(add-to-list 'dashboard-item-generators
           '(workspaces . dashboard-insert-workspaces)
           t)

(add-to-list 'dashboard-item-shortcuts
           '(workspaces . <span class="org-string">"w"</span>)
           t)

(<span class="org-keyword">defun</span> <span class="org-function-name">dashboard-insert-workspaces</span> (list-size)
  <span class="org-doc">"Add the list of LIST-SIZE items of treemacs workspaces."</span>
  (dashboard-insert-section
   <span class="org-string">"Workspaces:"</span>
   (dashboard-subseq (mapcar (<span class="org-keyword">lambda</span> (x) (cl-struct-slot-value 'treemacs-workspace 'name x)) treemacs--workspaces) list-size)
   list-size
   'workspaces
   (dashboard-get-shortcut 'workspaces)
   `(<span class="org-keyword">lambda</span> (<span class="org-type">&amp;rest</span> _) (treemacs-do-switch-workspace ,el))
   (<span class="org-keyword">let*</span> ((workspace-name el))
     workspace-name)))
</pre>
</div>
]]></description>
    </item>
    

  </channel>
</rss>
